Compare commits

...
Author SHA1 Message Date
GrovkillenandGitHub 1f1dd6f6b8 Update README.md 2018-02-02 12:51:37 +01:00
DatuXandGitHub 8f7fd936b8 Update README.md 2018-02-02 01:13:00 +01:00
JustMe-NLandDatuX 884205feea Update _P118_MPU6050.ino
Added safeguard against never triggering if detection window is smaller then detection threshold.
Bumped status from Development to Testing.
2017-02-27 15:05:25 +01:00
JustMe-NLandDatuX 21bd7d73d1 Update Misc.ino
Added wind sensor for Plugin 186 & FHEM or generic HTTP controllers.
2017-02-19 18:30:55 +01:00
DatuXandGitHub cd60d32966 Update README.md 2017-02-19 04:56:35 +01:00
DatuXandGitHub e53a92cd82 Update README.md 2017-02-19 04:48:50 +01:00
Edwin Eefting c618d37f0b rename to correct name (118). 2017-02-18 23:48:52 +01:00
Edwin Eefting f450aa4a01 intergrated required changes for _P186_Ventus_W266.ino into core (with proper ifdefs) 2017-02-18 23:48:52 +01:00
Edwin Eefting 32b9f40d02 added _P186_Ventus_W266.ino 2017-02-18 23:48:52 +01:00
JustMe-NLandDatuX ae082fd2bb Create _P186_Ventus_W266
Plugin to embed a esp into the display unit of a Ventus W266 weather station and sniff the data send by the remote sensor unit. The received data can then be send it to a controller like Domoticz.
2017-02-18 23:48:52 +01:00
JustMe-NLandDatuX 315adbc42f Create _P117_MPU6050.ino
This plugin lets the user read the raw values of the sensor, supports both addresses and can be set to monitor movement by setting range-threshold values for each axis (or disable monitoring for a axis) and set a movement detection threshold.

By first monitoring the raw values, a baseline can be determined using the home controllers ability to graph the values. The thresholds can then be added to the plugin and a timeframe can be set in which the thresholds need to be exceeded a programmable number of times before movement is signalled as a switch. By adding multiple instances of the plugin, multiple movement detection schemes can be set, this way it is possible to track a (e.g. washing) cycle.

It is still in development to fine tune the inner workings but is in a working order.
2017-02-18 23:48:52 +01:00
GeraldandDatuX face8a8bfc Update _C025.ino
server port should also be in host header
http status codes starting with 2 are ok, not only 200
2017-02-18 23:48:52 +01:00
Edwin Eefting fe1025e470 implemented speaker plugin from http://www.letscontrolit.com/forum/viewtopic.php?f=4&t=343&hilit=speaker&start=10 2017-02-18 23:48:52 +01:00
Edwin Eefting 4f6d7d1b82 blynk had the wrong plugin id defined 2017-02-18 23:48:52 +01:00
Edwin Eefting 894a01a534 Added all plugins from playground that didnt require extra dependencies and didnt have compile errors. They are all in the DEV plugin group because i dont know their development state yet. 2017-02-18 23:48:52 +01:00
Edwin Eefting a3317bcde1 moved blynk into testing 2017-02-18 23:48:52 +01:00
DatuX 6d9cbb3988 Update README.md 2017-02-18 23:48:52 +01:00
DatuX aa9cf0a795 Update README.md 2017-02-18 23:48:52 +01:00
DatuX d8244e1726 Update README.md 2017-02-18 23:48:52 +01:00
DatuX cabcd33a63 Update README.md 2017-02-18 23:48:52 +01:00
Edwin Eefting 3140833abb added P036 to development plugin set 2017-02-18 23:48:52 +01:00
Edwin Eefting 890351b920 show plugins sets 2017-02-18 23:48:52 +01:00
Edwin Eefting 0868b983d4 now show git version in webinterface 2017-02-18 23:48:52 +01:00
Aurelien.HannoteauxandDatuX 997e612c97 miss rename function name 2017-02-18 23:48:52 +01:00
Aurelien.HannoteauxandDatuX aadb6e0521 Add MQ2 gaz sensor plugin 2017-02-18 23:48:52 +01:00
Edwin Eefting fcd5c1ec01 ignore Version.h because its automaticly generated on builds with platformio 2017-02-18 23:48:52 +01:00
EA4GKQandDatuX e9c55e8685 _C001 Sent humstat to domoticz
Calculate humstat from humidity value.

Tested on 145
2017-02-18 23:48:52 +01:00
ChdidandDatuX 72c57aa2e3 add-Blynk-1.0 2017-02-18 23:48:52 +01:00
30 changed files with 5443 additions and 56 deletions
+1
View File
@@ -0,0 +1 @@
Version.h
+55 -24
View File
@@ -27,12 +27,12 @@ void ExecuteCommand(byte source, const char *Line)
Serial.print(int(loopCounterLast / 30));
Serial.println(F(")"));
}
if (strcasecmp_P(Command, PSTR("SerialFloat")) == 0)
{
success = true;
pinMode(1,INPUT);
pinMode(3,INPUT);
pinMode(1, INPUT);
pinMode(3, INPUT);
delay(60000);
}
@@ -110,7 +110,7 @@ void ExecuteCommand(byte source, const char *Line)
if (strcasecmp_P(Command, PSTR("TaskRun")) == 0)
{
success = true;
SensorSendTask(Par1 -1);
SensorSendTask(Par1 - 1);
}
if (strcasecmp_P(Command, PSTR("TimerSet")) == 0)
@@ -152,7 +152,7 @@ void ExecuteCommand(byte source, const char *Line)
int index = event.indexOf(',');
if (index > 0)
{
event = event.substring(index+1);
event = event.substring(index + 1);
SendUDPCommand(Par1, (char*)event.c_str(), event.length());
}
}
@@ -165,19 +165,19 @@ void ExecuteCommand(byte source, const char *Line)
int index = event.indexOf(',');
if (index > 0)
{
String topic = event.substring(0,index);
String value = event.substring(index+1);
MQTTclient.publish(topic.c_str(), value.c_str(),Settings.MQTTRetainFlag);
String topic = event.substring(0, index);
String value = event.substring(index + 1);
MQTTclient.publish(topic.c_str(), value.c_str(), Settings.MQTTRetainFlag);
}
}
if (strcasecmp_P(Command, PSTR("SendToUDP")) == 0)
{
success = true;
String strLine = Line;
String ip = parseString(strLine,2);
String port = parseString(strLine,3);
int msgpos = getParamStartPos(strLine,4);
String ip = parseString(strLine, 2);
String port = parseString(strLine, 3);
int msgpos = getParamStartPos(strLine, 4);
String message = strLine.substring(msgpos);
byte ipaddress[4];
str2ip((char*)ip.c_str(), ipaddress);
@@ -191,16 +191,16 @@ void ExecuteCommand(byte source, const char *Line)
{
success = true;
String strLine = Line;
String host = parseString(strLine,2);
String port = parseString(strLine,3);
int pathpos = getParamStartPos(strLine,4);
String host = parseString(strLine, 2);
String port = parseString(strLine, 3);
int pathpos = getParamStartPos(strLine, 4);
String path = strLine.substring(pathpos);
WiFiClient client;
if (client.connect(host.c_str(), port.toInt()))
{
client.print(String("GET ") + path + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
unsigned long timer = millis() + 200;
while (!client.available() && millis() < timer)
@@ -217,6 +217,38 @@ void ExecuteCommand(byte source, const char *Line)
}
}
#ifdef PLUGIN_BUILD_TESTING
// ****************************************
// special commands for Blynk
// ****************************************
if (strcasecmp(Command, "BlynkGet") == 0)
{
String event = Line;
event = event.substring(9);
int index = event.indexOf(',');
if (index > 0)
{
int index = event.lastIndexOf(',');
String blynkcommand = event.substring(index+1);
float value = 0;
if (Blynk_get(blynkcommand, &value))
{
UserVar[(VARS_PER_TASK * (Par1 - 1)) + Par2 - 1] = value;
}
else
status = F("Error getting data");
}
else
{
if (!Blynk_get(event))
{
status = F("Error getting data");
}
}
}
#endif
// ****************************************
// configure settings commands
@@ -238,19 +270,19 @@ void ExecuteCommand(byte source, const char *Line)
success = true;
WifiScan();
}
if (strcasecmp_P(Command, PSTR("WifiConnect")) == 0)
{
success = true;
WifiConnect(1);
}
if (strcasecmp_P(Command, PSTR("WifiDisconnect")) == 0)
{
success = true;
WifiDisconnect();
}
if (strcasecmp_P(Command, PSTR("Reboot")) == 0)
{
success = true;
@@ -354,12 +386,11 @@ void ExecuteCommand(byte source, const char *Line)
}
yield();
if (success)
status += F("\nOk");
else
else
status += F("\nUnknown command!");
SendStatus(source,status);
SendStatus(source, status);
yield();
}
+9 -5
View File
@@ -187,6 +187,7 @@
#define SENSOR_TYPE_SWITCH 10
#define SENSOR_TYPE_DIMMER 11
#define SENSOR_TYPE_LONG 20
#define SENSOR_TYPE_WIND 21
#define PLUGIN_INIT_ALL 1
#define PLUGIN_INIT 2
@@ -218,6 +219,7 @@
#define BOOT_CAUSE_COLD_BOOT 1
#define BOOT_CAUSE_EXT_WD 10
#include "Version.h"
#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <WiFiUdp.h>
@@ -347,7 +349,7 @@ struct SettingsStruct
unsigned long ConnectionFailuresThreshold;
int16_t TimeZone;
boolean MQTTRetainFlag;
boolean InitSPI;
boolean InitSPI;
} Settings;
struct ExtraTaskSettingsStruct
@@ -497,6 +499,9 @@ unsigned long flashWrites = 0;
String eventBuffer = "";
// Blynk_get prototype
boolean Blynk_get(String command,float *data = NULL );
/*********************************************************************************************\
* SETUP
\*********************************************************************************************/
@@ -612,7 +617,7 @@ void setup()
for (byte x = 0; x < TASKS_MAX; x++)
if (Settings.TaskDeviceTimer[x] !=0)
timerSensor[x] = millis() + 30000 + (x * Settings.MessageDelay);
timer = millis() + 30000; // startup delay 30 sec
}
else
@@ -811,7 +816,7 @@ void runEach30Seconds()
loopCounterMax = loopCounterLast;
WifiCheck();
}
@@ -895,7 +900,7 @@ void SensorSendTask(byte TaskIndex)
if (success)
{
for (byte varNr = 0; varNr < VARS_PER_TASK; varNr++)
{
{
if (ExtraTaskSettings.TaskDeviceFormula[varNr][0] != 0)
{
String spreValue = String(preValue[varNr]);
@@ -1017,4 +1022,3 @@ void backgroundtasks()
checkUDP();
yield();
}
+178 -1
View File
@@ -23,6 +23,9 @@ byte getValueCountFromSensorType(byte sensorType)
break;
case SENSOR_TYPE_TEMP_HUM_BARO:
case SENSOR_TYPE_TRIPLE:
#ifdef PLUGIN_186
case SENSOR_TYPE_WIND:
#endif
valueCount = 3;
break;
case SENSOR_TYPE_QUAD:
@@ -1327,7 +1330,7 @@ String parseTemplate(String &tmpString, byte lineSize)
newString.replace("%ip%", strIP);
newString.replace("%sysload%", String(100 - (100 * loopCounterLast / loopCounterMax)));
// padding spaces
while (newString.length() < lineSize)
newString += " ";
@@ -2244,3 +2247,177 @@ void createRuleEvents(byte TaskIndex)
rulesProcessing(eventString);
}
}
#ifdef PLUGIN_BUILD_TESTING
#define isdigit(n) (n >= '0' && n <= '9')
/********************************************************************************************\
Generate a tone of specified frequency on pin
\*********************************************************************************************/
void tone(uint8_t _pin, unsigned int frequency, unsigned long duration) {
analogWriteFreq(frequency);
analogWrite(_pin,100);
delay(duration);
analogWrite(_pin,0);
}
/********************************************************************************************\
Play RTTTL string on specified pin
\*********************************************************************************************/
void play_rtttl(uint8_t _pin, char *p )
{
#define OCTAVE_OFFSET 0
// Absolutely no error checking in here
int notes[] = { 0,
262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494,
523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988,
1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976,
2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951
};
byte default_dur = 4;
byte default_oct = 6;
int bpm = 63;
int num;
long wholenote;
long duration;
byte note;
byte scale;
// format: d=N,o=N,b=NNN:
// find the start (skip name, etc)
while(*p != ':') p++; // ignore name
p++; // skip ':'
// get default duration
if(*p == 'd')
{
p++; p++; // skip "d="
num = 0;
while(isdigit(*p))
{
num = (num * 10) + (*p++ - '0');
}
if(num > 0) default_dur = num;
p++; // skip comma
}
// get default octave
if(*p == 'o')
{
p++; p++; // skip "o="
num = *p++ - '0';
if(num >= 3 && num <=7) default_oct = num;
p++; // skip comma
}
// get BPM
if(*p == 'b')
{
p++; p++; // skip "b="
num = 0;
while(isdigit(*p))
{
num = (num * 10) + (*p++ - '0');
}
bpm = num;
p++; // skip colon
}
// BPM usually expresses the number of quarter notes per minute
wholenote = (60 * 1000L / bpm) * 4; // this is the time for whole note (in milliseconds)
// now begin note loop
while(*p)
{
// first, get note duration, if available
num = 0;
while(isdigit(*p))
{
num = (num * 10) + (*p++ - '0');
}
if (num) duration = wholenote / num;
else duration = wholenote / default_dur; // we will need to check if we are a dotted note after
// now get the note
note = 0;
switch(*p)
{
case 'c':
note = 1;
break;
case 'd':
note = 3;
break;
case 'e':
note = 5;
break;
case 'f':
note = 6;
break;
case 'g':
note = 8;
break;
case 'a':
note = 10;
break;
case 'b':
note = 12;
break;
case 'p':
default:
note = 0;
}
p++;
// now, get optional '#' sharp
if(*p == '#')
{
note++;
p++;
}
// now, get optional '.' dotted note
if(*p == '.')
{
duration += duration/2;
p++;
}
// now, get scale
if(isdigit(*p))
{
scale = *p - '0';
p++;
}
else
{
scale = default_oct;
}
scale += OCTAVE_OFFSET;
if(*p == ',')
p++; // skip comma for next note (or we may be at the end)
// now play the note
if(note)
{
tone(_pin, notes[(scale - 4) * 12 + note], duration);
}
else
{
delay(duration/10);
}
}
}
#endif
+6 -7
View File
@@ -1,11 +1,10 @@
# ESPEasy
Easy MultiSensor device based on ESP8266
# ESPEasy legacy branch
This is where development takes place. Beware that latest versions may be unstable.
**MASTER**
:warning:This is the ESPEasy legacy branch which isn't developed anymore.:warning:
Stable versions including libraries are currently on SoureForge:
Current development branch: https://github.com/letscontrolit/ESPEasy/tree/mega (new features + fixes from v2.0 )
Next stable version: https://github.com/letscontrolit/ESPEasy/tree/v2.0 (fixes only)
http://sourceforge.net/projects/espeasy/
Wiki: http://www.esp8266.nu
Forum: http://www.esp8266.nu/forum
+2
View File
@@ -0,0 +1,2 @@
#define BUILD_GIT "v1.1.0-beta8-0-g7ade851"
+17 -3
View File
@@ -210,6 +210,21 @@ void handle_root() {
reply += F(" ");
reply += F(BUILD_NOTES);
reply += F("<TR><TD>GIT version:<TD>");
reply += F(BUILD_GIT);
reply += F("<TR><TD>Plugin sets:<TD>");
#ifdef PLUGIN_BUILD_DEV
reply += F("Normal, Testing, Development");
#elif PLUGIN_BUILD_TESTING
reply += F("Normal, Testing");
#elif PLUGIN_BUILD_NORMAL
reply += F("Normal");
#else
reply += F("Minimal");
#endif
reply += F("<TR><TD>Core Version:<TD>");
reply += ESP.getCoreVersion();
@@ -823,11 +838,11 @@ void handle_devices() {
taskdevicevaluename[varNr].toCharArray(tmpString, 41);
strcpy(ExtraTaskSettings.TaskDeviceValueNames[varNr], tmpString);
}
TempEvent.TaskIndex = index - 1;
if (ExtraTaskSettings.TaskDeviceValueNames[0][0] == 0) // if field set empty, reload defaults
PluginCall(PLUGIN_GET_DEVICEVALUENAMES, &TempEvent, dummyString);
PluginCall(PLUGIN_WEBFORM_SAVE, &TempEvent, dummyString);
}
SaveTaskSettings(index - 1);
@@ -2693,4 +2708,3 @@ String URLEncode(const char* msg)
}
return encodedMsg;
}
+36 -6
View File
@@ -27,7 +27,7 @@ boolean CPlugin_001(byte function, struct EventStruct *event, String& string)
string = F(CPLUGIN_NAME_001);
break;
}
case CPLUGIN_PROTOCOL_SEND:
{
String authHeader = "";
@@ -39,7 +39,7 @@ boolean CPlugin_001(byte function, struct EventStruct *event, String& string)
auth += SecuritySettings.ControllerPassword;
authHeader = "Authorization: Basic " + encoder.encode(auth) + " \r\n";
}
char log[80];
boolean success = false;
char host[20];
@@ -80,13 +80,14 @@ boolean CPlugin_001(byte function, struct EventStruct *event, String& string)
url += toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]);
url += ";";
url += toString(UserVar[event->BaseVarIndex + 1],ExtraTaskSettings.TaskDeviceValueDecimals[1]);
break;
break;
case SENSOR_TYPE_TEMP_HUM: // temp + hum + hum_stat, used for DHT11
url += F("&svalue=");
url += toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]);
url += ";";
url += toString(UserVar[event->BaseVarIndex + 1],ExtraTaskSettings.TaskDeviceValueDecimals[1]);
url += ";0";
url += ";";
url += humStat(UserVar[event->BaseVarIndex + 1]);
break;
case SENSOR_TYPE_TEMP_BARO: // temp + hum + hum_stat + bar + bar_fore, used for BMP085
url += F("&svalue=");
@@ -100,7 +101,9 @@ boolean CPlugin_001(byte function, struct EventStruct *event, String& string)
url += toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]);
url += ";";
url += toString(UserVar[event->BaseVarIndex + 1],ExtraTaskSettings.TaskDeviceValueDecimals[1]);
url += ";0;";
url += ";";
url += humStat(UserVar[event->BaseVarIndex + 1]);
url += ";";
url += toString(UserVar[event->BaseVarIndex + 2],ExtraTaskSettings.TaskDeviceValueDecimals[2]);
url += ";0";
break;
@@ -125,6 +128,19 @@ boolean CPlugin_001(byte function, struct EventStruct *event, String& string)
url += UserVar[event->BaseVarIndex];
}
break;
#ifdef PLUGIN_186
case (SENSOR_TYPE_WIND):
url += F("&svalue=");
url += toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]);
char* bearing[] = {";N;",";NNE;",";NE;",";ENE;",";E;",";ESE;",";SE;",";SSE;",";S;",";SSW;",";SW;",";WSW;",";W;",";WNW;",";NW;",";NNW;" };
url += bearing[int(UserVar[event->BaseVarIndex] / 22.5)];
url += toString(UserVar[event->BaseVarIndex + 1],ExtraTaskSettings.TaskDeviceValueDecimals[1]);
url += ";";
url += toString(UserVar[event->BaseVarIndex + 2],ExtraTaskSettings.TaskDeviceValueDecimals[2]);
url += ";0";
break;
#endif
}
url.toCharArray(log, 80);
@@ -132,7 +148,7 @@ boolean CPlugin_001(byte function, struct EventStruct *event, String& string)
// This will send the request to the server
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" + authHeader +
"Host: " + host + "\r\n" + authHeader +
"Connection: close\r\n\r\n");
unsigned long timer = millis() + 200;
@@ -165,3 +181,17 @@ boolean CPlugin_001(byte function, struct EventStruct *event, String& string)
return success;
}
int humStat(int hum){
int lHumStat;
if(hum<30){
lHumStat = 2;
}else if(hum<40){
lHumStat = 0;
}else if(hum<59){
lHumStat = 1;
}else{
lHumStat = 3;
}
return lHumStat;
}
+18 -4
View File
@@ -28,7 +28,7 @@ boolean CPlugin_002(byte function, struct EventStruct *event, String& string)
string = F(CPLUGIN_NAME_002);
break;
}
case CPLUGIN_PROTOCOL_TEMPLATE:
{
strcpy_P(Settings.MQTTsubscribe, PSTR("domoticz/out"));
@@ -60,7 +60,7 @@ boolean CPlugin_002(byte function, struct EventStruct *event, String& string)
nvalue = nvaluealt;
if ((int)switchtype == 0)
switchtype = "?";
for (byte x = 0; x < TASKS_MAX; x++)
{
if (Settings.TaskDeviceID[x] == idx)
@@ -150,7 +150,7 @@ boolean CPlugin_002(byte function, struct EventStruct *event, String& string)
values += toString(UserVar[event->BaseVarIndex + 1],ExtraTaskSettings.TaskDeviceValueDecimals[1]);
values.toCharArray(str, 80);
root["svalue"] = str;
break;
break;
case SENSOR_TYPE_TEMP_HUM: // temp + hum + hum_stat, used for DHT11
root["nvalue"] = 0;
values = toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]);
@@ -194,6 +194,21 @@ boolean CPlugin_002(byte function, struct EventStruct *event, String& string)
else
root["Set%20Level"] = UserVar[event->BaseVarIndex];
break;
#ifdef PLUGIN_186
case SENSOR_TYPE_WIND:
values = toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]);
char* bearing[] = {";N;",";NNE;",";NE;",";ENE;",";E;",";ESE;",";SE;",";SSE;",";S;",";SSW;",";SW;",";WSW;",";W;",";WNW;",";NW;",";NNW;" };
values += bearing[int(UserVar[event->BaseVarIndex] / 22.5)];
values += toString(UserVar[event->BaseVarIndex + 1],ExtraTaskSettings.TaskDeviceValueDecimals[1]);
values += ";";
values += toString(UserVar[event->BaseVarIndex + 2],ExtraTaskSettings.TaskDeviceValueDecimals[2]);
values += ";0;0";
values.toCharArray(str, 80);
root["svalue"] = str;
break;
#endif
}
char json[256];
@@ -222,4 +237,3 @@ boolean CPlugin_002(byte function, struct EventStruct *event, String& string)
}
return success;
}
+185
View File
@@ -0,0 +1,185 @@
#ifdef PLUGIN_BUILD_TESTING
//#######################################################################################################
//########################### Controller Plugin 011: Blynk #############################################
//#######################################################################################################
#define CPLUGIN_011
#define CPLUGIN_ID_011 11
#define CPLUGIN_NAME_011 "Blynk HTTP [TESTING]"
boolean CPlugin_011(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case CPLUGIN_PROTOCOL_ADD:
{
Protocol[++protocolCount].Number = CPLUGIN_ID_011;
Protocol[protocolCount].usesMQTT = false;
Protocol[protocolCount].usesAccount = false;
Protocol[protocolCount].usesPassword = true;
Protocol[protocolCount].defaultPort = 8443;
break;
}
case CPLUGIN_GET_DEVICENAME:
{
string = F(CPLUGIN_NAME_011);
break;
}
case CPLUGIN_PROTOCOL_SEND:
{
String postDataStr = "";
switch (event->sensorType)
{
case SENSOR_TYPE_SINGLE: // single value sensor, used for Dallas, BH1750, etc
postDataStr = F("update/V") ;
postDataStr += event->idx;
postDataStr += F("?value=");
postDataStr += toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]);
Blynk_get(postDataStr );
break;
case SENSOR_TYPE_TEMP_HUM: // dual value
case SENSOR_TYPE_TEMP_BARO:
case SENSOR_TYPE_DUAL:
postDataStr = F("update/V") ;
postDataStr += event->idx;
postDataStr += F("?value=");
postDataStr += toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]);
Blynk_get(postDataStr );
postDataStr = F("update/V") ;
postDataStr += event->idx + 1;
postDataStr += F("?value=");
postDataStr += toString(UserVar[event->BaseVarIndex + 1],ExtraTaskSettings.TaskDeviceValueDecimals[1]);
Blynk_get(postDataStr );
break;
case SENSOR_TYPE_TEMP_HUM_BARO:
case SENSOR_TYPE_TRIPLE:
postDataStr = F("update/V") ;
postDataStr += event->idx;
postDataStr += F("?value=");
postDataStr += toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]);
Blynk_get(postDataStr );
postDataStr = F("update/V") ;
postDataStr += event->idx + 1;
postDataStr += F("?value=");
postDataStr += toString(UserVar[event->BaseVarIndex + 1],ExtraTaskSettings.TaskDeviceValueDecimals[1]);
Blynk_get(postDataStr );
postDataStr = F("update/V") ;
postDataStr += event->idx + 2;
postDataStr += F("?value=");
postDataStr += toString(UserVar[event->BaseVarIndex + 2],ExtraTaskSettings.TaskDeviceValueDecimals[2]);
Blynk_get(postDataStr );
break;
case SENSOR_TYPE_SWITCH:
break;
}
break;
}
}
return success;
}
boolean Blynk_get(String command,float *data )
{
boolean success = false;
char host[20];
char log[80];
char command_char[50];
command.toCharArray(command_char, 50);
sprintf_P(host, PSTR("%u.%u.%u.%u"), Settings.Controller_IP[0], Settings.Controller_IP[1], Settings.Controller_IP[2], Settings.Controller_IP[3]);
// Use WiFiClient class to create TCP connections
WiFiClient client;
if (!client.connect(host, Settings.ControllerPort))
{
connectionFailures++;
return false;
}
if (connectionFailures)
connectionFailures--;
// We now create a URI for the request
char requete[300];
sprintf_P(requete, PSTR("GET /%s/%s HTTP/1.1\r\n Host: %s \r\n Connection: close\r\n\r\n"),SecuritySettings.ControllerPassword,command_char , host );
addLog(LOG_LEVEL_DEBUG, requete);
client.print(requete);
unsigned long timer = millis() + 200;
while (!client.available() && millis() < timer)
delay(1);
// Read all the lines of the reply from server and print them to Serial
while (client.available()) {
String line = client.readStringUntil('\n');
// success ?
if (line.substring(0, 15) == "HTTP/1.1 200 OK") {
strcpy_P(log, PSTR("HTTP : Success"));
success = true;
}
else if (line.substring(0, 24) == "HTTP/1.1 400 Bad Request") {
strcpy_P(log, PSTR("HTTP : Unauthorized"));
}
else if (line.substring(0, 25) == "HTTP/1.1 401 Unauthorized") {
strcpy_P(log, PSTR("HTTP : Unauthorized"));
}
addLog(LOG_LEVEL_DEBUG, log);
// data only
if (data && line.startsWith("["))
{
String strValue = line;
byte pos = strValue.indexOf('"',2);
strValue = strValue.substring(2, pos);
strValue.trim();
float value = strValue.toFloat();
*data = value;
success = true;
char value_char[5] ;
strValue.toCharArray(value_char, 5);
sprintf_P(log, PSTR("Blynk get - %s => %s"),command_char , value_char );
addLog(LOG_LEVEL_DEBUG, log);
}
}
strcpy_P(log, PSTR("HTTP : closing connection"));
addLog(LOG_LEVEL_DEBUG, log);
client.flush();
client.stop();
// important - backgroudtasks - free mem
timer = millis() + Settings.MessageDelay;
while (millis() < timer)
backgroundtasks();
return success;
}
#endif
+182
View File
@@ -0,0 +1,182 @@
#ifdef PLUGIN_BUILD_DEV
//#######################################################################################################
//########################### Controller Plugin 022: Pimatic RestApi ####################################
//#######################################################################################################
/*******************************************************************************
* Release notes:
* V 1.0
- First version by deejaybeam, 7 July 2016
* V 1.01
- Update and rename _C009.ino to _C022.ino by Wutu due to new standard protocols, 21 September 2016
* V1.02
- Remove ">210" core statement to comply (and function) with new standard 230 core as of R114, 24 September 2016
*
/******************************************************************************/
#define CPLUGIN_022
#define CPLUGIN_ID_022 22
#define CPLUGIN_NAME_022 "Pimatic RestApi [DEVELOPMENT]"
boolean CPlugin_022(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case CPLUGIN_PROTOCOL_ADD:
{
Protocol[++protocolCount].Number = CPLUGIN_ID_022;
Protocol[protocolCount].usesMQTT = false;
Protocol[protocolCount].usesAccount = true;
Protocol[protocolCount].usesPassword = true;
Protocol[protocolCount].defaultPort = 80;
break;
}
case CPLUGIN_GET_DEVICENAME:
{
string = F(CPLUGIN_NAME_022);
break;
}
case CPLUGIN_PROTOCOL_SEND:
{
switch (event->sensorType)
{
case SENSOR_TYPE_SINGLE: // single value sensor, used for Dallas, BH1750, etc
case SENSOR_TYPE_SWITCH:
case SENSOR_TYPE_DIMMER:
pimaticUpdateVariable(event, 0, UserVar[event->BaseVarIndex], 0);
break;
case SENSOR_TYPE_LONG: // single LONG value, stored in two floats (rfid tags)
pimaticUpdateVariable(event, 0, 0, (unsigned long)UserVar[event->BaseVarIndex] + ((unsigned long)UserVar[event->BaseVarIndex + 1] << 16));
break;
case SENSOR_TYPE_TEMP_HUM:
case SENSOR_TYPE_TEMP_BARO:
{
pimaticUpdateVariable(event, 0, UserVar[event->BaseVarIndex], 0);
unsigned long timer = millis() + Settings.MessageDelay;
while (millis() < timer)
backgroundtasks();
pimaticUpdateVariable(event, 1, UserVar[event->BaseVarIndex + 1], 0);
break;
}
case SENSOR_TYPE_TEMP_HUM_BARO:
{
pimaticUpdateVariable(event, 0, UserVar[event->BaseVarIndex], 0);
unsigned long timer = millis() + Settings.MessageDelay;
while (millis() < timer)
backgroundtasks();
pimaticUpdateVariable(event, 1, UserVar[event->BaseVarIndex + 1], 0);
timer = millis() + Settings.MessageDelay;
while (millis() < timer)
backgroundtasks();
pimaticUpdateVariable(event, 2, UserVar[event->BaseVarIndex + 2], 0);
break;
}
}
break;
}
}
return success;
}
//********************************************************************************
// Pimatic updateVariable
//********************************************************************************
boolean pimaticUpdateVariable(struct EventStruct *event, byte varIndex, float value, unsigned long longValue)
{
String authHeader = "";
if ((SecuritySettings.ControllerUser[0] != 0) && (SecuritySettings.ControllerPassword[0] != 0))
{
base64 encoder;
String auth = SecuritySettings.ControllerUser;
auth += ":";
auth += SecuritySettings.ControllerPassword;
authHeader = "Authorization: Basic " + encoder.encode(auth) + " \r\n";
}
char log[80];
boolean success = false;
char host[20];
sprintf_P(host, PSTR("%u.%u.%u.%u"), Settings.Controller_IP[0], Settings.Controller_IP[1], Settings.Controller_IP[2], Settings.Controller_IP[3]);
sprintf_P(log, PSTR("%s%s using port %u"), "HTTP : connecting to ", host,Settings.ControllerPort);
addLog(LOG_LEVEL_DEBUG, log);
// Use WiFiClient class to create TCP connections
WiFiClient client;
if (!client.connect(host, Settings.ControllerPort))
{
connectionFailures++;
strcpy_P(log, PSTR("HTTP : connection failed"));
addLog(LOG_LEVEL_ERROR, log);
return false;
}
statusLED(true);
if (connectionFailures)
connectionFailures--;
if (ExtraTaskSettings.TaskDeviceValueNames[0][0] == 0)
PluginCall(PLUGIN_GET_DEVICEVALUENAMES, event, dummyString);
String url = "/api/variables/";
url += URLEncode(ExtraTaskSettings.TaskDeviceValueNames[varIndex]);
url.toCharArray(log, 80);
addLog(LOG_LEVEL_DEBUG_MORE, log);
String data;
if (longValue)
data = String(longValue);
else
data = toString(value, ExtraTaskSettings.TaskDeviceValueDecimals[varIndex]);
String yourdata = "{\"type\": \"value\", \"valueOrExpression\": \"" + data + "\"}";
String hostName = host;
if (Settings.UseDNS)
hostName = Settings.ControllerHostName;
// This will send the request to the server
client.print(String("PATCH ") + url + " HTTP/1.1\r\n" +
authHeader +
"Host: " + hostName + "\r\n" +
"Content-Type:application/json\r\n" +
"Content-Length: " + yourdata.length() + "\r\n\r\n" +
yourdata);
unsigned long timer = millis() + 200;
while (!client.available() && millis() < timer)
delay(1);
// Read all the lines of the reply from server and print them to Serial
while (client.available()) {
String line = client.readStringUntil('\n');
line.toCharArray(log, 80);
addLog(LOG_LEVEL_DEBUG_MORE, log);
if (line.substring(0, 15) == "HTTP/1.1 200 OK")
{
strcpy_P(log, PSTR("HTTP : Succes!"));
addLog(LOG_LEVEL_DEBUG, log);
success = true;
}
delay(1);
}
strcpy_P(log, PSTR("HTTP : closing connection"));
addLog(LOG_LEVEL_DEBUG, log);
client.flush();
client.stop();
}
#endif
+239
View File
@@ -0,0 +1,239 @@
#ifdef PLUGIN_BUILD_DEV
//#######################################################################################################
//########################### Controller Plugin 025: Generic HTTP #######################################
//#######################################################################################################
#define CPLUGIN_025
#define CPLUGIN_ID_025 25
#define CPLUGIN_NAME_025 "Generic HTTP TEST [DEVELOPMENT]"
#define P025_HTTP_METHOD_MAX_LEN 16
#define P025_HTTP_URI_MAX_LEN 240
#define P025_HTTP_HEADER_MAX_LEN 256
#define P025_HTTP_BODY_MAX_LEN 512
struct P025_ConfigStruct
{
char HttpMethod[P025_HTTP_METHOD_MAX_LEN];
char HttpUri[P025_HTTP_URI_MAX_LEN];
char HttpHeader[P025_HTTP_HEADER_MAX_LEN];
char HttpBody[P025_HTTP_BODY_MAX_LEN];
};
boolean CPlugin_025(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case CPLUGIN_PROTOCOL_ADD:
{
Protocol[++protocolCount].Number = CPLUGIN_ID_025;
Protocol[protocolCount].usesMQTT = false;
Protocol[protocolCount].usesAccount = false;
Protocol[protocolCount].usesPassword = false;
Protocol[protocolCount].defaultPort = 80;
break;
}
case CPLUGIN_GET_DEVICENAME:
{
string = F(CPLUGIN_NAME_025);
break;
}
case CPLUGIN_WEBFORM_LOAD:
{
P025_ConfigStruct customConfig;
LoadCustomControllerSettings((byte*)&customConfig, sizeof(customConfig));
String methods[] = { F("GET"), F("POST"), F("PUT") };
string += F("<TR><TD>HTTP Method :<TD><select name='P025httpmethod'>");
for (int i = 0; i < 3; i++)
{
string += F("<option value='");
string += methods[i] + "'";
string += methods[i].equals(customConfig.HttpMethod) ? F(" selected='selected'") : F("");
string += F(">");
string += methods[i];
string += F("</option>");
}
string += F("</select>");
string += F("<TR><TD>HTTP URI:<TD><input type='text' name='P025httpuri' size=80 maxlength='");
string += P025_HTTP_URI_MAX_LEN-1;
string += F("' value='");
string += customConfig.HttpUri;
string += F("'>");
string += F("<TR><TD>HTTP Header:<TD><textarea name='P025httpheader' rows='4' cols='50' maxlength='");
string += P025_HTTP_HEADER_MAX_LEN-1;
string += F("'>");
string += customConfig.HttpHeader;
string += F("</textarea>");
string += F("<TR><TD>HTTP Body:<TD><textarea name='P025httpbody' rows='8' cols='50' maxlength='");
string += P025_HTTP_BODY_MAX_LEN-1;
string += F("'>");
string += customConfig.HttpBody;
string += F("</textarea>");
break;
}
case CPLUGIN_WEBFORM_SAVE:
{
P025_ConfigStruct customConfig;
String httpmethod = WebServer.arg("P025httpmethod");
String httpuri = WebServer.arg("P025httpuri");
String httpheader = WebServer.arg("P025httpheader");
String httpbody = WebServer.arg("P025httpbody");
strncpy(customConfig.HttpMethod, httpmethod.c_str(), sizeof(customConfig.HttpMethod));
strncpy(customConfig.HttpUri, httpuri.c_str(), sizeof(customConfig.HttpUri));
strncpy(customConfig.HttpHeader, httpheader.c_str(), sizeof(customConfig.HttpHeader));
strncpy(customConfig.HttpBody, httpbody.c_str(), sizeof(customConfig.HttpBody));
SaveCustomControllerSettings((byte*)&customConfig, sizeof(customConfig));
break;
}
case CPLUGIN_PROTOCOL_SEND:
{
switch (event->sensorType)
{
case SENSOR_TYPE_SINGLE: // single value sensor, used for Dallas, BH1750, etc
case SENSOR_TYPE_SWITCH:
case SENSOR_TYPE_DIMMER:
HTTPSend025(event, 0, UserVar[event->BaseVarIndex], 0);
break;
case SENSOR_TYPE_LONG: // single LONG value, stored in two floats (rfid tags)
HTTPSend025(event, 0, 0, (unsigned long)UserVar[event->BaseVarIndex] + ((unsigned long)UserVar[event->BaseVarIndex + 1] << 16));
break;
case SENSOR_TYPE_TEMP_HUM:
case SENSOR_TYPE_TEMP_BARO:
{
HTTPSend025(event, 0, UserVar[event->BaseVarIndex], 0);
unsigned long timer = millis() + Settings.MessageDelay;
while (millis() < timer)
backgroundtasks();
HTTPSend025(event, 1, UserVar[event->BaseVarIndex + 1], 0);
break;
}
case SENSOR_TYPE_TEMP_HUM_BARO:
{
HTTPSend025(event, 0, UserVar[event->BaseVarIndex], 0);
unsigned long timer = millis() + Settings.MessageDelay;
while (millis() < timer)
backgroundtasks();
HTTPSend025(event, 1, UserVar[event->BaseVarIndex + 1], 0);
timer = millis() + Settings.MessageDelay;
while (millis() < timer)
backgroundtasks();
HTTPSend025(event, 2, UserVar[event->BaseVarIndex + 2], 0);
break;
}
}
break;
}
}
return success;
}
//********************************************************************************
// Generic HTTP get request
//********************************************************************************
boolean HTTPSend025(struct EventStruct *event, byte varIndex, float value, unsigned long longValue)
{
P025_ConfigStruct customConfig;
LoadCustomControllerSettings((byte*)&customConfig, sizeof(customConfig));
char log[80];
boolean success = false;
char host[20];
sprintf_P(host, PSTR("%u.%u.%u.%u"), Settings.Controller_IP[0], Settings.Controller_IP[1], Settings.Controller_IP[2], Settings.Controller_IP[3]);
sprintf_P(log, PSTR("%s%s using port %u"), "HTTP : connecting to ", host, Settings.ControllerPort);
addLog(LOG_LEVEL_DEBUG, log);
// Use WiFiClient class to create TCP connections
WiFiClient client;
if (!client.connect(host, Settings.ControllerPort))
{
connectionFailures++;
strcpy_P(log, PSTR("HTTP : connection failed"));
addLog(LOG_LEVEL_ERROR, log);
return false;
}
statusLED(true);
if (connectionFailures)
connectionFailures--;
if (ExtraTaskSettings.TaskDeviceValueNames[0][0] == 0)
PluginCall(PLUGIN_GET_DEVICEVALUENAMES, event, dummyString);
String hostName = host;
if (Settings.UseDNS)
hostName = Settings.ControllerHostName;
String payload = String(customConfig.HttpMethod) + " /";
payload += customConfig.HttpUri;
payload += String(" HTTP/1.1\r\n") +
"Host: " + hostName + ":" + Settings.ControllerPort + "\r\n" +
"Connection: close\r\n";
if (strlen(customConfig.HttpHeader) > 0)
payload += customConfig.HttpHeader;
ReplaceTokenByValue(payload, event, varIndex, value, longValue);
if (strlen(customConfig.HttpBody) > 0)
{
String body = String(customConfig.HttpBody);
ReplaceTokenByValue(body, event, varIndex, value, longValue);
payload += "\r\nContent-Length: " + String(body.length());
payload += "\r\n\r\n" + body;
}
// This will send the request to the server
client.print(payload);
unsigned long timer = millis() + 200;
while (!client.available() && millis() < timer)
delay(1);
// Read all the lines of the reply from server and print them to Serial
while (client.available()) {
String line = client.readStringUntil('\n');
line.toCharArray(log, 80);
addLog(LOG_LEVEL_DEBUG_MORE, log);
if (line.substring(0, 10) == "HTTP/1.1 2")
{
strcpy_P(log, PSTR("HTTP : Succes!"));
addLog(LOG_LEVEL_DEBUG, log);
success = true;
}
delay(1);
}
strcpy_P(log, PSTR("HTTP : closing connection"));
addLog(LOG_LEVEL_DEBUG, log);
client.flush();
client.stop();
}
//********************************************************************************
// Replace the token in a string by real value.
//********************************************************************************
void ReplaceTokenByValue(String& s, struct EventStruct *event, byte varIndex, float value, unsigned long longValue)
{
s.replace("%sysname%", URLEncode(Settings.Name));
s.replace("%tskname%", URLEncode(ExtraTaskSettings.TaskDeviceName));
s.replace("%id%", String(event->idx));
s.replace("%valname%", URLEncode(ExtraTaskSettings.TaskDeviceValueNames[varIndex]));
if (longValue)
s.replace("%value%", String(longValue));
else
s.replace("%value%", toString(value, ExtraTaskSettings.TaskDeviceValueDecimals[varIndex]));
}
#endif
+39 -6
View File
@@ -6,7 +6,6 @@
#define PLUGIN_ID_001 1
#define PLUGIN_NAME_001 "Switch input"
#define PLUGIN_VALUENAME1_001 "Switch"
boolean Plugin_001(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
@@ -137,7 +136,7 @@ boolean Plugin_001(byte function, struct EventStruct *event, String& string)
switchstate[event->TaskIndex] = digitalRead(Settings.TaskDevicePin1[event->TaskIndex]);
outputstate[event->TaskIndex] = switchstate[event->TaskIndex];
// if boot state must be send, inverse default state
if (Settings.TaskDevicePluginConfig[event->TaskIndex][3])
{
@@ -225,17 +224,51 @@ boolean Plugin_001(byte function, struct EventStruct *event, String& string)
}
}
#ifdef PLUGIN_BUILD_TESTING
if (command == F("rtttl"))
{
success = true;
if (event->Par1 >= 0 && event->Par1 <= 16)
{
pinMode(event->Par1, OUTPUT);
char sng[1024] ="";
string.replace("-","#");
string.toCharArray(sng, 1024);
play_rtttl(event->Par1, sng);
setPinState(PLUGIN_ID_001, event->Par1, PIN_MODE_OUTPUT, event->Par2);
log = String(F("SW : ")) + string;
addLog(LOG_LEVEL_INFO, log);
SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_001, event->Par1, log, 0));
}
}
if (command == F("tone"))
{
success = true;
if (event->Par1 >= 0 && event->Par1 <= 16)
{
pinMode(event->Par1, OUTPUT);
tone(event->Par1, event->Par2, event->Par3);
setPinState(PLUGIN_ID_001, event->Par1, PIN_MODE_OUTPUT, event->Par2);
log = String(F("SW : ")) + string;
addLog(LOG_LEVEL_INFO, log);
SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_001, event->Par1, log, 0));
}
}
#endif
if (command == F("pwm"))
{
success = true;
if (event->Par1 >= 0 && event->Par1 <= 16)
{
pinMode(event->Par1, OUTPUT);
if(event->Par3 != 0)
{
byte prev_mode;
uint16_t prev_value;
uint16_t prev_value;
getPinState(PLUGIN_ID_001, event->Par1, &prev_mode, &prev_value);
if(prev_mode != PIN_MODE_PWM)
prev_value = 0;
@@ -251,7 +284,7 @@ boolean Plugin_001(byte function, struct EventStruct *event, String& string)
delay(1);
}
}
analogWrite(event->Par1, event->Par2);
setPinState(PLUGIN_ID_001, event->Par1, PIN_MODE_PWM, event->Par2);
log = String(F("SW : GPIO ")) + String(event->Par1) + String(F(" Set PWM to ")) + String(event->Par2);
@@ -339,4 +372,4 @@ boolean Plugin_001(byte function, struct EventStruct *event, String& string)
}
}
return success;
}
}
+222
View File
@@ -0,0 +1,222 @@
#ifdef PLUGIN_BUILD_DEV
//#######################################################################################################
//#################################### Plugin 036: MQ2 sensors ###############################################
//#######################################################################################################
#define PLUGIN_036
#define PLUGIN_ID_036 36
#define PLUGIN_NAME_036 "MQ2 smoke sensor [DEVELOPMENT]"
#define PLUGIN_VALUENAME1_036 "LPG"
#define PLUGIN_VALUENAME2_036 "CO"
#define PLUGIN_VALUENAME3_036 "SMOKE"
/************************Hardware Related Macros************************************/
#define RL_VALUE (5) //define the load resistance on the board, in kilo ohms
#define RO_CLEAN_AIR_FACTOR (9.83) //RO_CLEAR_AIR_FACTOR=(Sensor resistance in clean air)/RO,
//which is derived from the chart in datasheet
/***********************Software Related Macros************************************/
#define CALIBARAION_SAMPLE_TIMES (50) //define how many samples you are going to take in the calibration phase
#define CALIBRATION_SAMPLE_INTERVAL (500) //define the time interal(in milisecond) between each samples in the
//cablibration phase
#define READ_SAMPLE_INTERVAL (20) //define how many samples you are going to take in normal operation
#define READ_SAMPLE_TIMES (5) //define the time interal(in milisecond) between each samples in
//normal operation
/**********************Application Related Macros**********************************/
#define GAS_LPG (0)
#define GAS_CO (1)
#define GAS_SMOKE (2)
/*****************************Globals***********************************************/
float LPGCurve[3] = {2.3,0.21,-0.47}; //two points are taken from the curve.
//with these two points, a line is formed which is "approximately equivalent"
//to the original curve.
//data format:{ x, y, slope}; point1: (lg200, 0.21), point2: (lg10000, -0.59)
float COCurve[3] = {2.3,0.72,-0.34}; //two points are taken from the curve.
//with these two points, a line is formed which is "approximately equivalent"
//to the original curve.
//data format:{ x, y, slope}; point1: (lg200, 0.72), point2: (lg10000, 0.15)
float SmokeCurve[3] ={2.3,0.53,-0.44}; //two points are taken from the curve.
//with these two points, a line is formed which is "approximately equivalent"
//to the original curve.
//data format:{ x, y, slope}; point1: (lg200, 0.53), point2: (lg10000, -0.22)
float Ro = 10; //Ro is initialized to 10 kilo ohms
boolean Plugin_036(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_036;
Device[deviceCount].Type = DEVICE_TYPE_ANALOG;
Device[deviceCount].VType = SENSOR_TYPE_TRIPLE;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = true;
Device[deviceCount].ValueCount = 3;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].TimerOption = true;
Device[deviceCount].GlobalSyncOption = true;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_036);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_036));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_036));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_036));
break;
}
case PLUGIN_INIT:
{
String log = F("MQ2 Calibrating...put it in fresh air");
addLog(LOG_LEVEL_INFO,log);
Ro = MQCalibration(); //Calibrating the sensor. Please make sure the sensor is in clean air
//when you perform the calibration
log = F("MQ2 Calibration is done...");
addLog(LOG_LEVEL_INFO,log);
log = F("Ro=");
log += Ro;
log += F("kohm");
addLog(LOG_LEVEL_INFO,log);
}
case PLUGIN_READ:
{
//Read LPG value
int value = MQGetGasPercentage(MQRead()/Ro,GAS_LPG);
UserVar[event->BaseVarIndex] = (float)value;
String log = F("MQ2 : LPG value: ");
log += value;
addLog(LOG_LEVEL_INFO,log);
//read CO value
value = MQGetGasPercentage(MQRead()/Ro,GAS_CO);
UserVar[event->BaseVarIndex +1] = (float)value;
log = F("MQ2 : CO value: ");
log += value;
addLog(LOG_LEVEL_INFO,log);
//Read SMOKE
value = MQGetGasPercentage(MQRead()/Ro,GAS_SMOKE);
UserVar[event->BaseVarIndex +2] = (float)value;
log = F("MQ2 : SMOKE value: ");
log += value;
addLog(LOG_LEVEL_INFO,log);
//affect result
success = true;
break;
}
}
return success;
}
/****************** MQResistanceCalculation ****************************************
Input: raw_adc - raw value read from adc, which represents the voltage
Output: the calculated sensor resistance
Remarks: The sensor and the load resistor forms a voltage divider. Given the voltage
across the load resistor and its resistance, the resistance of the sensor
could be derived.
************************************************************************************/
float MQResistanceCalculation(int raw_adc)
{
return ( ((float)RL_VALUE*(1023-raw_adc)/raw_adc));
}
/***************************** MQCalibration ****************************************
Input: mq_pin - analog channel
Output: Ro of the sensor
Remarks: This function assumes that the sensor is in clean air. It use
MQResistanceCalculation to calculates the sensor resistance in clean air
and then divides it with RO_CLEAN_AIR_FACTOR. RO_CLEAN_AIR_FACTOR is about
10, which differs slightly between different sensors.
************************************************************************************/
float MQCalibration()
{
int i;
float val=0;
for (i=0;i<CALIBARAION_SAMPLE_TIMES;i++) { //take multiple samples
val += MQResistanceCalculation(analogRead(A0));
delay(CALIBRATION_SAMPLE_INTERVAL);
}
val = val/CALIBARAION_SAMPLE_TIMES; //calculate the average value
val = val/RO_CLEAN_AIR_FACTOR; //divided by RO_CLEAN_AIR_FACTOR yields the Ro
//according to the chart in the datasheet
return val;
}
/***************************** MQRead *********************************************
Input: mq_pin - analog channel
Output: Rs of the sensor
Remarks: This function use MQResistanceCalculation to caculate the sensor resistenc (Rs).
The Rs changes as the sensor is in the different consentration of the target
gas. The sample times and the time interval between samples could be configured
by changing the definition of the macros.
************************************************************************************/
float MQRead()
{
int i;
float rs=0;
for (i=0;i<READ_SAMPLE_TIMES;i++) {
rs += MQResistanceCalculation(analogRead(A0));
delay(READ_SAMPLE_INTERVAL);
}
rs = rs/READ_SAMPLE_TIMES;
return rs;
}
/***************************** MQGetGasPercentage **********************************
Input: rs_ro_ratio - Rs divided by Ro
gas_id - target gas type
Output: ppm of the target gas
Remarks: This function passes different curves to the MQGetPercentage function which
calculates the ppm (parts per million) of the target gas.
************************************************************************************/
int MQGetGasPercentage(float rs_ro_ratio, int gas_id)
{
if ( gas_id == GAS_LPG ) {
return MQGetPercentage(rs_ro_ratio,LPGCurve);
} else if ( gas_id == GAS_CO ) {
return MQGetPercentage(rs_ro_ratio,COCurve);
} else if ( gas_id == GAS_SMOKE ) {
return MQGetPercentage(rs_ro_ratio,SmokeCurve);
}
return 0;
}
/***************************** MQGetPercentage **********************************
Input: rs_ro_ratio - Rs divided by Ro
pcurve - pointer to the curve of the target gas
Output: ppm of the target gas
Remarks: By using the slope and a point of the line. The x(logarithmic value of ppm)
of the line could be derived if y(rs_ro_ratio) is provided. As it is a
logarithmic coordinate, power of 10 is used to convert the result to non-logarithmic
value.
************************************************************************************/
int MQGetPercentage(float rs_ro_ratio, float *pcurve)
{
return (pow(10,( ((log(rs_ro_ratio)-pcurve[1])/pcurve[2]) + pcurve[0])));
}
#endif
+124
View File
@@ -0,0 +1,124 @@
#ifdef PLUGIN_BUILD_DEV
//#######################################################################################################
//#################### Plugin 100 SRF01 Ultrasonic Distance Sensor ######################################
//#######################################################################################################
#include <SoftwareSerial.h>
#define PLUGIN_100
#define PLUGIN_ID_100 100
#define PLUGIN_NAME_100 "Ultrasonic Sensor - SRF01 [DEVELOPMENT]"
#define PLUGIN_VALUENAME1_100 "Distance"
#define PLUGIN_100_srfAddress 0x01 // Address of the SFR01, default ist 0x01
#define PLUGIN_100_getSoft 0x5D // Byte to tell SRF01 we wish to read software version
#define PLUGIN_100_getRange 0x54 // Byte used to get range from SRF01 in cm
#define PLUGIN_100_getStatus 0x5F // Byte used to get the status of the transducer
SoftwareSerial *Plugin_100_SRF;
uint8_t Plugin_100_SRF_Pin;
boolean Plugin_100(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
byte timeout;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_100;
Device[deviceCount].Type = DEVICE_TYPE_SINGLE;
Device[deviceCount].VType = SENSOR_TYPE_SINGLE;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = true;
Device[deviceCount].ValueCount = 1;
Device[deviceCount].SendDataOption = true;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_100);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_100));
break;
}
case PLUGIN_INIT:
{
addLog(LOG_LEVEL_INFO, (char*)"INIT : SRF01");
Plugin_100_SRF_Pin = Settings.TaskDevicePin1[event->TaskIndex];
Plugin_100_SRF = new SoftwareSerial(Plugin_100_SRF_Pin, Plugin_100_SRF_Pin);
Plugin_100_SRF01_Cmd(PLUGIN_100_srfAddress, PLUGIN_100_getSoft);
for (timeout = 0; timeout < 10; timeout++) {
if (Plugin_100_SRF->available() < 1 ) {
delay(10);
} else {
int softVer = Plugin_100_SRF->read();
String log = F("SRF01 : Firmware-Version: ");
log += softVer;
addLog(LOG_LEVEL_INFO, log);
success = true;
return success;
};
};
addLog(LOG_LEVEL_ERROR, (char*)"SRF01-Init: protocol timeout!");
success = false;
break;
}
case PLUGIN_READ:
{
Plugin_100_SRF01_Cmd(PLUGIN_100_srfAddress, PLUGIN_100_getRange);
for (timeout = 0; timeout < 10; timeout++) {
if (Plugin_100_SRF->available() < 2 ) {
delay(10);
} else {
byte highByte = Plugin_100_SRF->read();
byte lowByte = Plugin_100_SRF->read();
int dist = ((highByte << 8) + lowByte);
UserVar[event->BaseVarIndex] = dist;
String log = F("SRF1 : Distance: ");
log += dist;
addLog(LOG_LEVEL_INFO, log);
success = true;
return success;
};
};
addLog(LOG_LEVEL_ERROR, (char*)"SRF01 : protocol timeout!");
UserVar[event->BaseVarIndex] = NAN;
success = false;
break;
}
}
return success;
}
//**************************************************************************/
// Send SRF01 Command
//**************************************************************************/
void Plugin_100_SRF01_Cmd(byte Address, byte cmd) {
Plugin_100_SRF->flush();
pinMode(Plugin_100_SRF_Pin, OUTPUT);
digitalWrite(Plugin_100_SRF_Pin, LOW);
delay(2);
digitalWrite(Plugin_100_SRF_Pin, HIGH);
delay(1);
Plugin_100_SRF->write(Address);
Plugin_100_SRF->write(cmd);
pinMode(Plugin_100_SRF_Pin, INPUT);
Plugin_100_SRF->flush();
}
#endif
+95
View File
@@ -0,0 +1,95 @@
#ifdef PLUGIN_BUILD_DEV
//#######################################################################################################
//#################################### Plugin 102: Nodo Event Bridge V2 #################################
//#######################################################################################################
#define PLUGIN_102
#define PLUGIN_ID_102 102
#define PLUGIN_NAME_102 "Nodo Event Bridge V2 [DEVELOPMENT]"
#define PLUGIN_VALUENAME1_102 ""
boolean Plugin_102(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
static byte unicastTargetUnit = 0;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_102;
Device[deviceCount].Custom = true;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_102);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_102));
break;
}
case PLUGIN_UDP_IN:
{
// event->Data is pointer to UDP buffer
if (event->Data[0] == 255 && event->Data[1] == 254)
{
if (event->Data[11] == 0) // normal traffic, Nodo flags are 0
{
unicastTargetUnit = 0;
}
else
{
unicastTargetUnit = event->Data[9]; // remote source Nodo unit will be unicast target
}
for (byte x = 0; x < 16; x++)
Serial.write(event->Data[x]);
}
success = true;
break;
}
case PLUGIN_SERIAL_IN:
{
if (Serial.peek() == 255)
{
delay(20); // wait for message to complete
if (Serial.read() == 255 && Serial.read() == 254)
{
byte data[16];
data[0] = 255;
data[1] = 254;
byte count = 2;
while (Serial.available() && count < 16)
data[count++] = Serial.read();
IPAddress sendIP(255, 255, 255, 255);
if (data[11] != 0) // flags set, set to unicast mode
{
if ((unicastTargetUnit != 0) && (Nodes[unicastTargetUnit].ip[0] != 0))
for(byte x=0; x <4; x++)
sendIP[x] = Nodes[unicastTargetUnit].ip[x];
}
portUDP.beginPacket(sendIP, Settings.UDPPort);
portUDP.write(data, 16);
portUDP.endPacket();
}
success = true;
}
break;
}
}
return success;
}
#endif
+175
View File
@@ -0,0 +1,175 @@
#ifdef PLUGIN_BUILD_DEV
//#######################################################################################################
//######################### Plugin 104: SRF02 Ultrasonic range finder sensor ############################
//#######################################################################################################
#define PLUGIN_104
#define PLUGIN_ID_104 104
#define PLUGIN_NAME_104 "Ultrasonic range finder - SRF02 [DEVELOPMENT]"
#define PLUGIN_VALUENAME1_104 "Distance"
#define SRF02_ADDRESS (0x70) // default address (0x70 = datasheet address 0xE0)
#define SRF02_REG_COMMAND (0x00) // a read on this register returns the software revision
#define SRF02_REG_UNUSED (0x01)
#define SRF02_REG_RANGE_HIGH_BYTE (0x02)
#define SRF02_REG_RANGE_LOW_BYTE (0x03)
#define SRF02_REG_AUTOTUNE_MINIMUM_HIGH_BYTE (0x04)
#define SRF02_REG_AUTOTUNE_MINIMUM_LOW_BYTE (0x05)
#define SRF02_CMD_REAL_RANGING_MODE_INCH (0x50)
#define SRF02_CMD_REAL_RANGING_MODE_CM (0x51)
#define SRF02_CMD_REAL_RANGING_MODE_US (0x52)
#define SRF02_CMD_FAKE_RANGING_MODE_INCH (0x56)
#define SRF02_CMD_FAKE_RANGING_MODE_CM (0x57)
#define SRF02_CMD_FAKE_RANGING_MODE_US (0x58)
#define SRF02_CMD_FORCE_AUTOTUNE_RESTART (0x5C)
#define SRF02_CMD_I2C_CHANGE_SEQ_1 (0xA0)
#define SRF02_CMD_I2C_CHANGE_SEQ_2 (0xA5)
#define SRF02_CMD_I2C_CHANGE_SEQ_3 (0xAA)
uint8_t SRF02_i2caddr;
boolean Plugin_104(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_104;
Device[deviceCount].Type = DEVICE_TYPE_I2C;
Device[deviceCount].VType = SENSOR_TYPE_SINGLE;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = true;
Device[deviceCount].ValueCount = 1;
Device[deviceCount].SendDataOption = true;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_104);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_104));
break;
}
case PLUGIN_WEBFORM_LOAD:
{
byte choice = Settings.TaskDevicePluginConfig[event->TaskIndex][0];
String options[1];
options[0] = F("Distance");
int optionValues[1];
optionValues[0] = 0;
string += F("<TR><TD>Report:<TD><select name='plugin_104_value'>");
string += F("<option value='");
string += optionValues[0];
string += "'";
if (choice == optionValues[0])
string += F(" selected");
string += ">";
string += options[0];
string += F("</option>");
string += F("</select>");
success = true;
break;
}
case PLUGIN_WEBFORM_SAVE:
{
String plugin1 = WebServer.arg("plugin_104_value");
Settings.TaskDevicePluginConfig[event->TaskIndex][0] = plugin1.toInt();
success = true;
break;
}
case PLUGIN_INIT:
{
Plugin_104_begin();
success = true;
break;
}
case PLUGIN_READ:
{
float value;
value = Plugin_104_getDistance();
UserVar[event->BaseVarIndex] = value;
String log = F("SRF02 : value : ");
log += value;
log += F("mm");
addLog(LOG_LEVEL_INFO,log);
success = true;
break;
}
}
return success;
}
//**************************************************************************/
// I2C single byte write
//**************************************************************************/
void Plugin_104_wireWriteByte(uint8_t reg, uint8_t value)
{
Wire.beginTransmission(SRF02_i2caddr);
Wire.write(reg);
Wire.write(value);
Wire.endTransmission();
}
//**************************************************************************/
// I2C two byte read
//**************************************************************************/
void Plugin_104_wireReadTwoBytes(uint8_t reg, uint16_t *value)
{
Wire.beginTransmission(SRF02_i2caddr);
Wire.write(reg);
Wire.endTransmission();
delayMicroseconds(10);
Wire.requestFrom(SRF02_i2caddr, (uint8_t)2);
*value = ((Wire.read() << 8) | Wire.read());
}
//**************************************************************************/
// Sensor setup
//**************************************************************************/
void Plugin_104_begin(void)
{
SRF02_i2caddr = SRF02_ADDRESS;
}
//**************************************************************************/
// Report distance
//**************************************************************************/
float Plugin_104_getDistance()
{
uint16_t value;
Plugin_104_wireWriteByte(SRF02_REG_COMMAND, SRF02_CMD_REAL_RANGING_MODE_US);
delay(70); // transmit -> receive turnaround time (up to 65ms)
Plugin_104_wireReadTwoBytes(SRF02_REG_RANGE_HIGH_BYTE, &value);
return (float)value/(float)5.82750583; // distance in [mm] (2*1000/343.2)
}
#endif
+136
View File
@@ -0,0 +1,136 @@
#ifdef PLUGIN_BUILD_DEV
//#######################################################################################################
//#################################### Plugin 108: WOL receiver #########################################
//#######################################################################################################
// This plugin receives Wake On Lan (WOL) messages that can be used to power on a connected device.
// Note that the ESP itself has no WOL feature. It must be active to receive this message.
// So you can't wake an ESP from deepsleep using WOL because Wifi is down during deepsleep.
#define PLUGIN_108
#define PLUGIN_ID_108 108
#define PLUGIN_NAME_108 "WOL Receiver [DEVELOPMENT]"
#define PLUGIN_VALUENAME1_108 "WOL"
WiFiUDP *WOL;
boolean Plugin_108(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_108;
Device[deviceCount].Type = DEVICE_TYPE_SINGLE;
Device[deviceCount].Custom = true;
Device[deviceCount].TimerOption = false;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_108);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_108));
break;
}
case PLUGIN_WEBFORM_LOAD:
{
string += F("<TR><TD>Power control pin:<TD>");
addPinSelect(false, string, "taskdevicepin1", Settings.TaskDevicePin1[event->TaskIndex]);
byte choice = Settings.TaskDevicePluginConfig[event->TaskIndex][0];
String options[2];
options[0] = F("Active Low");
options[1] = F("Active High");
int optionValues[2];
optionValues[0] = 0;
optionValues[1] = 1;
string += F("<TR><TD>Output state:<TD><select name='plugin_108_state'>");
for (byte x = 0; x < 2; x++)
{
string += F("<option value='");
string += optionValues[x];
string += "'";
if (choice == optionValues[x])
string += F(" selected");
string += ">";
string += options[x];
string += F("</option>");
}
string += F("</select>");
success = true;
break;
}
case PLUGIN_WEBFORM_SAVE:
{
String plugin1 = WebServer.arg("plugin_108_state");
Settings.TaskDevicePluginConfig[event->TaskIndex][0] = plugin1.toInt();
success = true;
break;
}
case PLUGIN_INIT:
{
if (!WOL)
{
WOL = new WiFiUDP;
WOL->begin(7);
}
if (Settings.TaskDevicePin1[event->TaskIndex] != -1)
{
pinMode(Settings.TaskDevicePin1[event->TaskIndex], OUTPUT);
digitalWrite(Settings.TaskDevicePin1[event->TaskIndex], !Settings.TaskDevicePluginConfig[event->TaskIndex][0]);
}
}
success = true;
break;
case PLUGIN_ONCE_A_SECOND:
{
int packetSize = WOL->parsePacket();
if (packetSize)
{
statusLED(true);
char packetBuffer[128];
int len = WOL->read(packetBuffer, 128);
byte match = 0;
uint8_t mac[] = {0, 0, 0, 0, 0, 0};
uint8_t* macread = WiFi.macAddress(mac);
for (byte x = 0; x < 10; x++)
if (packetBuffer[x + 6] == macread[x])
match++;
if (match == 6)
{
String log = F("WOL : Magic packet received!");
addLog(LOG_LEVEL_INFO, log);
if (Settings.TaskDevicePin1[event->TaskIndex] != -1)
{
String log = F("WOL : Power cycle using pin: ");
log += Settings.TaskDevicePin1[event->TaskIndex];
addLog(LOG_LEVEL_INFO, log);
digitalWrite(Settings.TaskDevicePin1[event->TaskIndex], Settings.TaskDevicePluginConfig[event->TaskIndex][0]);
delay(500);
digitalWrite(Settings.TaskDevicePin1[event->TaskIndex], !Settings.TaskDevicePluginConfig[event->TaskIndex][0]);
}
}
}
success = true;
break;
}
}
return success;
}
#endif
+260
View File
@@ -0,0 +1,260 @@
#ifdef PLUGIN_BUILD_DEV
//#######################################################################################################
//################################## Plugin 109 RESOL DeltaSol Pro ######################################
//#######################################################################################################
#include <SoftwareSerial.h>
#define PLUGIN_109
#define PLUGIN_ID_109 109
#define PLUGIN_NAME_109 "RESOL DeltaSol Pro [DEVELOPMENT]"
#define PLUGIN_VALUENAME1_109 "register"
// uart rx-buffer size
#define RXBUF_SIZE 32
// RESOL register options
#define REG_TEMP_SENSOR_1 0
#define REG_TEMP_SENSOR_2 1
#define REG_TEMP_SENSOR_3 2
#define REG_RELAIS_1 3
#define REG_RELAIS_2 4
// total number of selectable registers
#define RESOL_REGISTER_OPTIONS 5
uint8_t Plugin_109_UART_Pin;
boolean Plugin_109_init = false;
boolean valuesValid = false;
uint8_t RXbuf[RXBUF_SIZE];
uint8_t RXbuf_IDX;
int16_t T1, T2 ,T3;
uint8_t R1, R2;
SoftwareSerial *Plugin_109_UART;
// RESOL CRC check
uint8_t VBus_CalcCrc(uint8_t *buf, uint8_t len)
{
uint8_t crc;
crc = 0x7F;
for (uint8_t i = 0; i < len; i++)
crc = (crc - buf [i]) & 0x7F;
return crc;
}
boolean Plugin_109(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_109;
Device[deviceCount].Type = DEVICE_TYPE_DUAL;
Device[deviceCount].VType = SENSOR_TYPE_SINGLE;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = true;
Device[deviceCount].ValueCount = 1;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].TimerOption = true;
Device[deviceCount].TimerOptional = true;
Device[deviceCount].GlobalSyncOption = true;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_109);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_109));
break;
}
case PLUGIN_WEBFORM_LOAD:
{
int16_t choice = Settings.TaskDevicePluginConfig[event->TaskIndex][0];
String options[RESOL_REGISTER_OPTIONS];
uint optionValues[RESOL_REGISTER_OPTIONS];
optionValues[0] = REG_TEMP_SENSOR_1;
options[0] = F("Temperature sensor 1");
optionValues[1] = REG_TEMP_SENSOR_2;
options[1] = F("Temperature sensor 2");
optionValues[2] = REG_TEMP_SENSOR_3;
options[2] = F("Temperature sensor 3");
optionValues[3] = REG_RELAIS_1;
options[3] = F("Relais 1");
optionValues[4] = REG_RELAIS_2;
options[4] = F("Relais 2");
string += F("<TR><TD>Register:<TD><select name='plugin_109_register'>");
for (byte x = 0; x < RESOL_REGISTER_OPTIONS; x++)
{
string += F("<option value='");
string += optionValues[x];
string += "'";
if (choice == optionValues[x])
string += F(" selected");
string += ">";
string += options[x];
string += F("</option>");
}
string += F("</select>");
success = true;
break;
}
case PLUGIN_WEBFORM_SAVE:
{
String plugin1 = WebServer.arg("plugin_109_register");
Settings.TaskDevicePluginConfig[event->TaskIndex][0] = plugin1.toInt();
Plugin_109_init = false; // Force device setup next time
success = true;
break;
}
case PLUGIN_INIT:
{
addLog(LOG_LEVEL_INFO, (char*)"INIT : RESOL DeltaSol Pro");
Plugin_109_UART = new SoftwareSerial(Settings.TaskDevicePin1[event->TaskIndex], Settings.TaskDevicePin2[event->TaskIndex], false, (2 * RXBUF_SIZE)); // set RX und Tx Pin number, no invert, buffer
Plugin_109_init = true;
success = true;
break;
}
case PLUGIN_TEN_PER_SECOND:
{
if (Plugin_109_init == true)
{
// buffer filled with more than twize the frame size bytes? -> at least one complete frame should then be available
if (Plugin_109_UART->available() > (2 * 30))
{
// search for first appearance of SOF (0xAA)
while ((Plugin_109_UART->available()) && (Plugin_109_UART->read() != 0xAA));
RXbuf_IDX = 0;
RXbuf[RXbuf_IDX] = 0;
while ((Plugin_109_UART->available()) && (RXbuf[RXbuf_IDX] != 0xAA) && (RXbuf_IDX < (RXBUF_SIZE - 1)))
RXbuf[++RXbuf_IDX] = Plugin_109_UART->read();
if (RXbuf[RXbuf_IDX] == 0xAA)
{
// ********* DeltaSol Pro registers *********
// check CRC of first and second group
if ( (VBus_CalcCrc(&RXbuf[10], 5) == RXbuf[15]) && (VBus_CalcCrc(&RXbuf[16], 5) == RXbuf[21]) )
{
// Temperature sensor 1
T1 = RXbuf[10] + ((RXbuf[14] & (1 << 0)) << 7);
T1 += (RXbuf[11] + ((RXbuf[14] & (1 << 1)) << 6)) << 8;
// Temperature sensor 2
T2 = RXbuf[12] + ((RXbuf[14] & (1 << 2)) << 5);
T2 += (RXbuf[13] + ((RXbuf[14] & (1 << 3)) << 4)) << 8;
// Temperature sensor 3
T3 = RXbuf[16] + ((RXbuf[20] & (1 << 0)) << 7);
T3 += (RXbuf[17] + ((RXbuf[20] & (1 << 1)) << 6)) << 8;
// Relais/speed
R1 = RXbuf[18] + ((RXbuf[20] & (1 << 2)) << 5);
R2 = RXbuf[19] + ((RXbuf[20] & (1 << 3)) << 4);
valuesValid = true;
} else
valuesValid = false;
}
Plugin_109_UART->flush();
}
}
success = true;
break;
}
case PLUGIN_READ:
{
float regValue;
String log = F("RESOL : ");
if (valuesValid == true)
{
switch (Settings.TaskDevicePluginConfig[event->TaskIndex][0])
{
case REG_TEMP_SENSOR_1:
{
log += F("T1");
regValue = (float)T1 / 10;
break;
}
case REG_TEMP_SENSOR_2:
{
log += F("T2");
regValue = (float)T2 / 10;
break;
}
case REG_TEMP_SENSOR_3:
{
log += F("T3");
regValue = (float)T3 / 10;
break;
}
case REG_RELAIS_1:
{
log += F("R1");
regValue = R1;
break;
}
case REG_RELAIS_2:
{
log += F("R2");
regValue = R2;
break;
}
default:
{
log += F("unknown");
regValue = 0;
break;
}
}
log += F("=");
log += regValue;
addLog(LOG_LEVEL_INFO, log);
UserVar[event->BaseVarIndex] = regValue;
success = true;
break;
}
}
}
return success;
}
#endif
+121
View File
@@ -0,0 +1,121 @@
#ifdef PLUGIN_BUILD_DEV
//#######################################################################################################
//############################# Plugin 111: SenseAir CO2 Sensors ########################################
//#######################################################################################################
/*
Plugin written by: Daniel Tedenljung info__AT__tedenljungconsulting.com
This plugin reads the co2 value of SenseAir Co2 Sensors. (S8 works, K30 is ongoing)
Datasheet can be found here for S8: http://www.senseair.com/products/oem-modules/senseair-s8/
Datasheet can be found here for K30: http://www.senseair.com/products/oem-modules/k30/
You can buy sensor from m.nu in Sweden:
S8 https://www.m.nu/co2matare-fran-senseair-p-1440.html
K30 https://www.m.nu/k30-co2matare-p-302.html
*/
#define PLUGIN_111
#define PLUGIN_ID_111 111
#define PLUGIN_NAME_111 "SenseAir CO2 Sensor [DEVELOPMENT]"
#define PLUGIN_VALUENAME1_111 "PPM"
boolean Plugin_111_init = false;
#include <SoftwareSerial.h>
SoftwareSerial *Plugin_111_S8;
// 0xFE=Any address 0x04=Read input registers 0x0003=Starting address 0x0001=Number of registers to read 0xD5C5=CRC in reverse order
byte cmdReadPPM[] = {0xFE, 0x04, 0x00, 0x03, 0x00, 0x01, 0xD5, 0xC5};
byte ReciveBuffer[7];
byte Data[5];
byte co2[2];
long ppm;
boolean Plugin_111(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_111;
Device[deviceCount].Type = DEVICE_TYPE_DUAL;
Device[deviceCount].VType = SENSOR_TYPE_SINGLE;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = true;
Device[deviceCount].ValueCount = 1;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].TimerOption = true;
Device[deviceCount].GlobalSyncOption = true;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_111);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_111));
break;
}
case PLUGIN_INIT:
{
Plugin_111_init = true;
Plugin_111_S8 = new SoftwareSerial(Settings.TaskDevicePin1[event->TaskIndex], Settings.TaskDevicePin2[event->TaskIndex]); // TODO: Explain this in plugin description RX=GPIO Setting 1, TX=GPIO Setting 2, Use 1kOhm in serie on datapins!
success = true;
break;
}
case PLUGIN_READ:
{
if (Plugin_111_init)
{
Plugin_111_S8->write(cmdReadPPM, sizeof(cmdReadPPM)); //Send the byte array
delay(50);
// Read answer from S8
int ByteCounter = 0;
while (Plugin_111_S8->available()) {
ReciveBuffer[ByteCounter] = Plugin_111_S8->read();
ByteCounter++;
}
// Divide recived chunk in different registers
for (int i = 0 ; i < sizeof(ReciveBuffer) - 2 ; i++)
{
Data[i] = ReciveBuffer[i];
}
co2[0] = Data[3];
co2[1] = Data[4];
ppm = (co2[0] << 8) | (co2[1]);
int value = ppm;
UserVar[event->BaseVarIndex] = (float)value;
String log = F("S8 : PPM value: "); //TODO: Different string if K30 is used.
log += value;
addLog(LOG_LEVEL_INFO, log);
success = true;
break;
}
break;
}
}
return success;
}
#endif
+237
View File
@@ -0,0 +1,237 @@
#ifdef PLUGIN_BUILD_DEV
//#######################################################################################################
//#################################### Plugin 112: Power Counter ########################################
//#######################################################################################################
//This sketch is based on Plugin 003: Pulse
#define PLUGIN_112
#define PLUGIN_ID_112 112
#define PLUGIN_NAME_112 "Power Counter [DEVELOPMENT]"
#define PLUGIN_VALUENAME1_112 "PowerWh"
#define PLUGIN_VALUENAME2_112 "PowerCountTotal"
unsigned long Plugin_112_pulseCounter[TASKS_MAX];
unsigned long Plugin_112_pulseTotalCounter[TASKS_MAX];
unsigned long Plugin_112_pulseTime[TASKS_MAX];
unsigned long Plugin_112_pulseTimePrevious[TASKS_MAX];
float Plugin_112_pulseUsage[TASKS_MAX];
boolean Plugin_112(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_112;
Device[deviceCount].Type = DEVICE_TYPE_SINGLE;
Device[deviceCount].VType = SENSOR_TYPE_DUAL;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = true;
Device[deviceCount].ValueCount = 2;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].TimerOption = true;
Device[deviceCount].GlobalSyncOption = true;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_112);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_112));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_112));
break;
}
case PLUGIN_WEBFORM_LOAD:
{
char tmpString[128];
sprintf_P(tmpString, PSTR("<TR><TD>Debounce Time (mSec):<TD><input type='text' name='plugin_112_debounce' value='%u'>"), Settings.TaskDevicePluginConfig[event->TaskIndex][0]);
string += tmpString;
sprintf_P(tmpString, PSTR("<TR><TD>Pulses per KWh:<TD><input type='text' name='plugin_112_pulsesperkwh' value='%u'>"), Settings.TaskDevicePluginConfig[event->TaskIndex][1]);
string += tmpString;
success = true;
break;
}
case PLUGIN_WEBFORM_SAVE:
{
String plugin1 = WebServer.arg("plugin_112_debounce");
Settings.TaskDevicePluginConfig[event->TaskIndex][0] = plugin1.toInt();
String plugin2 = WebServer.arg("plugin_112_pulsesperkwh");
Settings.TaskDevicePluginConfig[event->TaskIndex][1] = plugin2.toInt();
success = true;
break;
}
case PLUGIN_WEBFORM_SHOW_VALUES:
{
string += ExtraTaskSettings.TaskDeviceValueNames[0];
string += F(":");
string += Plugin_112_pulseUsage[event->TaskIndex];
string += F("<BR>");
string += ExtraTaskSettings.TaskDeviceValueNames[1];
string += F(":");
string += Plugin_112_pulseTotalCounter[event->TaskIndex];
success = true;
break;
}
case PLUGIN_INIT:
{
String log = F("INIT : Power Counter ");
log += Settings.TaskDevicePin1[event->TaskIndex];
addLog(LOG_LEVEL_INFO,log);
pinMode(Settings.TaskDevicePin1[event->TaskIndex], INPUT_PULLUP);
Plugin_112_pulseinit(Settings.TaskDevicePin1[event->TaskIndex], event->TaskIndex);
success = true;
break;
}
case PLUGIN_READ:
{
Plugin_112_idleusage(event->TaskIndex);
UserVar[event->BaseVarIndex] = Plugin_112_pulseUsage[event->TaskIndex];
UserVar[event->BaseVarIndex+1] = Plugin_112_pulseTotalCounter[event->TaskIndex];
Plugin_112_pulseCounter[event->TaskIndex] = 0;
success = true;
break;
}
}
return success;
}
/*********************************************************************************************\
* Update usage when no pulse has been received for some time, so it will decrease on every
* PLUGIN_READ event instead off keeping the last calculated usage value.
\*********************************************************************************************/
void Plugin_112_idleusage(byte Index)
{
unsigned long PulseTime=millis() - Plugin_112_pulseTimePrevious[Index];
if(PulseTime > (Settings.TaskDeviceTimer[Index] * 1000) && //More than $device_delay passed since last pulse
PulseTime > Plugin_112_pulseTime[Index] ) { //More than last pulse interval
// Let's prevent divison by zero
if(Settings.TaskDevicePluginConfig[Index][1]==0) {
Settings.TaskDevicePluginConfig[Index][1]=1000; // if not configged correctly prevent crashes and set it to 1000 as default value.
}
// WH = =3600000/[pulses per kwh]/[time since last pulse (ms)]
Plugin_112_pulseUsage[Index] = (3600000000./Settings.TaskDevicePluginConfig[Index][1])/PulseTime;
}
}
/*********************************************************************************************\
* Check Pulse Counters (called from irq handler)
\*********************************************************************************************/
void Plugin_112_pulsecheck(byte Index)
{
unsigned long PulseTime=millis() - Plugin_112_pulseTimePrevious[Index];
if(PulseTime > Settings.TaskDevicePluginConfig[Index][0]) // check with debounce time for this task
{
Plugin_112_pulseTimePrevious[Index]=millis(); // moved this up as the operations below might take some time
Plugin_112_pulseCounter[Index]++;
Plugin_112_pulseTotalCounter[Index]++;
Plugin_112_pulseTime[Index] = PulseTime;
// Let's prevent divison by zero
if(Settings.TaskDevicePluginConfig[Index][1]==0) {
Settings.TaskDevicePluginConfig[Index][1]=1000; // if not configged correctly prevent crashes and set it to 1000 as default value.
}
// WH = =3600000/[pulses per kwh]/[time since last pulse (ms)]
Plugin_112_pulseUsage[Index] = (3600000000./Settings.TaskDevicePluginConfig[Index][1])/PulseTime;
}
}
/*********************************************************************************************\
* Pulse Counter IRQ handlers
\*********************************************************************************************/
void Plugin_112_pulse_interrupt1()
{
Plugin_112_pulsecheck(0);
}
void Plugin_112_pulse_interrupt2()
{
Plugin_112_pulsecheck(1);
}
void Plugin_112_pulse_interrupt3()
{
Plugin_112_pulsecheck(2);
}
void Plugin_112_pulse_interrupt4()
{
Plugin_112_pulsecheck(3);
}
void Plugin_112_pulse_interrupt5()
{
Plugin_112_pulsecheck(4);
}
void Plugin_112_pulse_interrupt6()
{
Plugin_112_pulsecheck(5);
}
void Plugin_112_pulse_interrupt7()
{
Plugin_112_pulsecheck(6);
}
void Plugin_112_pulse_interrupt8()
{
Plugin_112_pulsecheck(7);
}
/*********************************************************************************************\
* Init Pulse Counters
\*********************************************************************************************/
void Plugin_112_pulseinit(byte Par1, byte Index)
{
// Init IO pins
String log = F("Power Counter: Init");
addLog(LOG_LEVEL_INFO,log);
switch (Index)
{
case 0:
attachInterrupt(Par1, Plugin_112_pulse_interrupt1, FALLING);
break;
case 1:
attachInterrupt(Par1, Plugin_112_pulse_interrupt2, FALLING);
break;
case 2:
attachInterrupt(Par1, Plugin_112_pulse_interrupt3, FALLING);
break;
case 3:
attachInterrupt(Par1, Plugin_112_pulse_interrupt4, FALLING);
break;
case 4:
attachInterrupt(Par1, Plugin_112_pulse_interrupt5, FALLING);
break;
case 5:
attachInterrupt(Par1, Plugin_112_pulse_interrupt6, FALLING);
break;
case 6:
attachInterrupt(Par1, Plugin_112_pulse_interrupt7, FALLING);
break;
case 7:
attachInterrupt(Par1, Plugin_112_pulse_interrupt8, FALLING);
break;
}
}
#endif
+352
View File
@@ -0,0 +1,352 @@
#ifdef PLUGIN_BUILD_DEV
//#######################################################################################################
//#################################### Plugin-113: SI1145 - UV index / IR / visible ####################
//#######################################################################################################
//#include <Adafruit_SI1145.h>
#define PLUGIN_113
#define PLUGIN_ID_113 113
#define PLUGIN_NAME_113 "SI1145 UV index (Adafruit-QA) [DEVELOPMENT]"
#define PLUGIN_VALUENAME1_113 "VISIBLE"
#define PLUGIN_VALUENAME2_113 "INFRA"
#define PLUGIN_VALUENAME3_113 "UV"
// ADAFRUIT SI1145 LIBRARY - Adafruit_SI1145.h
// =============================================
#if (ARDUINO >= 100)
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include <Wire.h>
/* COMMANDS */
#define SI1145_PARAM_QUERY 0x80
#define SI1145_PARAM_SET 0xA0
#define SI1145_NOP 0x0
#define SI1145_RESET 0x01
#define SI1145_BUSADDR 0x02
#define SI1145_PS_FORCE 0x05
#define SI1145_ALS_FORCE 0x06
#define SI1145_PSALS_FORCE 0x07
#define SI1145_PS_PAUSE 0x09
#define SI1145_ALS_PAUSE 0x0A
#define SI1145_PSALS_PAUSE 0xB
#define SI1145_PS_AUTO 0x0D
#define SI1145_ALS_AUTO 0x0E
#define SI1145_PSALS_AUTO 0x0F
#define SI1145_GET_CAL 0x12
/* Parameters */
#define SI1145_PARAM_I2CADDR 0x00
#define SI1145_PARAM_CHLIST 0x01
#define SI1145_PARAM_CHLIST_ENUV 0x80
#define SI1145_PARAM_CHLIST_ENAUX 0x40
#define SI1145_PARAM_CHLIST_ENALSIR 0x20
#define SI1145_PARAM_CHLIST_ENALSVIS 0x10
#define SI1145_PARAM_CHLIST_ENPS1 0x01
#define SI1145_PARAM_CHLIST_ENPS2 0x02
#define SI1145_PARAM_CHLIST_ENPS3 0x04
#define SI1145_PARAM_PSLED12SEL 0x02
#define SI1145_PARAM_PSLED12SEL_PS2NONE 0x00
#define SI1145_PARAM_PSLED12SEL_PS2LED1 0x10
#define SI1145_PARAM_PSLED12SEL_PS2LED2 0x20
#define SI1145_PARAM_PSLED12SEL_PS2LED3 0x40
#define SI1145_PARAM_PSLED12SEL_PS1NONE 0x00
#define SI1145_PARAM_PSLED12SEL_PS1LED1 0x01
#define SI1145_PARAM_PSLED12SEL_PS1LED2 0x02
#define SI1145_PARAM_PSLED12SEL_PS1LED3 0x04
#define SI1145_PARAM_PSLED3SEL 0x03
#define SI1145_PARAM_PSENCODE 0x05
#define SI1145_PARAM_ALSENCODE 0x06
#define SI1145_PARAM_PS1ADCMUX 0x07
#define SI1145_PARAM_PS2ADCMUX 0x08
#define SI1145_PARAM_PS3ADCMUX 0x09
#define SI1145_PARAM_PSADCOUNTER 0x0A
#define SI1145_PARAM_PSADCGAIN 0x0B
#define SI1145_PARAM_PSADCMISC 0x0C
#define SI1145_PARAM_PSADCMISC_RANGE 0x20
#define SI1145_PARAM_PSADCMISC_PSMODE 0x04
#define SI1145_PARAM_ALSIRADCMUX 0x0E
#define SI1145_PARAM_AUXADCMUX 0x0F
#define SI1145_PARAM_ALSVISADCOUNTER 0x10
#define SI1145_PARAM_ALSVISADCGAIN 0x11
#define SI1145_PARAM_ALSVISADCMISC 0x12
#define SI1145_PARAM_ALSVISADCMISC_VISRANGE 0x20
#define SI1145_PARAM_ALSIRADCOUNTER 0x1D
#define SI1145_PARAM_ALSIRADCGAIN 0x1E
#define SI1145_PARAM_ALSIRADCMISC 0x1F
#define SI1145_PARAM_ALSIRADCMISC_RANGE 0x20
#define SI1145_PARAM_ADCCOUNTER_511CLK 0x70
#define SI1145_PARAM_ADCMUX_SMALLIR 0x00
#define SI1145_PARAM_ADCMUX_LARGEIR 0x03
/* REGISTERS */
#define SI1145_REG_PARTID 0x00
#define SI1145_REG_REVID 0x01
#define SI1145_REG_SEQID 0x02
#define SI1145_REG_INTCFG 0x03
#define SI1145_REG_INTCFG_INTOE 0x01
#define SI1145_REG_INTCFG_INTMODE 0x02
#define SI1145_REG_IRQEN 0x04
#define SI1145_REG_IRQEN_ALSEVERYSAMPLE 0x01
#define SI1145_REG_IRQEN_PS1EVERYSAMPLE 0x04
#define SI1145_REG_IRQEN_PS2EVERYSAMPLE 0x08
#define SI1145_REG_IRQEN_PS3EVERYSAMPLE 0x10
#define SI1145_REG_IRQMODE1 0x05
#define SI1145_REG_IRQMODE2 0x06
#define SI1145_REG_HWKEY 0x07
#define SI1145_REG_MEASRATE0 0x08
#define SI1145_REG_MEASRATE1 0x09
#define SI1145_REG_PSRATE 0x0A
#define SI1145_REG_PSLED21 0x0F
#define SI1145_REG_PSLED3 0x10
#define SI1145_REG_UCOEFF0 0x13
#define SI1145_REG_UCOEFF1 0x14
#define SI1145_REG_UCOEFF2 0x15
#define SI1145_REG_UCOEFF3 0x16
#define SI1145_REG_PARAMWR 0x17
#define SI1145_REG_COMMAND 0x18
#define SI1145_REG_RESPONSE 0x20
#define SI1145_REG_IRQSTAT 0x21
#define SI1145_REG_IRQSTAT_ALS 0x01
#define SI1145_REG_ALSVISDATA0 0x22
#define SI1145_REG_ALSVISDATA1 0x23
#define SI1145_REG_ALSIRDATA0 0x24
#define SI1145_REG_ALSIRDATA1 0x25
#define SI1145_REG_PS1DATA0 0x26
#define SI1145_REG_PS1DATA1 0x27
#define SI1145_REG_PS2DATA0 0x28
#define SI1145_REG_PS2DATA1 0x29
#define SI1145_REG_PS3DATA0 0x2A
#define SI1145_REG_PS3DATA1 0x2B
#define SI1145_REG_UVINDEX0 0x2C
#define SI1145_REG_UVINDEX1 0x2D
#define SI1145_REG_PARAMRD 0x2E
#define SI1145_REG_CHIPSTAT 0x30
#define SI1145_ADDR 0x60
class Adafruit_SI1145 {
public:
Adafruit_SI1145(void);
boolean begin();
void reset();
uint16_t readUV();
uint16_t readIR();
uint16_t readVisible();
uint16_t readProx();
private:
uint16_t read16(uint8_t addr);
uint8_t read8(uint8_t addr);
void write8(uint8_t reg, uint8_t val);
uint8_t readParam(uint8_t p);
uint8_t writeParam(uint8_t p, uint8_t v);
uint8_t _addr;
};
// ADAFRUIT SI1145 LIBRARY - Adafruit_SI1145.cpp
// =============================================
Adafruit_SI1145::Adafruit_SI1145() {
_addr = SI1145_ADDR;
}
boolean Adafruit_SI1145::begin(void) {
Wire.begin();
uint8_t id = read8(SI1145_REG_PARTID);
if (id != 0x45) return false; // look for SI1145
reset();
/***********************************/
// enable UVindex measurement coefficients!
write8(SI1145_REG_UCOEFF0, 0x29);
write8(SI1145_REG_UCOEFF1, 0x89);
write8(SI1145_REG_UCOEFF2, 0x02);
write8(SI1145_REG_UCOEFF3, 0x00);
// enable UV sensor
writeParam(SI1145_PARAM_CHLIST, SI1145_PARAM_CHLIST_ENUV |
SI1145_PARAM_CHLIST_ENALSIR | SI1145_PARAM_CHLIST_ENALSVIS |
SI1145_PARAM_CHLIST_ENPS1);
// enable interrupt on every sample
write8(SI1145_REG_INTCFG, SI1145_REG_INTCFG_INTOE);
write8(SI1145_REG_IRQEN, SI1145_REG_IRQEN_ALSEVERYSAMPLE);
/****************************** Prox Sense 1 */
// program LED current
write8(SI1145_REG_PSLED21, 0x03); // 20mA for LED 1 only
writeParam(SI1145_PARAM_PS1ADCMUX, SI1145_PARAM_ADCMUX_LARGEIR);
// prox sensor #1 uses LED #1
writeParam(SI1145_PARAM_PSLED12SEL, SI1145_PARAM_PSLED12SEL_PS1LED1);
// fastest clocks, clock div 1
writeParam(SI1145_PARAM_PSADCGAIN, 0);
// take 511 clocks to measure
writeParam(SI1145_PARAM_PSADCOUNTER, SI1145_PARAM_ADCCOUNTER_511CLK);
// in prox mode, high range
writeParam(SI1145_PARAM_PSADCMISC, SI1145_PARAM_PSADCMISC_RANGE|
SI1145_PARAM_PSADCMISC_PSMODE);
writeParam(SI1145_PARAM_ALSIRADCMUX, SI1145_PARAM_ADCMUX_SMALLIR);
// fastest clocks, clock div 1
writeParam(SI1145_PARAM_ALSIRADCGAIN, 0);
// take 511 clocks to measure
writeParam(SI1145_PARAM_ALSIRADCOUNTER, SI1145_PARAM_ADCCOUNTER_511CLK);
// in high range mode
writeParam(SI1145_PARAM_ALSIRADCMISC, SI1145_PARAM_ALSIRADCMISC_RANGE);
// fastest clocks, clock div 1
writeParam(SI1145_PARAM_ALSVISADCGAIN, 0);
// take 511 clocks to measure
writeParam(SI1145_PARAM_ALSVISADCOUNTER, SI1145_PARAM_ADCCOUNTER_511CLK);
// in high range mode (not normal signal)
writeParam(SI1145_PARAM_ALSVISADCMISC, SI1145_PARAM_ALSVISADCMISC_VISRANGE);
/************************/
// measurement rate for auto
write8(SI1145_REG_MEASRATE0, 0xFF); // 255 * 31.25uS = 8ms
// auto run
write8(SI1145_REG_COMMAND, SI1145_PSALS_AUTO);
return true;
}
void Adafruit_SI1145::reset() {
write8(SI1145_REG_MEASRATE0, 0);
write8(SI1145_REG_MEASRATE1, 0);
write8(SI1145_REG_IRQEN, 0);
write8(SI1145_REG_IRQMODE1, 0);
write8(SI1145_REG_IRQMODE2, 0);
write8(SI1145_REG_INTCFG, 0);
write8(SI1145_REG_IRQSTAT, 0xFF);
write8(SI1145_REG_COMMAND, SI1145_RESET);
delay(10);
write8(SI1145_REG_HWKEY, 0x17);
delay(10);
}
//////////////////////////////////////////////////////
// returns the UV index * 100 (divide by 100 to get the index)
uint16_t Adafruit_SI1145::readUV(void) { return read16(0x2C); }
// returns visible+IR light levels
uint16_t Adafruit_SI1145::readVisible(void) { return read16(0x22); }
// returns IR light levels
uint16_t Adafruit_SI1145::readIR(void) { return read16(0x24); }
// returns "Proximity" - assumes an IR LED is attached to LED
uint16_t Adafruit_SI1145::readProx(void) {
return read16(0x26);
}
/*********************************************************************/
uint8_t Adafruit_SI1145::writeParam(uint8_t p, uint8_t v) {
//Serial.print("Param 0x"); Serial.print(p, HEX);
//Serial.print(" = 0x"); Serial.println(v, HEX);
write8(SI1145_REG_PARAMWR, v);
write8(SI1145_REG_COMMAND, p | SI1145_PARAM_SET);
return read8(SI1145_REG_PARAMRD);
}
uint8_t Adafruit_SI1145::readParam(uint8_t p) {
write8(SI1145_REG_COMMAND, p | SI1145_PARAM_QUERY);
return read8(SI1145_REG_PARAMRD);
}
/*********************************************************************/
uint8_t Adafruit_SI1145::read8(uint8_t reg) {
uint16_t val;
Wire.beginTransmission(_addr);
Wire.write((uint8_t)reg);
Wire.endTransmission();
Wire.requestFrom((uint8_t)_addr, (uint8_t)1);
return Wire.read();
}
uint16_t Adafruit_SI1145::read16(uint8_t a) {
uint16_t ret;
Wire.beginTransmission(_addr); // start transmission to device
Wire.write(a); // sends register address to read from
Wire.endTransmission(); // end transmission
Wire.requestFrom(_addr, (uint8_t)2);// send data n-bytes read
ret = Wire.read(); // receive DATA
ret |= (uint16_t)Wire.read() << 8; // receive DATA
return ret;
}
void Adafruit_SI1145::write8(uint8_t reg, uint8_t val) {
Wire.beginTransmission(_addr); // start transmission to device
Wire.write(reg); // sends register address to write
Wire.write(val); // sends value
Wire.endTransmission(); // end transmission
}
Adafruit_SI1145 uv = Adafruit_SI1145();
boolean Plugin_113_init = false;
boolean Plugin_113(byte function, struct EventStruct *event, String& string)
{
boolean success=false;
switch(function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_113;
Device[deviceCount].Type = DEVICE_TYPE_I2C;
Device[deviceCount].VType = SENSOR_TYPE_SINGLE;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = true;
Device[deviceCount].ValueCount = 3;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].TimerOption = true;
Device[deviceCount].GlobalSyncOption = true;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_113);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_113));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_113));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_113));
break;
}
case PLUGIN_READ:
{
if (!Plugin_113_init)
{
// ziadnu EXTRA inicializaciu netreba ...
if (uv.begin()) { Plugin_113_init = true; }
}
float si1145_vis = 0.0;
float si1145_ir = 0.0;
float si1145_uv = 0.0;
si1145_vis = uv.readVisible();
si1145_ir = uv.readIR();
si1145_uv = uv.readUV();
si1145_uv /= 100.0;
UserVar[event->BaseVarIndex] = si1145_vis;
UserVar[event->BaseVarIndex+1] = si1145_ir;
UserVar[event->BaseVarIndex+2] = si1145_uv;
String log = F("SI1145: Visible: ");
log += UserVar[event->BaseVarIndex];
addLog(LOG_LEVEL_INFO,log);
log = F("SI1145: Infrared: ");
log += UserVar[event->BaseVarIndex+1];
addLog(LOG_LEVEL_INFO,log);
log = F("SI1145: UV index: ");
log += UserVar[event->BaseVarIndex+2];
addLog(LOG_LEVEL_INFO,log);
success=true;
break;
}
}
return success;
}
#endif
+178
View File
@@ -0,0 +1,178 @@
#ifdef PLUGIN_BUILD_DEV
//#######################################################################################################
//#################################### Plugin 114: DMS501A #############################################
//#################################### by serpa #############################################
//#######################################################################################################
#define PLUGIN_114
#define PLUGIN_ID_114 114
#define PLUGIN_NAME_114 "Dust sensor - DSM501a [DEVELOPMENT]"
#define PLUGIN_VALUENAME1_114 "PM1.0" // from the datasheet the detection is from PM1 and up. You could have from PM1 to PM2.5, on subtracting PM2.5 value on PM1 value. This value come from the pin #4
#define PLUGIN_VALUENAME2_114 "PM2.5" // from the datasheet the detection is from PM2.5 and up. This value come from the pin #2. With different resistor topn the pin #1, you could adjust the size threshold detection
unsigned long Plugin_114_pulseCounter[TASKS_MAX];
unsigned long Plugin_114_pulseTotalCounter[TASKS_MAX];
unsigned long Plugin_114_pulseTime[TASKS_MAX];
unsigned long Plugin_114_pulseTimePrevious[TASKS_MAX];
unsigned long tstart1, tstart2;
unsigned long tduration = 30000; // duration of measurement in ms
//unsigned long triggerOn; // start of pulse time in us
//unsigned long triggerOff; // end of pulse time in us
//unsigned long lowpulseoccupancy; // duration of pulse in us
volatile unsigned long thigh1, thigh2;
volatile unsigned long tlow1, tlow2;
volatile unsigned long startlow1, startlow2;
volatile unsigned long starthigh1, starthigh2;
volatile boolean done1, done2;
volatile boolean value1, value2;
//boolean trigger = false;
volatile float ratio1, ratio2;
volatile int DMSpin1, DMSpin2;
float dens1, dens2;
boolean Plugin_114(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_114;
Device[deviceCount].Type = DEVICE_TYPE_DUAL;
Device[deviceCount].VType = SENSOR_TYPE_TEMP_HUM;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = true;
Device[deviceCount].ValueCount = 2;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].TimerOption = true;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_114);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_114));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_114));
break;
}
case PLUGIN_WEBFORM_LOAD:
{
char tmpString[128];
sprintf_P(tmpString, PSTR("<TR><TD>Averaging Time (mSec):<TD><input type='text' name='plugin_114' value='%u'>"), Settings.TaskDevicePluginConfig[event->TaskIndex][0]);
string += tmpString;
success = true;
break;
}
case PLUGIN_WEBFORM_SAVE:
{
String plugin1 = WebServer.arg("plugin_114");
Settings.TaskDevicePluginConfig[event->TaskIndex][0] = plugin1.toInt();
// tduration= Settings.TaskDevicePluginConfig[event->TaskIndex][0];
success = true;
break;
}
case PLUGIN_INIT:
{
String log = F("INIT : DSM501A ");
log += Settings.TaskDevicePin1[event->TaskIndex];
addLog(LOG_LEVEL_INFO, log);
// tduration= Settings.TaskDevicePluginConfig[event->TaskIndex][0];
tstart1 = millis();
startlow1 = micros();
starthigh1 = startlow1;
DMSpin1 = Settings.TaskDevicePin1[event->TaskIndex];
pinMode(DMSpin1, INPUT);
attachInterrupt(digitalPinToInterrupt(DMSpin1), Plugin_114_ISR1, CHANGE);
tstart2 = millis();
startlow2 = micros();
starthigh2 = startlow2;
DMSpin2 = Settings.TaskDevicePin2[event->TaskIndex];
pinMode(DMSpin2, INPUT);
attachInterrupt(digitalPinToInterrupt(DMSpin2), Plugin_114_ISR2, CHANGE);
success = true;
break;
}
case PLUGIN_READ:
{
if (done1 && done2) {
done1 = FALSE;
dens1 = ratio1 * 110; // ug/m^3
done2 = FALSE;
dens2 = ratio2 * 110; // ug/m^3
String log = F("DSM501A: PM1.0=");
log += dens1;
log += F(" PM2.5=");
log += dens2;
addLog(LOG_LEVEL_INFO, log);
UserVar[event->BaseVarIndex] = (float) dens1;
UserVar[event->BaseVarIndex + 1] = (float) dens2;
}
success = true;
break;
}
}
return success;
}
/*********************************************************************************************\
Check Pulse (called from irq handler)
\*********************************************************************************************/
void Plugin_114_ISR1()
{
value1 = digitalRead(DMSpin1); //read input pin just changed
if (value1 == 0) { // gone low
startlow1 = micros(); // record starting of low period
thigh1 += startlow1 - starthigh1; // record duration of past high state
} else { // gone high
starthigh1 = micros(); // record starting of high period
tlow1 += starthigh1 - startlow1; // record duration of past low state
}
if (millis() > tstart1 + tduration) { // check if average time has past
tstart1 = millis(); // reset time period
ratio1 = float(tlow1) / float(thigh1 + tlow1) * 100; // compute ratio low to total
tlow1 = 0; // reset low time counter
thigh1 = 0; // reset high time counter
done1 = TRUE; // set reading complete flag
}
}
/*********************************************************************************************\
Check Pulse (called from irq handler)
\*********************************************************************************************/
void Plugin_114_ISR2()
{
value2 = digitalRead(DMSpin2); //read input pin just changed
if (value2 == 0) { // gone low
startlow2 = micros(); // record starting of low period
thigh2 += startlow2 - starthigh2; // record duration of past high state
} else { // gone high
starthigh2 = micros(); // record starting of high period
tlow2 += starthigh2 - startlow2; // record duration of past low state
}
if (millis() > tstart2 + tduration) { // check if average time has past
tstart2 = millis(); // reset time period
ratio2 = float(tlow2) / float(thigh2 + tlow2) * 100; // compute ratio low to high
tlow2 = 0; // reset low time counter
thigh2 = 0; // reset high time counter
done2 = TRUE; // set reading complete flag
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
#endif
+140
View File
@@ -0,0 +1,140 @@
#ifdef PLUGIN_BUILD_DEV
//#######################################################################################################
//#################################### Plugin 116: Serial RFID ID-12 ####################################
//#######################################################################################################
#define PLUGIN_116
#define PLUGIN_ID_116 116
#define PLUGIN_NAME_116 "RFID Reader - ID12LA [DEVELOPMENT]"
#define PLUGIN_VALUENAME1_116 "Tag"
boolean Plugin_116_init = false;
boolean Plugin_116(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_116;
Device[deviceCount].VType = SENSOR_TYPE_LONG;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = false;
Device[deviceCount].ValueCount = 1;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].TimerOption = false;
Device[deviceCount].GlobalSyncOption = true;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_116);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_116));
break;
}
case PLUGIN_INIT:
{
Plugin_116_init = true;
Serial.begin(9600);
success = true;
break;
}
case PLUGIN_SERIAL_IN:
{
if (Plugin_116_init)
{
byte val = 0;
byte code[6];
byte checksum = 0;
byte bytesread = 0;
byte tempbyte = 0;
if ((val = Serial.read()) == 2)
{ // check for header
bytesread = 0;
while (bytesread < 12) { // read 10 digit code + 2 digit checksum
if ( Serial.available() > 0) {
val = Serial.read();
if ((val == 0x0D) || (val == 0x0A) || (val == 0x03) || (val == 0x02)) {
// if header or stop bytes before the 10 digit reading
break;
}
// Do Ascii/Hex conversion:
if ((val >= '0') && (val <= '9')) {
val = val - '0';
}
else if ((val >= 'A') && (val <= 'F')) {
val = 10 + val - 'A';
}
// Every two hex-digits, add byte to code:
if (bytesread & 1 == 1) {
// make some space for this hex-digit by
// shifting the previous hex-digit with 4 bits to the left:
code[bytesread >> 1] = (val | (tempbyte << 4));
if (bytesread >> 1 != 5) { // If we're at the checksum byte,
checksum ^= code[bytesread >> 1]; // Calculate the checksum... (XOR)
};
}
else {
tempbyte = val; // Store the first hex digit first...
};
bytesread++; // ready to read next digit
}
}
}
if (bytesread == 12)
{
if (code[5] == checksum)
{
// temp woraround, ESP Easy framework does not currently prepare this...
byte index = 0;
for (byte y = 0; y < TASKS_MAX; y++)
if (Settings.TaskDeviceNumber[y] == PLUGIN_ID_116)
index = y;
byte DeviceIndex = getDeviceIndex(Settings.TaskDeviceNumber[index]);
event->TaskIndex = index;
event->BaseVarIndex = index * VARS_PER_TASK;
event->idx = Settings.TaskDeviceID[index];
event->sensorType = Device[DeviceIndex].VType;
// endof workaround
unsigned long key = 0;
for (byte i = 1; i < 5; i++) key = key | (((unsigned long) code[i] << ((4 - i) * 8)));
UserVar[event->BaseVarIndex] = (key & 0xFFFF);
UserVar[event->BaseVarIndex + 1] = ((key >> 16) & 0xFFFF);
String log = F("RFID : Tag: ");
log += key;
addLog(LOG_LEVEL_INFO, log);
sendData(event);
}
}
success = true;
}
break;
}
}
return success;
}
#endif
+434
View File
@@ -0,0 +1,434 @@
//#######################################################################################################
//#################################### Plugin 118: MPU6050 [Testing] ####################################
//#######################################################################################################
// Based on the works of Nolan Gilley @ https://home-assistant.io/blog/2016/08/03/laundry-automation-update/
// falling under the following license CC-BY-SA, https://creativecommons.org/licenses/by-sa/2.0/
// and the works of Jeff Rowberg @ https://www.i2cdevlib.com/devices/mpu6050, specifically his I2C Functions
// in this plugin are based on or are a copy from the following two libraries:
// I2Cdev: https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/I2Cdev
// MPU6050: https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/MPU6050
// Which contain the following license information:
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions: The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// This plugin enables the use of a MPU6050 sensor as e.g. used in the breakoutboard GY-521.
// Using the webform you can set thresholds for the x-y-z axis and timeout values. If the thresholds are
// exceeded the sensor is on, if the thresholds are not met during the timeout period the sensor is off.
// Using this plugin you can get a notification from your home automationsystem when the monitored machine or
// device is no longer vibrating and thus this can be used as a signaling device for the end of a (dish)washer
// or dryer cycle.
// You can also use the plugin to read raw sensorvalues. You can use more then one instance of the plugin and
// you can set multiple movement alarms by giving each instance other threshold values if needed.
// Best practise: Create three custom sensors in your homecontroller (like domoticz) and let it plot the x, y and
// z range. Plot the sensorvalues while you use the washingmachine and/or dryer. Also keep monitoring when they
// are not in use so you can determine the needed thresholds. When you have these you can select the movement
// detection function to setup the plugin for further use.
// The plugin can simultanious be used with two MPU6050 devices by adding multiple instances.
// Plugin var usage:
// Globals - int16_t _P118_axis[3][5][2] Array to store sensorvalues of the axis
// _P118_axis[0-2][x][x] = x, y, z axis
// _P118_axis[x][0-4][x] = min values, max values, range (max-min), a-values, g-values.
// _P118_axis[x][x][0-1] = device address: 0=0x68, 1=0x69
// - long _P118_time[2] = Timer to check values each 5 seconds for each used device address.
// Framework - Settings.TaskDevicePluginConfig[x][0] - Device address (0x68 | 0x69)
// Settings.TaskDevicePluginConfig[x][1] - Instance function
// Settings.TaskDevicePluginConfig[x][2] - ax threshold value
// Settings.TaskDevicePluginConfig[x][3] - ay threshold value
// Settings.TaskDevicePluginConfig[x][4] - az threshold value
// Settings.TaskDevicePluginConfig[x][5] - Minimal detection threshold value
// Settings.TaskDevicePluginConfig[x][6] - Detection threshold window value
// Settings.TaskDevicePluginConfig[x][7] - Last known status of switch
// Settings.TaskDevicePluginConfigLong[x][0] - Minimal detection threshold counter
// Settings.TaskDevicePluginConfigLong[x][1] - Detection threshold window counter
#ifdef PLUGIN_BUILD_TESTING
#define MPU6050_RA_GYRO_CONFIG 0x1B
#define MPU6050_RA_ACCEL_CONFIG 0x1C
#define MPU6050_RA_ACCEL_XOUT_H 0x3B
#define MPU6050_RA_PWR_MGMT_1 0x6B
#define MPU6050_ACONFIG_AFS_SEL_BIT 4
#define MPU6050_ACONFIG_AFS_SEL_LENGTH 2
#define MPU6050_GCONFIG_FS_SEL_BIT 4
#define MPU6050_GCONFIG_FS_SEL_LENGTH 2
#define MPU6050_CLOCK_PLL_XGYRO 0x01
#define MPU6050_GYRO_FS_250 0x00
#define MPU6050_ACCEL_FS_2 0x00
#define MPU6050_PWR1_SLEEP_BIT 6
#define MPU6050_PWR1_CLKSEL_BIT 2
#define MPU6050_PWR1_CLKSEL_LENGTH 3
#define PLUGIN_118
#define PLUGIN_ID_118 118
#define PLUGIN_NAME_118 "MPU 6050 [TESTING]"
#define PLUGIN_VALUENAME1_118 ""
int16_t _P118_axis[3][5][2]; // [xyz], [min/max/range,a,g], [0x68/0x69]
long _P118_time[2];
boolean Plugin_118(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_118;
Device[deviceCount].Type = DEVICE_TYPE_I2C;
Device[deviceCount].VType = SENSOR_TYPE_SINGLE;
Device[deviceCount].ValueCount = 1; // Unfortunatly domoticz has no custom multivalue sensors.
Device[deviceCount].SendDataOption = true; // and I use Domoticz ... so there.
Device[deviceCount].TimerOption = true;
Device[deviceCount].FormulaOption = false;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_118);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_118));
break;
}
case PLUGIN_WEBFORM_LOAD:
{
// Setup webform for address selection
byte choice = Settings.TaskDevicePluginConfig[event->TaskIndex][0];
String options[10];
options[0] = F("0x68 - default settings (ADDR Low)");
options[1] = F("0x69 - alternate settings (ADDR High)");
int optionValues[2];
optionValues[0] = 0x68;
optionValues[1] = 0x69;
string += F("<TR><TD>I2C Address:<TD><select name='plugin_118_address'>");
for (byte x = 0; x < 2; x++)
{
string += F("<option value='");
string += optionValues[x];
string += "'";
if (choice == optionValues[x])
string += F(" selected");
string += ">";
string += options[x];
string += F("</option>");
}
string += F("</select>");
// Setup webform for instance function
choice = Settings.TaskDevicePluginConfig[event->TaskIndex][1];
string += F("<TR><TD>Function:<TD><select name='plugin_118_function'>");
options[0] = F("Movement detection");
options[1] = F("Range acceleration X");
options[2] = F("Range acceleration Y");
options[3] = F("Range acceleration Z");
options[4] = F("Acceleration X");
options[5] = F("Acceleration Y");
options[6] = F("Acceleration Z");
options[7] = F("G-force X");
options[8] = F("G-force Y");
options[9] = F("G-force Z");
for (byte x = 0; x < 10; x++)
{
string += F("<option value='");
string += x;
string += "'";
if (choice == x)
string += F(" selected");
string += ">";
string += options[x];
string += F("</option>");
}
string += F("</select>");
if (choice == 0) {
// If this is instance function 0, setup webform for additional vars
// Show some user information about the webform and what the vars mean.
string += F("<TR><TD><TD>The thresholdvalues (0-65535) can be used to set a threshold for one or more<br>");
string += F("axis. The axis will trigger when the range for that axis exceeds the threshold<br>");
string += F("value. A value of 0 disables movement detection for that axis.");
string += F("<TR><TD>Detection threshold X:<TD><input type='text' size='6' maxlength='6' name='plugin_118_threshold_x' value='");
string += Settings.TaskDevicePluginConfig[event->TaskIndex][2];
string += F("'>");
string += F("<TR><TD>Detection threshold Y:<TD><input type='text' size='6' maxlength='6' name='plugin_118_threshold_y' value='");
string += Settings.TaskDevicePluginConfig[event->TaskIndex][3];
string += F("'>");
string += F("<TR><TD>Detection threshold Z:<TD><input type='text' size='6' maxlength='6' name='plugin_118_threshold_z' value='");
string += Settings.TaskDevicePluginConfig[event->TaskIndex][4];
string += F("'>");
string += F("<TR><TD><TD>Each 30 seconds a counter for the detection window is increased plus all axis<br>");
string += F("are checked and if they *all* exceeded the threshold values, a counter is increased.<br>");
string += F("Each period, defined by the [detection window], the counter is checked against<br>");
string += F("the [min. detection count] and if found equal or larger, movement is detected.<br>");
string += F("If in the next window the [min. detection count] value is not met, movement has stopped.");
string += F("The [detection window] cannot be smaller than the [min. detection count].");
string += F("<TR><TD>Min. detection count:<TD><input type='text' size='6' maxlength='6' name='plugin_118_threshold_counter' value='");
string += Settings.TaskDevicePluginConfig[event->TaskIndex][5];
string += F("'>");
string += F("<TR><TD>Detection window:<TD><input type='text' size='6' maxlength='6' name='plugin_118_threshold_window' value='");
string += Settings.TaskDevicePluginConfig[event->TaskIndex][6];
string += F("'>");
}
success = true;
break;
}
case PLUGIN_WEBFORM_SAVE:
{
// Save the vars
String plugin1 = WebServer.arg("plugin_118_address");
Settings.TaskDevicePluginConfig[event->TaskIndex][0] = plugin1.toInt();
plugin1 = WebServer.arg("plugin_118_function");
Settings.TaskDevicePluginConfig[event->TaskIndex][1] = plugin1.toInt();
plugin1 = WebServer.arg("plugin_118_threshold_x");
Settings.TaskDevicePluginConfig[event->TaskIndex][2] = plugin1.toInt();
plugin1 = WebServer.arg("plugin_118_threshold_y");
Settings.TaskDevicePluginConfig[event->TaskIndex][3] = plugin1.toInt();
plugin1 = WebServer.arg("plugin_118_threshold_z");
Settings.TaskDevicePluginConfig[event->TaskIndex][4] = plugin1.toInt();
plugin1 = WebServer.arg("plugin_118_threshold_counter");
Settings.TaskDevicePluginConfig[event->TaskIndex][5] = plugin1.toInt();
plugin1 = WebServer.arg("plugin_118_threshold_window");
Settings.TaskDevicePluginConfig[event->TaskIndex][6] = plugin1.toInt();
if (Settings.TaskDevicePluginConfig[event->TaskIndex][6] < Settings.TaskDevicePluginConfig[event->TaskIndex][5]) {
Settings.TaskDevicePluginConfig[event->TaskIndex][6] = Settings.TaskDevicePluginConfig[event->TaskIndex][5];
}
success = true;
break;
}
case PLUGIN_INIT:
{
// Initialize the MPU6050. This *can* be done multiple times per instance and device address.
// We could make sure that this is only done once per device address, but why bother?
uint8_t devAddr = Settings.TaskDevicePluginConfig[event->TaskIndex][0];
if ((devAddr < 0x68) || (devAddr > 0x69)) { // Just in case the address is not initialized, set it anyway.
devAddr = 0x68;
Settings.TaskDevicePluginConfig[event->TaskIndex][0] = devAddr;
}
// Initialize the MPU6050, for details look at the MPU6050 library: MPU6050::Initialize
_P118_writeBits(devAddr, MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_CLKSEL_BIT, MPU6050_PWR1_CLKSEL_LENGTH, MPU6050_CLOCK_PLL_XGYRO);
_P118_writeBits(devAddr, MPU6050_RA_GYRO_CONFIG, MPU6050_GCONFIG_FS_SEL_BIT, MPU6050_GCONFIG_FS_SEL_LENGTH, MPU6050_GYRO_FS_250);
_P118_writeBits(devAddr, MPU6050_RA_ACCEL_CONFIG, MPU6050_ACONFIG_AFS_SEL_BIT, MPU6050_ACONFIG_AFS_SEL_LENGTH, MPU6050_ACCEL_FS_2);
_P118_writeBits(devAddr, MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_SLEEP_BIT, 1, 0);
// Read the MPU6050 once to clear out zeros (1st time reading MPU6050 returns all 0s)
int16_t ax, ay, az, gx, gy, gz;
_P118_getMotion6(devAddr, &ax, &ay, &az, &gx, &gy, &gz);
// Reset vars
Settings.TaskDevicePluginConfig[event->TaskIndex][7] = 0; // Last known value of "switch" is off
UserVar[event->BaseVarIndex] = 0; // Switch is off
Settings.TaskDevicePluginConfigLong[event->TaskIndex][0] = 0; // Minimal detection counter is zero
Settings.TaskDevicePluginConfigLong[event->TaskIndex][1] = 0; // Detection window counter is zero
success = true;
break;
}
case PLUGIN_ONCE_A_SECOND:
{
uint8_t devAddr = Settings.TaskDevicePluginConfig[event->TaskIndex][0];
byte dev = devAddr & 1;
// Read the sensorvalues, we run this bit every 1/10th of a second
_P118_getMotion6(devAddr, &_P118_axis[0][3][dev], &_P118_axis[1][3][dev], &_P118_axis[2][3][dev], &_P118_axis[0][4][dev], &_P118_axis[1][4][dev], &_P118_axis[2][4][dev]);
// Set the minimum and maximum value for each axis a-value, overwrite previous values if smaller/larger
_P118_trackMinMax(_P118_axis[0][3][dev], &_P118_axis[0][0][dev], &_P118_axis[0][1][dev]);
_P118_trackMinMax(_P118_axis[1][3][dev], &_P118_axis[1][0][dev], &_P118_axis[1][1][dev]);
_P118_trackMinMax(_P118_axis[2][3][dev], &_P118_axis[2][0][dev], &_P118_axis[2][1][dev]);
// ^ current value @ 3 ^ min val @ 0 ^ max val @ 1
/* // Uncomment this block if you want to debug your MPU6050, but be prepared for a log overload
String log = F("MPU6050 : axis values: ");
log += _P118_axis[0][3][dev]
log += F(", ");
log += _P118_axis[1][3][dev];
log += F(", ");
log += _P118_axis[2][3][dev];
log += F(", g values: ");
log += _P118_axis[0][4][dev];
log += F(", ");
log += _P118_axis[1][4][dev];
log += F(", ");
log += _P118_axis[2][4][dev];
addLog(LOG_LEVEL_INFO,log);
*/
// Run this bit every 5 seconds per deviceaddress (not per instance)
if ((_P118_time[dev] + 5000) < millis())
{
_P118_time[dev] = millis();
// Determine the maximum measured range of each axis
for (uint8_t i=0; i<3; i++) {
_P118_axis[i][2][dev] = abs(_P118_axis[i][1][dev] - _P118_axis[i][0][dev]);
_P118_axis[i][0][dev] = _P118_axis[i][3][dev];
_P118_axis[i][1][dev] = _P118_axis[i][3][dev];
}
}
success = true;
break;
}
case PLUGIN_READ:
{
int devAddr = Settings.TaskDevicePluginConfig[event->TaskIndex][0];
byte dev = devAddr & 1;
int _P118_Function = Settings.TaskDevicePluginConfig[event->TaskIndex][1];
switch (_P118_Function)
{
// Function 0 is for movement detection
case 0:
{
// Check if all (enabled, so !=0) thresholds are exceeded, if one fails then thresexceed (thesholds exceeded) is reset to false;
boolean thresexceed = true;
byte count = 0; // Counter to check if not all thresholdvalues are set to 0 or disabled
for (byte i=0; i<3; i++)
{
// for each axis:
if (Settings.TaskDevicePluginConfig[event->TaskIndex][i + 2] != 0) { // not disabled, check threshold
if (_P118_axis[i][2][dev] < Settings.TaskDevicePluginConfig[event->TaskIndex][i + 2]) { thresexceed = false; }
} else { count++; } // If disabled count + 1
}
if (count == 3) { thresexceed = false; } // If we counted to three, all three axis are disabled.
// If all enabled thresholds are exceeded the increase the counter
if (thresexceed) { Settings.TaskDevicePluginConfigLong[event->TaskIndex][0]++; }
// And increase the window counter
Settings.TaskDevicePluginConfigLong[event->TaskIndex][1]++;
if (Settings.TaskDevicePluginConfigLong[event->TaskIndex][1] >= Settings.TaskDevicePluginConfig[event->TaskIndex][6]) {
// Detection window has passed.
Settings.TaskDevicePluginConfigLong[event->TaskIndex][1] = 0; // reset window counter
// Did we count more times exceeded then the minimum detection value?
if (Settings.TaskDevicePluginConfigLong[event->TaskIndex][0] >= Settings.TaskDevicePluginConfig[event->TaskIndex][5]) {
UserVar[event->BaseVarIndex] = 1; // x times threshold exceeded within window.
} else {
UserVar[event->BaseVarIndex] = 0; // reset because x times threshold within window not met.
}
// Check if UserVar changed so we do not overload homecontroller with the same readings
if (Settings.TaskDevicePluginConfigLong[event->TaskIndex][7] != UserVar[event->BaseVarIndex]) {
success = true; // Update switch status
Settings.TaskDevicePluginConfigLong[event->TaskIndex][7] = UserVar[event->BaseVarIndex];
} else {
success = false; // Do not update switch status
}
Settings.TaskDevicePluginConfigLong[event->TaskIndex][0] = 0; // reset threshold exceeded counter
}
// The default sensorType of the device is a single sensor value. But for detection movement we want it to be
// a switch so we change the sensortype here. Looks like a legal thing to do because _P001_Switch does it as well.
event->sensorType = SENSOR_TYPE_SWITCH;
break;
}
// All other functions are reading values. So extract xyz value and wanted type from function number:
default: // [1-3]: range-values, [4-6]: a-values, [7-9]: g-values
{
uint8_t reqaxis = (_P118_Function - 1) % 3; // xyz -> eg: function 5(ay) (5-1) % 3 = 1 (y)
uint8_t reqvar = ((_P118_Function - 1) / 3) + 2; // range, a, g -> eg: function 9(gz) ((9-1) / 3 = 2) + 2 = 4 (g)
UserVar[event->BaseVarIndex] = float(_P118_axis[reqaxis][reqvar][dev]);
success = true;
break;
}
}
break;
}
}
return success;
}
void _P118_trackMinMax(int16_t current, int16_t *min, int16_t *max)
// From nodemcu-laundry.ino by Nolan Gilley
{
if (current > *max)
{
*max = current;
}
else if (current < *min)
{
*min = current;
}
}
/** Get raw 6-axis motion sensor readings (accel/gyro).
* Retrieves all currently available motion sensor values.
* @param devAddr I2C slave device address
* @param ax 16-bit signed integer container for accelerometer X-axis value
* @param ay 16-bit signed integer container for accelerometer Y-axis value
* @param az 16-bit signed integer container for accelerometer Z-axis value
* @param gx 16-bit signed integer container for gyroscope X-axis value
* @param gy 16-bit signed integer container for gyroscope Y-axis value
* @param gz 16-bit signed integer container for gyroscope Z-axis value
*/
void _P118_getMotion6(uint8_t devAddr, int16_t* ax, int16_t* ay, int16_t* az, int16_t* gx, int16_t* gy, int16_t* gz) {
// From I2Cdev::readBytes and MPU6050::getMotion6, both by Jeff Rowberg
uint8_t buffer[14];
uint8_t count = 0;
Wire.beginTransmission(devAddr);
Wire.write(MPU6050_RA_ACCEL_XOUT_H);
Wire.endTransmission();
Wire.beginTransmission(devAddr);
Wire.requestFrom(devAddr, (uint8_t)14);
for (; Wire.available(); count++) {
buffer[count] = Wire.read();
}
*ax = (((int16_t)buffer[0]) << 8) | buffer[1];
*ay = (((int16_t)buffer[2]) << 8) | buffer[3];
*az = (((int16_t)buffer[4]) << 8) | buffer[5];
*gx = (((int16_t)buffer[8]) << 8) | buffer[9];
*gy = (((int16_t)buffer[10]) << 8) | buffer[11];
*gz = (((int16_t)buffer[12]) << 8) | buffer[13];
}
/** Write multiple bits in an 8-bit device register.
* @param devAddr I2C slave device address
* @param regAddr Register regAddr to write to
* @param bitStart First bit position to write (0-7)
* @param length Number of bits to write (not more than 8)
* @param data Right-aligned value to write
*/
void _P118_writeBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t data) {
// From I2Cdev::writeBits by Jeff Rowberg
// 010 value to write
// 76543210 bit numbers
// xxx args: bitStart=4, length=3
// 00011100 mask byte
// 10101111 original value (sample)
// 10100011 original & ~mask
// 10101011 masked | value
uint8_t b;
Wire.beginTransmission(devAddr);
Wire.write(regAddr);
Wire.endTransmission();
Wire.requestFrom(devAddr, uint8_t(1));
if (Wire.available()) {
b = Wire.read();
uint8_t mask = ((1 << length) - 1) << (bitStart - length + 1);
data <<= (bitStart - length + 1); // shift data into correct position
data &= mask; // zero all non-important bits in data
b &= ~(mask); // zero all important bits in existing byte
b |= data; // combine data with existing byte
Wire.beginTransmission(devAddr);
Wire.write(regAddr);
Wire.write(b);
Wire.endTransmission();
}
}
#endif
+292
View File
@@ -0,0 +1,292 @@
#ifdef PLUGIN_BUILD_DEV
//#######################################################################################################
//######################## Plugin 120: Thermocouple (MAX6675 / MAX31855) ################################
//#######################################################################################################
// Plugin Description
// This Plugin reads the data from Thermocouples. You have to use an Adapter Board with a
// MAX6675 or MAX31855 in order to read the values. Take a look at ebay to find such boards :-)
// You can only use ESP8266 boards which expose the SPI Interface. This Plugin uses only the Hardware
// SPI Interface - no software SPI at the moment.
// But neverless you need at least 3 Pins to use SPI. So using an very simple ESP-01 is no option - Sorry.
// The Wiring ist straight forward ...
//
// If you like to send suggestions feel free to send me an email : dominik@logview.info
// Have fun ... Dominik
// Wiring
// https://de.wikipedia.org/wiki/Serial_Peripheral_Interface
// You need an ESP8266 device with accessable SPI Pins. These are:
// Name Description GPIO NodeMCU Notes
// MOSI Master Output GPIO13 D7 Not used (No Data sending to MAX)
// MISO Master Input GPIO12 D6 Hardware SPI
// SCK Clock Output GPIO14 D5 Hardware SPI
// CS Chip Select GPIO15 D8 Hardware SPI (CS is configurable through the web interface)
// Thermocouple Infos
// http://www.bristolwatch.com/ele2/therc.htm
// Chips
// MAX6675 - Cold-Junction-Compensated K-Thermocouple-to-Digital Converter ( 0°C to +1024°C)
// https://cdn-shop.adafruit.com/datasheets/MAX6675.pdf (only
// MAX31855 - Cold-Junction Compensated Thermocouple-to-Digital Converter (-270°C to +1800°C)
// https://cdn-shop.adafruit.com/datasheets/MAX31855.pdf
#include <SPI.h>
#define PLUGIN_120
#define PLUGIN_ID_120 120
#define PLUGIN_NAME_120 "Temperature Thermocouple [DEVELOPMENT]"
#define PLUGIN_VALUENAME1_120 "Temperature C"
#define PLUGIN_VALUENAME2_120 "Temperature K"
uint8_t Plugin_120_SPI_CS_Pin = 15; // D8
bool Plugin_120_SensorAttached = true;
double Plugin_120_Celsius = 0.0;
boolean Plugin_120(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_120;
Device[deviceCount].Type = DEVICE_TYPE_SINGLE;
Device[deviceCount].VType = SENSOR_TYPE_DUAL; // 2 Messwerte
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = true;
Device[deviceCount].ValueCount = 2;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].TimerOption = true;
Device[deviceCount].GlobalSyncOption = true;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_120);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_120));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_120));
break;
}
case PLUGIN_INIT:
{
// Get CS Pin
// If no Pin is in Config we use 15 as default -> Hardware Chip Select on ESP8266
if (Settings.TaskDevicePin1[event->TaskIndex] != 0)
{
// Konvert the GPIO Pin to a Dogotal Puin Number first ...
Plugin_120_SPI_CS_Pin = Settings.TaskDevicePin1[event->TaskIndex];
}
// set the slaveSelectPin as an output:
pinMode(Plugin_120_SPI_CS_Pin, OUTPUT);
// initialize SPI:
SPI.setHwCs(false);
SPI.begin();
addLog(LOG_LEVEL_INFO, (char*)"P120 : SPI Init");
success = true;
break;
}
case PLUGIN_WEBFORM_LOAD:
{
string += F("<TR><TD>Info GPIO:<TD><b>1st GPIO</b> = CS (Usable GPIOs : 0, 2, 4, 5, 15)");
byte choice = Settings.TaskDevicePluginConfig[event->TaskIndex][0];
String options[2];
options[0] = F("MAX 6675");
options[1] = F("MAX 31855");
//options[2] = F("MAX 31865");
int optionValues[2];
optionValues[0] = 1;
optionValues[1] = 2;
//optionValues[2] = 3;
string += F("<TR><TD>Adapter IC:<TD><select name='plugin_120_maxtype'>");
for (byte x = 0; x < 2; x++)
{
string += F("<option value='");
string += optionValues[x];
string += "'";
if (choice == optionValues[x])
string += F(" selected");
string += ">";
string += options[x];
string += F("</option>");
}
string += F("</select>");
success = true;
break;
}
case PLUGIN_WEBFORM_SAVE:
{
String plugin1 = WebServer.arg("plugin_120_maxtype");
Settings.TaskDevicePluginConfig[event->TaskIndex][0] = plugin1.toInt();
success = true;
break;
}
case PLUGIN_READ:
{
// Get the MAX Type (6675 / 31855)
// TBD ... Auswertung je nach Chip !!!
byte MaxType = Settings.TaskDevicePluginConfig[event->TaskIndex][0];
// Get CS Pin
// Konvert the GPIO Pin to a Dogotal Puin Number first ...
Plugin_120_SPI_CS_Pin = Settings.TaskDevicePin1[event->TaskIndex];
switch (MaxType) {
case 1: // MAX6675
Plugin_120_Celsius = readMax6675();
break;
case 2: // MAX31855
Plugin_120_Celsius = readMax31855();
break;
case 3: // MAX31865 (not implemented yet)
//do something when var equals 2
break;
}
if (Plugin_120_Celsius != NAN)
{
UserVar[event->BaseVarIndex] = Plugin_120_Celsius;
UserVar[event->BaseVarIndex + 1] = CelsiusToFahrenheit(Plugin_120_Celsius);
String log = F("P120 : C : ");
log += UserVar[event->BaseVarIndex];
log += F(" - F : ");
log += UserVar[event->BaseVarIndex + 1];
addLog(LOG_LEVEL_INFO, log);
success = true;
}
else
{
UserVar[event->BaseVarIndex] = NAN;
UserVar[event->BaseVarIndex + 1] = NAN;
String log = F("P120 : No Sensor attached !");
addLog(LOG_LEVEL_INFO, log);
success = false;
}
break;
}
}
return success;
}
double readMax6675()
{
uint16_t rawvalue = 0;
// take the SS pin low to select the chip:
digitalWrite(Plugin_120_SPI_CS_Pin, LOW);
// String log = F("P120 : CS Pin : ");
// log += Plugin_120_SPI_CS_Pin;
// addLog(LOG_LEVEL_INFO, log);
// "transfer" 0x0 and read the Data from the Chip
rawvalue = SPI.transfer16(0x0);
// take the SS pin high to de-select the chip:
digitalWrite(Plugin_120_SPI_CS_Pin, HIGH);
String log = F("P120 : MAX6675 : RAW - BIN:");
log += String(rawvalue, BIN);
log += " HEX:";
log += String(rawvalue, HEX);
log += " DEC:";
log += String(rawvalue);
addLog(LOG_LEVEL_DEBUG, log);
// Open Thermocouple
// Bit D2 is normally low and goes high if the thermocouple input is open. In order to allow the operation of the
// open thermocouple detector, T- must be grounded. Make the ground connection as close to the GND pin
// as possible.
Plugin_120_SensorAttached = !(rawvalue & 0x0004);
if (Plugin_120_SensorAttached)
{
// Shift RAW value 3 Bits to the right to get the data
rawvalue >>= 3;
// Calculate Celsius
return rawvalue * 0.25;
}
else
{
return NAN;
}
}
double readMax31855()
{
uint32_t rawvalue = 0;
// take the SS pin low to select the chip:
digitalWrite(Plugin_120_SPI_CS_Pin, LOW);
// "transfer" 0x0 and read the MSB Data from the Chip
rawvalue = SPI.transfer16(0x0);
// Shift MSB 16 Bits to the left
rawvalue <<= 16;
// "transfer" 0x0 and read the LSB Data from the Chip
rawvalue |= SPI.transfer16(0x0);
// take the SS pin high to de-select the chip:
digitalWrite(Plugin_120_SPI_CS_Pin, HIGH);
String log = F("P120 : MAX31855 : RAW - BIN:");
log += String(rawvalue, BIN);
log += " HEX:";
log += String(rawvalue, HEX);
log += " DEC:";
log += String(rawvalue);
addLog(LOG_LEVEL_DEBUG, log);
// D16 - This bit reads at 1 when any of the SCV, SCG, or OC faults are active. Default value is 0.
Plugin_120_SensorAttached = !(rawvalue & 0x00010000);
if (Plugin_120_SensorAttached)
{
// Data is D[31:18]
// Shift RAW value 18 Bits to the right to get the data
rawvalue >>= 18;
// Check for negative Values
// +25.00 0000 0001 1001 00
// 0.00 0000 0000 0000 00
// -0.25 1111 1111 1111 11
// -1.00 1111 1111 1111 00
// -250.00 1111 0000 0110 00
if (rawvalue & 0x2000) // Bit 31=1 -> neg Values
{
// Negate all Bits
rawvalue = ~rawvalue;
// Add 1 and make negative
rawvalue = (rawvalue + 1) * -1;
}
// Calculate Celsius
return rawvalue * 0.25;
}
else
{
return NAN;
}
}
// Convert Celsius to Fahrenheit
double CelsiusToFahrenheit(double celsius) {
return celsius * 9.0 / 5.0 + 32;
}
#endif
+111
View File
@@ -0,0 +1,111 @@
#ifdef PLUGIN_BUILD_DEV
/*
This plug in is written by Dmitry (rel22 ___ inbox.ru)
Plugin is based upon SenseAir plugin by Daniel Tedenljung info__AT__tedenljungconsulting.com
This plugin reads the CO2 value from MH-Z19 NDIR Sensor
DevicePin1 - is RX for ESP
DevicePin2 - is TX for ESP
*/
#define PLUGIN_149
#define PLUGIN_ID_149 149
#define PLUGIN_NAME_149 "NDIR CO2 Sensor MH-Z19 [DEVELOPMENT]"
#define PLUGIN_VALUENAME1_149 "PPM"
boolean Plugin_149_init = false;
#include <SoftwareSerial.h>
SoftwareSerial *Plugin_149_S8;
// 9-bytes CMD PPM read command
byte mhzCmd[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79};
byte mhzResp[9]; // 9 bytes bytes response
boolean Plugin_149(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_149;
Device[deviceCount].Type = DEVICE_TYPE_DUAL;
Device[deviceCount].VType = SENSOR_TYPE_SINGLE;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = true;
Device[deviceCount].ValueCount = 1;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].TimerOption = true;
Device[deviceCount].GlobalSyncOption = true;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_149);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_149));
break;
}
case PLUGIN_INIT:
{
Plugin_149_init = true;
Plugin_149_S8 = new SoftwareSerial(Settings.TaskDevicePin1[event->TaskIndex], Settings.TaskDevicePin2[event->TaskIndex]);
success = true;
break;
}
case PLUGIN_READ:
{
if (Plugin_149_init)
{
Plugin_149_S8->write(mhzCmd, 9);
memset(mhzResp, 0, 9);
Plugin_149_S8->readBytes(mhzResp, 9);
int i;
unsigned int ppm = 0;
byte crc = 0;
for (i = 1; i < 8; i++) crc+=mhzResp[i];
crc = 255 - crc;
crc++;
if ( !(mhzResp[0] == 0xFF && mhzResp[1] == 0x86 && mhzResp[8] == crc) ) {
String log = F("MHZ19: CRC error: ");
log += String(crc); log += " / "; log += String(mhzResp[8]);
addLog(LOG_LEVEL_ERROR, log);
success = false;
break;
} else {
unsigned int mhzRespHigh = (unsigned int) mhzResp[2];
unsigned int mhzRespLow = (unsigned int) mhzResp[3];
ppm = (256*mhzRespHigh) + mhzRespLow;
}
UserVar[event->BaseVarIndex] = (float)ppm;
String log = F("MHZ19: PPM value: ");
log += ppm;
addLog(LOG_LEVEL_INFO, log);
success = true;
break;
}
break;
}
}
return success;
}
#endif
+525
View File
@@ -0,0 +1,525 @@
//#######################################################################################################
//#################################### Plugin 186: Ventus W266 [Testing] ################################
//#######################################################################################################
// Purpose: Sniff the data received by the Ventus W266 display unit and send it to Domoticz
// Status : "Initial release"
// This plugin can be used on a esp8266 connected to the recievermodule inside a Ventus W266 or
// Renkforce W205GU display unit. The plugin then reads the data send from the remote sensorunit and
// can send the data to Domoticz or an other controller. The plugin does not read the data of the
// sensors in the display unit such as indoor temperature and airpressure.
// The displaynit has a RFM31 reciever but the pinout of this receiver is identical to the pinout
// of a RFM01 reciever. We need to connect 4 wires to read the SPI exchange between the host and the
// reciever. Because we need to read both MOSI and MISO signals the hardware SPI is unusable and we
// use bitbanging to achive the same result.
// Connect pins 5-8 from the "RFM31BJ-S1" to the pins you defined in de webgui.
// Pinout of the RFM31: 5-MOSI, 6-SCLK, 7-nSEL (active low CS), 8-MISO, 14-GND.
// In my original setup these were connected to 4, 12, 14 and 5.
// Try to avoid GPIO 15 as nSEL line because if the line is high during a reboot, and it is mostly high,
// the boot will fail!
// The Ventus W266 remote has the following sensor outputs:
// Humidity, Temperature, Wind direction, Wind avarage, Wind gust, Rainfall, UV and Lightning. That is
// more than te maximum of 4 values per device for espeasy. The plugins functionality is therefore
// devided per sensorgroup. To read all the sensor data you need to run 6 instances of the plugin.
// The plugin can (and should) be used more then one time, however only one plugin instance can be
// the main plugin. The plugin function can be selected by a dropdown and only the main plugin has
// the ability to set the I/O lines.
// The plugin uses two buffers, ine for the ISR routines and one for the other plugin instances.
// Why plugin nr 186? Well 266 is not possible, 206 (id on the back of the unit) is nit within the
// playground range and the W186 is an additinal external thermometer for the Ventus W266.
// The Ventus W266 is also known as the Renkforce W205GU.
// TaskDevicePluginConfig[x][0] = Instance function
// TaskDevicePluginConfig[x][1] = MOSI (eg. GPOI pin 4)
// TaskDevicePluginConfig[x][2] = SCLK (eg. GPIO pin 12)
// TaskDevicePluginConfig[x][3] = nSEL (eg. GPIO pin 14)
// TaskDevicePluginConfig[x][4] = MISO (eg. GPIO pin 5)
// If you use GPIO 4&5, please disable the I2C/SPI option in the hardware tab.
// The buffers contain the following data:
// hhIDhh 1A tlth ?b tlth wb alahglgh rlrh?? uv ld?? lllhcrc
// 7F9827 1A B100 00 B100 06 00000000 1E0000 00 3F8A 2A0017
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
// hh=header > This is actualy not part of the real payload but a wanted artifact of the sniffing method.
// hh=humidity (bcd) > Humidity is bcd encoded
// tlth=temperature-low/temphigh (*10) > Temperature is stored as a 16bit integer holding the temperature in Celcius * 10 but low byte first.
// b=battery (1=low) > This byte is 00 but 01 when the battery of the transmitter runs low
// wb=bearing (cw0-15) > The windbearing in 16 clockwise steps (0 = north, 4 = east, 8 = south and C = west)
// alah=windaverage-low/high (m/s/2) > A 16 bit int holding the wind avarage in m/s * 2, low byte first
// rlrh=rainfall-low/high (1/4mm) > A 16 bit int holding the wind gust in m/s * 2, low byte first
// uv=uvindex (*10) > The UV value * 10
// ld=lightningstorm-distance (km, 3F is max) > The distance to the stormfront in km
// lllh=strikecount-low/high (#) > A 16 bit integer holding the number of detected lightning strikes, low byte first
// crc > poly 0x31, init 0xff, revin&revout, xorout 0x00. Like Maxim 1-wire but with a 0xff initvalue. Crc is calculated over bytes 1-22
//
// Events:
// None
// Commands:
// None
// Current state / limitations:
// 1.0 Initial release. All values are always visible although sometimes
// only one is really used with domoticz.
// Needs work on a sliding window for the lightning detection.
// Exploits the fact that event->sensorType is not reset after PLUGIN_READ.
// This plugin is based on the work of the Plugin 199: RF KaKu receiver/sender and Plugin 026: Analog.
// CRC calculation is based on the works by Paul Stoffregen from the 1-Wire arduino library. Special
// thanks to Greg Cook and the team behind reveng.sourceforge.net.
#ifdef PLUGIN_BUILD_TESTING
#define PLUGIN_186_DEBUG true // Shows recieved frames and crc in log@INFO
#define PLUGIN_186 // Mandatory framework constants
#define PLUGIN_ID_186 186
#define PLUGIN_NAME_186 "Ventus W266 [TESTING]"
#define PLUGIN_VALUENAME1_186 ""
#define PLUGIN_VALUENAME2_186 ""
#define PLUGIN_VALUENAME3_186 ""
#define Plugin_186_MagicByte 0x7F // When we read this byte on MOSI, switch to MISO
#define Plugin_186_RAW_BUFFER_SIZE 24 // Payload is 23 bytes, added space for header
#define Plugin_186_Payload 23
int8_t Plugin_186_MOSIpin = -1; // GPIO pins
int8_t Plugin_186_SCLKpin = -1;
int8_t Plugin_186_nSELpin = -1;
int8_t Plugin_186_MISOpin = -1;
// Vars used in data collection:
byte Plugin_186_ISR_Buffer[Plugin_186_RAW_BUFFER_SIZE]; // Buffer used in ISR routine
//Test data: volatile byte Plugin_186_databuffer[] = {0x7F, 0x98, 0x33, 0x1A, 0xB0, 0x00, 0x00, 0xB0, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x3F, 0x8A, 0x25, 0x00, 0x49, 0x00}; // Buffer used by other instances
byte Plugin_186_databuffer[Plugin_186_RAW_BUFFER_SIZE]; // Buffer used by other instances
boolean Plugin_186_RecieveActive = false; // Active session in progress
boolean Plugin_186_MasterSlave = false; // Which pin o read? false=MOSI, true=MISO
boolean Plugin_186_newData = false; // "Valid" data ready, please process
byte Plugin_186_bitpointer; // Pointer for recieved bit
byte Plugin_186_bytepointer; // Pointe for ISR recieve buffer
byte Plugin_186_recievedData; // Byte to store recieved bits
// Vars used for interpreting the data:
volatile unsigned long Plugin_186_lastrainctr; // Keep track of wdcounter (1/2 min tick)
volatile int Plugin_186_lastraincount; // Last rain count
volatile float Plugin_186_rainmmph = 0;
volatile unsigned long Plugin_186_laststrikectr; // Keep track of wdcounter (1/2 min tick)
volatile unsigned int Plugin_186_laststrikecount; // Last number of strikes
volatile int Plugin_186_strikesph = 0;
void Plugin_186_ISR_nSEL() ICACHE_RAM_ATTR; // Interrupt routines
void Plugin_186_ISR_SCLK() ICACHE_RAM_ATTR;
boolean Plugin_186(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_186;
Device[deviceCount].Type = DEVICE_TYPE_DUMMY; // Nothing else really fit the bill ...
Device[deviceCount].VType = SENSOR_TYPE_DUAL; // New type, see ESPEasy.ino
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = true;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].ValueCount = 3;
break;
}
case PLUGIN_WEBFORM_LOAD:
{
byte choice = Settings.TaskDevicePluginConfig[event->TaskIndex][0];
byte nrchoices = 9;
String options[nrchoices];
options[0] = F("Main + Temp/Hygro");
options[1] = F("Wind");
options[2] = F("Rain");
options[3] = F("UV");
options[4] = F("Lightning strikes");
options[5] = F("Lightning distance");
options[6] = F("Unknown 1, byte 6");
options[7] = F("Unknown 2, byte 16");
options[8] = F("Unknown 3, byte 19");
int optionValues[nrchoices];
for (byte x = 0; x < nrchoices; x++) {
optionValues[x] = x;
}
string += F("<TR><TD>Plugin function:<TD><select name='plugin_186'>");
for (byte x = 0; x < nrchoices; x++) {
string += F("<option value='");
string += optionValues[x];
string += "'";
if (choice == optionValues[x])
string += F(" selected");
string += ">";
string += options[x];
string += F("</option>");
}
string += F("</select>");
if (choice==0) {
string += F("<TR><TD>1st GPIO (5-MOSI):<TD>");
addPinSelect(false, string, "taskdevicepin1", Settings.TaskDevicePluginConfig[event->TaskIndex][1]);
string += F("<TR><TD>2nd GPIO (6-SCLK):<TD>");
addPinSelect(false, string, "taskdevicepin2", Settings.TaskDevicePluginConfig[event->TaskIndex][2]);
string += F("<TR><TD>3rd GPIO (7-nSEL):<TD>");
addPinSelect(false, string, "taskdevicepin3", Settings.TaskDevicePluginConfig[event->TaskIndex][3]);
string += F("<TR><TD>4th GPIO (8-MISO):<TD>");
addPinSelect(false, string, "taskdeviceport", Settings.TaskDevicePluginConfig[event->TaskIndex][4]);
}
switch (choice)
{
case (0):
{
string += F("<TR><TD><B>Be sure you only have 1 main plugin!</B></TD>");
string += F("<TR><TD>Value 1: Temperature, 1 decimal<BR>Value 2: Humidity, 0 decimals");
string += F("<BR>Value 3: not used</TD>");
break;
}
case (1):
{
string += F("<TR><TD>Value 1: Direction, 0 decimals<BR>");
string += F("Value 2: Average, 1 decimal<Br>Value 3: Gust, 1 decimal</TD>");
break;
}
case (2):
{
string += F("<TR><TD>Value 1: Rain in mm per hour<BR>Value 2: Total rain in mm");
string += F("<BR>Value 3: not used</TD>");
break;
}
case (3):
{
string += F("<TR><TD>Value 1: UV, 1 decimal");
string += F("<BR>Values 2, 3</TD>");
break;
}
case (4):
{
string += F("<TR><TD>Value 1: Strikes this hour, 0 decimals");
string += F("<BR>Values 2, 3: not used</TD>");
break;
}
case (5):
{
string += F("<TR><TD>Value 1: Distance in km, 0 decimals");
string += F("<BR>Values 2, 3: not used</TD>");
break;
}
case (6):
{
string += F("<TR><TD>Value 1: Batterybyte, 0 decimals");
string += F("<BR>Values 2, 3: not used</TD>");
break;
}
case (7):
{
string += F("<TR><TD>Value 1: Last rainbyte, 0 decimals");
string += F("<BR>Values 2, 3: not used</TD>");
break;
}
case (8):
{
string += F("<TR><TD>Value 1: Last lightningbyte, 0 decimals");
string += F("<BR>Values 2, 3: not used</TD>");
break;
}
}
success = true;
break;
}
case PLUGIN_WEBFORM_SAVE:
{
String plugin1 = WebServer.arg("plugin_186");
Settings.TaskDevicePluginConfig[event->TaskIndex][0] = plugin1.toInt();
if (plugin1.toInt()==0) {
String plugin2 = WebServer.arg("taskdevicepin1");
Settings.TaskDevicePluginConfig[event->TaskIndex][1] = plugin2.toInt();
String plugin3 = WebServer.arg("taskdevicepin2");
Settings.TaskDevicePluginConfig[event->TaskIndex][2] = plugin3.toInt();
String plugin4 = WebServer.arg("taskdevicepin3");
Settings.TaskDevicePluginConfig[event->TaskIndex][3] = plugin4.toInt();
String plugin5 = WebServer.arg("taskdeviceport");
Settings.TaskDevicePluginConfig[event->TaskIndex][4] = plugin5.toInt();
}
success = true;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_186);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_186));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_186));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_186));
break;
}
case PLUGIN_INIT:
{
byte choice = Settings.TaskDevicePluginConfig[event->TaskIndex][0];
switch (choice)
{
case (0):
{
Plugin_186_MOSIpin = Settings.TaskDevicePluginConfig[event->TaskIndex][1];
Plugin_186_SCLKpin = Settings.TaskDevicePluginConfig[event->TaskIndex][2];
Plugin_186_nSELpin = Settings.TaskDevicePluginConfig[event->TaskIndex][3];
Plugin_186_MISOpin = Settings.TaskDevicePluginConfig[event->TaskIndex][4];
int8_t total = Plugin_186_MOSIpin + Plugin_186_SCLKpin + Plugin_186_nSELpin + Plugin_186_MISOpin;
if (total > 6) { // All pins configured?
pinMode(Plugin_186_MOSIpin, INPUT);
pinMode(Plugin_186_SCLKpin, INPUT);
pinMode(Plugin_186_nSELpin, INPUT);
pinMode(Plugin_186_MISOpin, INPUT);
Plugin_186_databuffer[0] = 0; // buffer is "empty"
Plugin_186_lastrainctr = 0;
Plugin_186_lastraincount = -1;
Plugin_186_laststrikectr = 0;
Plugin_186_laststrikecount = -1;
attachInterrupt(Plugin_186_SCLKpin, Plugin_186_ISR_SCLK, RISING);
attachInterrupt(Plugin_186_nSELpin, Plugin_186_ISR_nSEL, CHANGE);
}
break;
}
}
break;
}
case PLUGIN_TEN_PER_SECOND:
{
if (Settings.TaskDevicePluginConfig[event->TaskIndex][0] == 0) {
if (Plugin_186_newData) {
uint8_t crc = 0xff; // init = 0xff
char data; // CRC = MAXIM with modified init: poly 0x31, init 0xff, refin 1; refout 1, xorout 0x00
// Copy recieved data to buffer and check CRC
Plugin_186_databuffer[0] = Plugin_186_ISR_Buffer[0];
for (int i = 1; i < Plugin_186_bytepointer; i++) {
data = Plugin_186_ISR_Buffer[i];
Plugin_186_databuffer[i] = data;
for (int j = 0; j < 8; j++) // crc routine from Jim Studt
{ // Onewire library
uint8_t mix = (crc ^ data) & 0x01;
crc >>= 1;
if (mix) crc ^= 0x8C;
data >>= 1;
}
}
Plugin_186_MasterSlave = false;
Plugin_186_newData = false;
if (PLUGIN_186_DEBUG) {
String log = "Ventus W266 Rcvd(";
log += hour();
log += ":";
if (minute() < 10) { log += "0"; }
log += minute();
log += ":";
if (tm.Second < 10) { log += "0"; }
log += tm.Second;
log += ") ";
for (int i = 0; i < Plugin_186_Payload; i++) {
if ((i==2)||(i==3)||(i==4)||(i==9)||(i==10)||(i==14)||(i==17)||(i==18)||(i==20)) {
log += ":";
}
char myHex = (Plugin_186_databuffer[i] >> 4) + 0x30;
if (myHex > 0x39) { myHex += 7; }
log += myHex;
myHex = (Plugin_186_databuffer[i] & 0x0f) + 0x30;
if (myHex > 0x39) { myHex += 7; }
log += myHex;
}
log += " > ";
char myHex = (crc >> 4) + 0x30;
if (myHex > 0x39) { myHex += 7; }
log += myHex;
myHex = (crc & 0x0f) + 0x30;
if (myHex > 0x39) { myHex += 7; }
log += myHex;
addLog(LOG_LEVEL_INFO, log);
}
if (crc != 00)
{
Plugin_186_databuffer[0] = 0; // Not MagicByte, so not valid.
}
}
}
success = true;
break;
}
case PLUGIN_READ:
{
if (Plugin_186_databuffer[0] == Plugin_186_MagicByte) // buffer[0] should be the MagicByte if valid
{
UserVar[event->BaseVarIndex + 1] = 0;
byte choice = Settings.TaskDevicePluginConfig[event->TaskIndex][0]; // Which instance?
switch (choice)
{
case (0):
{
int myTemp = int((Plugin_186_databuffer[5] * 256) + Plugin_186_databuffer[4]);
if (myTemp > 0x8000) { myTemp |= 0xffff0000; } // int @ esp8266 = 32 bits!
float temperature = float(myTemp) / 10.0; // Temperature
byte myHum = (Plugin_186_databuffer[2] >> 4) * 10 + (Plugin_186_databuffer[2] & 0x0f);
float humidity = float(myHum);
UserVar[event->BaseVarIndex] = temperature;
UserVar[event->BaseVarIndex + 1] = humidity;
event->sensorType = SENSOR_TYPE_TEMP_HUM;
break;
}
case (1):
{
float average = float((Plugin_186_databuffer[11]) * 256 + Plugin_186_databuffer[10]); // Wind speed average in 10 * m/s
float gust = float((Plugin_186_databuffer[13]) * 256 + Plugin_186_databuffer[12]); // Wind speed gust in 10 * m/s
float bearing = float(Plugin_186_databuffer[9] & 0x0f) * 22.5; // Wind bearing (0-359)
UserVar[event->BaseVarIndex] = bearing; // degrees
UserVar[event->BaseVarIndex + 1] = average;
UserVar[event->BaseVarIndex + 2] = gust;
event->sensorType = SENSOR_TYPE_WIND;
break;
}
case (2):
{
float raincnt = float(((Plugin_186_databuffer[15]) * 256 + Plugin_186_databuffer[14]) / 4);
int rainnow = int(raincnt);
if (wdcounter < Plugin_186_lastrainctr) { Plugin_186_lastrainctr = wdcounter; }
if (Plugin_186_lastrainctr > (wdcounter + 10)) // 5 min interval
{
Plugin_186_lastrainctr = wdcounter;
if (rainnow > Plugin_186_lastraincount)
{ // per 5 min * 12 = per hour
Plugin_186_rainmmph = float(rainnow - Plugin_186_lastraincount) * 12;
Plugin_186_lastraincount = rainnow;
} else {
Plugin_186_rainmmph = 0;
}
}
UserVar[event->BaseVarIndex] = Plugin_186_rainmmph;
UserVar[event->BaseVarIndex + 1] = raincnt;
break;
}
case (3):
{
float uvindex = float((Plugin_186_databuffer[17]) / 10);
UserVar[event->BaseVarIndex] = uvindex;
break;
}
case (4):
{
int strikes = 0;
int strikesnow = int((Plugin_186_databuffer[21]) * 256 + Plugin_186_databuffer[20]);
if (wdcounter < Plugin_186_laststrikectr) { Plugin_186_laststrikectr = wdcounter; }
if (Plugin_186_laststrikectr > (wdcounter + 10)) // 5 min interval
{
Plugin_186_laststrikectr = wdcounter;
if (strikesnow > Plugin_186_laststrikecount)
{
Plugin_186_strikesph = strikesnow - Plugin_186_laststrikecount;
Plugin_186_laststrikecount = strikesnow;
} else {
Plugin_186_strikesph = 0;
}
}
UserVar[event->BaseVarIndex] = float(Plugin_186_strikesph);
break;
}
case (5):
{
float distance = float(-1);
if (Plugin_186_databuffer[18] != 0x3F )
{
distance = float(Plugin_186_databuffer[18]);
}
UserVar[event->BaseVarIndex] = distance;
break;
}
case (6):
{
UserVar[event->BaseVarIndex] = float(Plugin_186_databuffer[6]);
break;
}
case (7):
{
UserVar[event->BaseVarIndex] = float(Plugin_186_databuffer[16]);
break;
}
case (8):
{
UserVar[event->BaseVarIndex] = float(Plugin_186_databuffer[19]);
break;
}
} // switch
success = true;
} else {
success = false;
}
break;
} // case READ
} // switch
return success;
}
void Plugin_186_ISR_nSEL() // Interrupt on nSEL change
{
if (digitalRead(Plugin_186_nSELpin)) {
Plugin_186_RecieveActive = false; // nSEL high? Recieve done.
if (Plugin_186_MasterSlave) { // If MISO not active, no data recieved
if (Plugin_186_bytepointer == Plugin_186_Payload) { // If not 23 then bad datapacket
Plugin_186_newData = true; // We have new data!
}
}
} else { // nSEL low? Start recieve
if (!Plugin_186_newData) { // Only accept new data if the old is processed
Plugin_186_bitpointer = 7; // reset pointer (MSB first)
Plugin_186_bytepointer = 0; // reset pointers & flags
Plugin_186_MasterSlave = false;
Plugin_186_RecieveActive = true; // We are now recieving data
}
}
}
void Plugin_186_ISR_SCLK() // Interrupt on SCLK rising
{
if (Plugin_186_RecieveActive) { // Are we recieving or glitch?
if (Plugin_186_MasterSlave) { // Read MISO or MOSI?
bitWrite(Plugin_186_recievedData, Plugin_186_bitpointer, digitalRead(Plugin_186_MISOpin));
} else {
bitWrite(Plugin_186_recievedData, Plugin_186_bitpointer, digitalRead(Plugin_186_MOSIpin));
}
if (Plugin_186_bitpointer == 0) { // 8 bits done?
Plugin_186_bitpointer = 7;
if (Plugin_186_recievedData==Plugin_186_MagicByte) { // Switch data pins?
Plugin_186_MasterSlave = true;
}
Plugin_186_ISR_Buffer[Plugin_186_bytepointer] = Plugin_186_recievedData;
Plugin_186_bytepointer++; // TReady for the next byte ...
if (Plugin_186_bytepointer > Plugin_186_RAW_BUFFER_SIZE) {
Plugin_186_RecieveActive = false; // We don't want a bufferoverflow, so abort
Plugin_186_MasterSlave = false;
}
} else {
Plugin_186_bitpointer--; // Not yet done with all bits ...
}
}
}
#endif
+584
View File
@@ -0,0 +1,584 @@
#ifdef PLUGIN_BUILD_DEV
//#######################################################################################################
//#################################### Plugin 199: RF KaKu receiver/sender ##############################
//#######################################################################################################
// Purpose: Control Klik-Aan-Klik-Uit RF 433MHz devices directly from ESP Easy (receive and send)
// Status : "Proof of concept"
// Connect the RF Receiver data pin to the first pin selected in the webgui
// Connect the RF Transmitter data pin to the second pin selected in the webgui
// Events:
// newKaku_<address>#<Channel>=<state> (0=off, 1-15=dimvalue, 16=on)
// Kaku_<address>#<Channel>=<state> (0=off, 1=on)
// HE300EU_<address>#<Channel>=<state> (0=off, 1=on)
// Commands:
// newKakuSend <address>, <Channel>, <state/dim>
// KakuSend <address>, <Channel>, <state>
// This is a Work in Progress mini project!
// It has limited use because in most cases, your fancy Home Automation controller can handle 433MHz devices quite well using RFLink.
// It was implemented because in some cases i would like to have local "Klik-Aan-Klik-Uit" support using a standalone ESP Easy.
// (Just because i own quite a lot of these Kaku devices)
// Current state / limitations:
// Implemented send and receive support for KaKu with automatic code (no code wheel)
// Implemented send and receive support for old KaKu unit's with code wheels
// Implemented receive support for HomeEasy HE300EU remotes
// RF Sender and RF receiver each need their own antenna! (as opposed to using a transceiver)
#define MIN_PULSE_LENGTH 100 // Too short pulses are considered to be noise...
#define SIGNAL_TIMEOUT 5 // gap between transmissions
#define MIN_RAW_PULSES 32 // Minimum number of pulses to be received, otherwise considered to be noise...
#define RAW_BUFFER_SIZE 256
#define RAWSIGNAL_MULTIPLY 25
void RF_ISR() ICACHE_RAM_ATTR;
// We need our own rawsignal buffer here.
// During plugin rawsignal checks, the IRQ routine will alPlugin_199_ready be working on the next signal burst...
volatile byte Plugin_199_RFBuffer[RAW_BUFFER_SIZE];
volatile boolean Plugin_199_ready = false;
volatile byte Plugin_199_pulses[RAW_BUFFER_SIZE + 2];
volatile int Plugin_199_number;
unsigned long Plugin_199_codeHash;
unsigned long Plugin_199_lastTime;
int8_t Plugin_199_RXpin = -1;
int8_t Plugin_199_TXpin = -1;
#define PLUGIN_199
#define PLUGIN_ID_199 199
#define PLUGIN_NAME_199 "RF Receiver/Sender [DEVELOPMENT]"
#define PLUGIN_VALUENAME1_199 "Address"
#define PLUGIN_VALUENAME2_199 "Channel"
#define PLUGIN_VALUENAME3_199 "State"
boolean Plugin_199(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_199;
Device[deviceCount].Type = DEVICE_TYPE_DUAL;
Device[deviceCount].VType = SENSOR_TYPE_DUAL;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = false;
Device[deviceCount].ValueCount = 3;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_199);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_199));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_199));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_199));
break;
}
case PLUGIN_INIT:
{
if (Settings.TaskDevicePin1[event->TaskIndex] != -1)
{
Plugin_199_RXpin = Settings.TaskDevicePin1[event->TaskIndex];
pinMode(Plugin_199_RXpin, INPUT_PULLUP);
attachInterrupt(Plugin_199_RXpin, RF_ISR, CHANGE);
success = true;
}
if (Settings.TaskDevicePin1[event->TaskIndex] != -1)
{
Plugin_199_TXpin = Settings.TaskDevicePin2[event->TaskIndex];
pinMode(Plugin_199_TXpin, OUTPUT);
}
break;
}
case PLUGIN_TEN_PER_SECOND:
{
if (Plugin_199_ready)
{
if (decodeNewKaku(event->BaseVarIndex));
else if (decodeKaku());
else if (decodeHE300EU());
Plugin_199_ready = false;
}
break;
}
case PLUGIN_WRITE:
{
String command = parseString(string, 1);
if (command == F("newkakusend"))
{
success = true;
sendNewKaku(event->Par1, event->Par2, event->Par3);
}
if (command == F("kakusend"))
{
success = true;
sendKaku(event->Par1, event->Par2, event->Par3);
}
break;
}
}
return success;
}
//********************************************************************************
// Interrupt handler for RF messages
//********************************************************************************
void RF_ISR()
{
static unsigned int counter = 0;
static unsigned long TimeStamp = 0;
unsigned long TimeElapsed = 0;
TimeElapsed = micros() - TimeStamp;
TimeStamp = micros();
if (TimeElapsed > MIN_PULSE_LENGTH && counter < RAW_BUFFER_SIZE)
{
counter++;
Plugin_199_RFBuffer[counter] = TimeElapsed / 25;
}
else
counter = 0;
if (TimeElapsed > (SIGNAL_TIMEOUT * 1000) )
{
if (counter > MIN_RAW_PULSES)
{
Plugin_199_number = counter;
// copy IRQ RF working buffer to RawSignal struct
for (unsigned int x = 0; x <= counter; x++)
Plugin_199_pulses[x] = Plugin_199_RFBuffer[x];
Plugin_199_ready = true;
counter = 0;
}
else
counter = 0;
}
if (counter >= RAW_BUFFER_SIZE)
counter = 0;
}
//********************************************************************************
// Transmit pulses using pulse array
//********************************************************************************
void RawSendRF(void)
{
if (Plugin_199_RXpin != -1)
detachInterrupt(Plugin_199_RXpin);
int x;
//digitalWrite(PIN_RF_RX_VCC,LOW); // Turn off RF receiver
//digitalWrite(PIN_RF_TX_VCC,HIGH); // Turn on RF sender
delay(5); // small delay between switching send/receive
Plugin_199_pulses[Plugin_199_number] = 1; // force last duration as 1 msec
for (byte y = 0; y < 7; y++) // repeats RF code
{
x = 1;
noInterrupts();
while (x < Plugin_199_number)
{
digitalWrite(Plugin_199_TXpin, HIGH);
delayMicroseconds(Plugin_199_pulses[x++] * 25 - 5);
digitalWrite(Plugin_199_TXpin, LOW);
delayMicroseconds(Plugin_199_pulses[x++] * 25 - 7);
}
interrupts();
delay(20);// Delay must run outside interrupt blocked code.
}
delay(5);
//digitalWrite(PIN_RF_TX_VCC,LOW); // turn off RF sender
//digitalWrite(PIN_RF_RX_VCC,HIGH); // turn on RF receiver
if (Plugin_199_RXpin != -1)
attachInterrupt(Plugin_199_RXpin, RF_ISR, CHANGE);
}
//********************************************************************************
// Decode NewKaku protocol (without code wheel)
//********************************************************************************
#define NewKAKU_RawSignalLength 132
#define NewKAKUdim_RawSignalLength 148
#define NewKAKU_1T 275 // us
#define NewKAKU_mT 650 // us
#define NewKAKU_4T 1100 // us
#define NewKAKU_8T 2200 // us
boolean decodeNewKaku(byte BaseVarIndex)
{
boolean success = false;
byte Par1 = 0;
unsigned long Par2 = 0;
unsigned long bitstream = 0L;
unsigned long address = 0L;
byte channel = 0;
byte command = 0;
boolean Bit;
int i;
int P0, P1, P2, P3;
Par1 = 0;
if (Plugin_199_number == NewKAKU_RawSignalLength || Plugin_199_number == NewKAKUdim_RawSignalLength)
{
i = 3; // Plugin_199_pulses[3] is de eerste van een T,xT,T,xT combinatie
do
{
P0 = Plugin_199_pulses[i] * 25;
P1 = Plugin_199_pulses[i + 1] * 25;
P2 = Plugin_199_pulses[i + 2] * 25;
P3 = Plugin_199_pulses[i + 3] * 25;
if (P0 < NewKAKU_mT && P1 < NewKAKU_mT && P2 < NewKAKU_mT && P3 > NewKAKU_mT)Bit = 0; // T,T,T,4T
else if (P0 < NewKAKU_mT && P1 > NewKAKU_mT && P2 < NewKAKU_mT && P3 < NewKAKU_mT)Bit = 1; // T,4T,T,T
else if (P0 < NewKAKU_mT && P1 < NewKAKU_mT && P2 < NewKAKU_mT && P3 < NewKAKU_mT) // T,T,T,T Deze hoort te zitten op i=111 want: 27e NewKAKU bit maal 4 plus 2 posities voor startbit
{
if (Plugin_199_number != NewKAKUdim_RawSignalLength) // als de dim-bits er niet zijn
return false;
}
else
return false; // andere mogelijkheden zijn niet geldig in NewKAKU signaal.
if (i < 130) // alle bits die tot de 32-bit pulstrein behoren 32bits * 4posities per bit + pulse/space voor startbit
bitstream = (bitstream << 1) | Bit;
else // de resterende vier bits die tot het dimlevel behoren
Par1 = (Par1 << 1) | Bit;
i += 4; // volgende pulsenquartet
} while (i < Plugin_199_number - 2); //-2 omdat de space/pulse van de stopbit geen deel meer van signaal uit maakt.
if (i > 140) // Commando en Dim deel
Par1++; // Dim level. +1 omdat gebruiker dim level begint bij één.
else
Par1 = ((bitstream >> 4) & 0x01) ? 16 : 0; // On/Off bit omzetten naar een Nodo waarde.
Par2 = bitstream;
address = bitstream >> 6;
channel = (bitstream & 0x0f) + 1;
command = (bitstream >> 4) & 0x03;
if (command > 1)
channel = 0;
// valid signal, remember timestamp to suppress repeats...
elapsed = millis() - Plugin_199_lastTime;
Plugin_199_lastTime = millis();
unsigned long codeHash = Par2 + Par1;
if (codeHash != Plugin_199_codeHash || (codeHash == Plugin_199_codeHash && elapsed > 250))
{
UserVar[BaseVarIndex] = address;
UserVar[BaseVarIndex + 1] = channel;
UserVar[BaseVarIndex + 2] = Par1;
String eventString = F("NewKaku_");
eventString += address;
eventString += F("#");
eventString += channel;
eventString += F("=");
eventString += Par1;
rulesProcessing(eventString);
success = true;
}
Plugin_199_codeHash = Par2 + Par1;
}
return success;
}
//********************************************************************************
// Send NewKaku protocol (without code wheel)
//********************************************************************************
void sendNewKaku(unsigned long address, byte channel, byte state)
{
unsigned long bitstream = 0L;
byte i = 1;
byte x; // aantal posities voor pulsen/spaces in RawSignal
bitstream = address << 6;
bitstream |= (channel - 1);
//RawSignal.Repeats = 7; // Aantal herhalingen van het signaal.
//RawSignal.Delay = 20; // Tussen iedere pulsenreeks enige tijd rust.
if (state == 16 || state == 0)
{
bitstream |= (state == 16) << 4; // bit-5 is het on/off commando in KAKU signaal
x = 130; // verzend startbit + 32-bits = 130
}
else
x = 146; // verzend startbit + 32-bits = 130 + 4dimbits = 146
// bitstream bevat nu de KAKU-bits die verzonden moeten worden.
for (i = 3; i <= x; i++)Plugin_199_pulses[i] = NewKAKU_1T / 25; // De meeste tijden in signaal zijn T. Vul alle pulstijden met deze waarde. Later worden de 4T waarden op hun plek gezet
i = 1;
Plugin_199_pulses[i++] = NewKAKU_1T / 25; //pulse van de startbit
Plugin_199_pulses[i++] = NewKAKU_8T / 25; //space na de startbit
byte y = 31; // bit uit de bitstream
while (i < x)
{
if ((bitstream >> (y--)) & 1)
Plugin_199_pulses[i + 1] = NewKAKU_4T / 25; // Bit=1; // T,4T,T,T
else
Plugin_199_pulses[i + 3] = NewKAKU_4T / 25; // Bit=0; // T,T,T,4T
if (x == 146) // als het een dim opdracht betreft
{
if (i == 111) // Plaats van de Commando-bit uit KAKU
Plugin_199_pulses[i + 3] = NewKAKU_1T / 25; // moet een T,T,T,T zijn bij een dim commando.
if (i == 127) // als alle pulsen van de 32-bits weggeschreven zijn
{
bitstream = (unsigned long)state; // nog vier extra dim-bits om te verzenden.
y = 3;
}
}
i += 4;
}
Plugin_199_pulses[i++] = NewKAKU_1T / 25; //pulse van de stopbit
Plugin_199_pulses[i] = 0; //space van de stopbit
Plugin_199_number = i; // aantal bits*2 die zich in het opgebouwde RawSignal bevinden
RawSendRF();
}
//********************************************************************************
// Decode Kaku protocol (with code wheel)
//********************************************************************************
#define KAKU_CodeLength 12
#define KAKU_T 350
boolean decodeKaku()
{
boolean success = false;
byte Par1 = 0;
unsigned long Par2 = 0;
int i, j;
unsigned long bitstream = 0;
if (Plugin_199_number != (KAKU_CodeLength * 4) + 2)return false; // conventionele KAKU bestaat altijd uit 12 data bits plus stop. Ongelijk, dan geen KAKU!
for (i = 0; i < KAKU_CodeLength; i++)
{
j = (KAKU_T * 2) / 25;
if (Plugin_199_pulses[4 * i + 1] < j && Plugin_199_pulses[4 * i + 2] > j && Plugin_199_pulses[4 * i + 3] < j && Plugin_199_pulses[4 * i + 4] > j) {
bitstream = (bitstream >> 1); // 0
}
else if (Plugin_199_pulses[4 * i + 1] < j && Plugin_199_pulses[4 * i + 2] > j && Plugin_199_pulses[4 * i + 3] > j && Plugin_199_pulses[4 * i + 4] < j) {
bitstream = (bitstream >> 1 | (1 << (KAKU_CodeLength - 1))); // 1
}
else if (Plugin_199_pulses[4 * i + 1] < j && Plugin_199_pulses[4 * i + 2] > j && Plugin_199_pulses[4 * i + 3] < j && Plugin_199_pulses[4 * i + 4] < j) {
bitstream = (bitstream >> 1); // Short 0, Groep commando op 2e bit.
Par1 = 2;
}
else {
return false; // foutief signaal
}
}
if ((bitstream & 0x600) == 0x600) // twee vaste bits van KAKU gebruiken als checksum
{ // Alles is in orde, bouw event op
Par2 = bitstream & 0xFF;
Par1 |= (bitstream >> 11) & 0x01;
// valid signal, remember timestamp to suppress repeats...
elapsed = millis() - Plugin_199_lastTime;
Plugin_199_lastTime = millis();
unsigned long codeHash = Par2 + Par1;
if (codeHash != Plugin_199_codeHash || (codeHash == Plugin_199_codeHash && elapsed > 250))
{
String eventString = F("Kaku_");
eventString += (Par2 & 0x0f) + 1;
eventString += F("#");
eventString += (Par2 >> 4) + 1;
eventString += F("=");
eventString += Par1;
rulesProcessing(eventString);
success = true;
}
Plugin_199_codeHash = Par2 + Par1;
}
return success;
}
//********************************************************************************
// Send Kaku protocol (with code wheel)
//********************************************************************************
void sendKaku(unsigned long address, byte channel, byte state)
{
byte Par1 = state;
unsigned long Par2 = ((channel-1) << 4) + address-1;
unsigned long Bitstream = Par2 | (0x600 | ((Par1 & 1 /*Commando*/) << 11)); // Stel een bitstream samen
// loop de 12-bits langs en vertaal naar pulse/space signalen.
for (byte i = 0; i < KAKU_CodeLength; i++)
{
Plugin_199_pulses[4 * i + 1] = KAKU_T / RAWSIGNAL_MULTIPLY;
Plugin_199_pulses[4 * i + 2] = (KAKU_T * 3) / RAWSIGNAL_MULTIPLY;
if (((Par1 >> 1) & 1) /* Groep */ && i >= 4 && i < 8)
{
Plugin_199_pulses[4 * i + 3] = KAKU_T / RAWSIGNAL_MULTIPLY;
Plugin_199_pulses[4 * i + 4] = KAKU_T / RAWSIGNAL_MULTIPLY;
} // short 0
else
{
if ((Bitstream >> i) & 1) // 1
{
Plugin_199_pulses[4 * i + 3] = (KAKU_T * 3) / RAWSIGNAL_MULTIPLY;
Plugin_199_pulses[4 * i + 4] = KAKU_T / RAWSIGNAL_MULTIPLY;
}
else //0
{
Plugin_199_pulses[4 * i + 3] = KAKU_T / RAWSIGNAL_MULTIPLY;
Plugin_199_pulses[4 * i + 4] = (KAKU_T * 3) / RAWSIGNAL_MULTIPLY;
}
}
// Stopbit
Plugin_199_pulses[4 * KAKU_CodeLength + 1] = KAKU_T / RAWSIGNAL_MULTIPLY;
Plugin_199_pulses[4 * KAKU_CodeLength + 2] = KAKU_T / RAWSIGNAL_MULTIPLY;
}
Plugin_199_number=KAKU_CodeLength*4+2;
RawSendRF();
}
//********************************************************************************
// Decode HomeEasy 3xx series EU protocol (without code wheel)
//********************************************************************************
boolean decodeHE300EU()
{
boolean success = false;
byte Par1 = 0;
unsigned long Par2 = 0;
unsigned long address = 0;
unsigned long bitstream = 0;
int counter = 0;
byte rfbit = 0;
byte state = 0;
unsigned long channel = 0;
// valid messages are 116 pulses
if (Plugin_199_number != 116) return false;
for (byte x = 1; x <= Plugin_199_number; x = x + 2)
{
if ((Plugin_199_pulses[x] * 25 < 500) & (Plugin_199_pulses[x + 1] * 25 > 500))
rfbit = 1;
else
rfbit = 0;
if ((x >= 23) && (x <= 86)) address = (address << 1) | rfbit;
if ((x >= 87) && (x <= 114)) bitstream = (bitstream << 1) | rfbit;
}
state = ((bitstream >> 8) & 0x3) - 1;
channel = (bitstream) & 0x3f;
Par1 = state;
Par2 = address + channel;
// valid signal, remember timestamp to suppress repeats...
elapsed = millis() - Plugin_199_lastTime;
Plugin_199_lastTime = millis();
unsigned long codeHash = Par2 + Par1;
if (codeHash != Plugin_199_codeHash || (codeHash == Plugin_199_codeHash && elapsed > 250))
{
String eventString = F("HE300EU_");
eventString += address;
eventString += F("#");
eventString += channel;
eventString += F("=");
eventString += Par1;
rulesProcessing(eventString);
success = true;
}
Plugin_199_codeHash = Par2 + Par1;
}
//********************************************************************************
// Decode generic protocol, deriving hash value
//********************************************************************************
boolean decodeUnknown()
{
boolean success = false;
byte Par1 = 0;
unsigned long Par2 = 0;
int x;
unsigned int MinPulse = 0xffff;
unsigned int MinSpace = 0xffff;
unsigned long CodeM = 0L;
unsigned long CodeS = 0L;
if (Plugin_199_number < MIN_RAW_PULSES) return false;
for (x = 5; x < Plugin_199_number - 2; x += 2)
{
if (Plugin_199_pulses[x] < MinPulse)MinPulse = Plugin_199_pulses[x];
if (Plugin_199_pulses[x + 1] < MinSpace)MinSpace = Plugin_199_pulses[x + 1];
}
MinPulse += (MinPulse * 100) / 100;
MinSpace += (MinSpace * 100) / 100;
// Data kan zowel in de mark als de space zitten. Daarom pakken we beide voor data opbouw.
for (x = 3; x <= Plugin_199_number; x += 2)
{
CodeM = (CodeM << 1) | (Plugin_199_pulses[x] > MinPulse);
CodeS = (CodeS << 1) | (Plugin_199_pulses[x + 1] > MinSpace);
}
// Data kan zowel in de mark als de space zitten. We nemen de grootste waarde voor de data.
if (CodeM > CodeS)
Par2 = CodeM;
else
Par2 = CodeS;
// valid signal, remember timestamp to suppress repeats...
elapsed = millis() - Plugin_199_lastTime;
Plugin_199_lastTime = millis();
unsigned long codeHash = Par2;
if (codeHash != Plugin_199_codeHash || (codeHash == Plugin_199_codeHash && elapsed > 250))
{
String eventString = F("UnknownRF_");
eventString += Par2;
rulesProcessing(eventString);
success = true;
}
Plugin_199_codeHash = Par2;
}
#endif
+490
View File
@@ -0,0 +1,490 @@
#ifdef PLUGIN_BUILD_DEV
//#######################################################################################################
//#################################### Plugin 210: MQTT Import ##########################################
//#################################### Version 0.4 5-Aug-2016 ###########################################
//## This Version Requires ESPEasy 114+, Core 2.3 and PubSub 2.6 #######
//#######################################################################################################
// This task reads data from the MQTT Import input stream and saves the value
#define PLUGIN_210
#define PLUGIN_ID_210 210
#define PLUGIN_NAME_210 "MQTT Import [DEVELOPMENT]"
#define PLUGIN_VALUENAME1_210 "Value1"
#define PLUGIN_VALUENAME2_210 "Value2"
#define PLUGIN_VALUENAME3_210 "Value3"
#define PLUGIN_VALUENAME4_210 "Value4"
#define PLUGIN_IMPORT 210 // This is a 'private' function used only by this import module
// Declare a Wifi client for this plugin only
WiFiClient espclient_210;
PubSubClient MQTTclient_210(espclient_210); // Create a new pubsub instance
boolean Plugin_210(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
char deviceTemplate[4][41]; // variable for saving the subscription topics
//
// Generate the MQTT import client name from the system name and a suffix
//
String tmpClientName = "%sysname%-Import";
String ClientName = parseTemplate(tmpClientName, 20);
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_210;
Device[deviceCount].Type = SENSOR_TYPE_SWITCH;
Device[deviceCount].VType = SENSOR_TYPE_SINGLE; // This means it has a single pin
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = true; // Need this in order to get the decimals option
Device[deviceCount].ValueCount = 4;
Device[deviceCount].SendDataOption = false;
Device[deviceCount].TimerOption=false;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_210);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_210));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_210));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_210));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_210));
break;
}
case PLUGIN_WEBFORM_LOAD:
{
LoadCustomTaskSettings(event->TaskIndex, (byte*)&deviceTemplate, sizeof(deviceTemplate));
for (byte varNr = 0; varNr < 4; varNr++)
{
string += F("<TR><TD>MQTT Topic ");
string += varNr + 1;
string += F(":<TD><input type='text' size='40' maxlength='40' name='Plugin_210_template");
string += varNr + 1;
string += F("' value='");
string += deviceTemplate[varNr];
string += F("'>");
}
success = true;
break;
}
case PLUGIN_WEBFORM_SAVE:
{
String argName;
for (byte varNr = 0; varNr < 4; varNr++)
{
argName = F("Plugin_210_template");
argName += varNr + 1;
strncpy(deviceTemplate[varNr], WebServer.arg(argName).c_str(), sizeof(deviceTemplate[varNr]));
}
Settings.TaskDeviceID[event->TaskIndex] = 1; // temp fix, needs a dummy value
SaveCustomTaskSettings(event->TaskIndex, (byte*)&deviceTemplate, sizeof(deviceTemplate));
success = true;
break;
}
case PLUGIN_INIT:
{
// When we edit the subscription data from the webserver, the plugin is called again with init.
// In order to resubscribe we have to disconnect and reconnect in order to get rid of any obsolete subscriptions
MQTTclient_210.disconnect();
if (MQTTConnect_210(ClientName))
{
// Subscribe to ALL the topics from ALL instance of this import module
MQTTSubscribe_210();
success = true;
}
else
{
success=false;
}
}
case PLUGIN_TEN_PER_SECOND:
{
MQTTclient_210.loop(); // Listen out for callbacks
success=true;
break;
}
case PLUGIN_ONCE_A_SECOND:
{
// Here we check that the MQTT client is alive.
if (!MQTTclient_210.connected()){
String log = F("IMPT : MQTT 210 Connection lost");
addLog(LOG_LEVEL_ERROR, log);
MQTTclient_210.disconnect();
delay(1000);
if (! MQTTConnect_210(ClientName)){
success=false;
break;
}
MQTTSubscribe_210();
}
success=true;
break;
}
case PLUGIN_READ:
{
// This routine does not output any data and so we do not need to respond to regular read requests
success = false;
break;
}
case PLUGIN_IMPORT:
{
// This is a private option only used by the MQTT 210 callback function
// Get the payload and check it out
String Payload=event->String2;
float floatPayload=string2float(Payload);
LoadTaskSettings(event->TaskIndex);
if (floatPayload == -999){
String log=F("IMPT : Bad Import MQTT Command ");
log+=event->String1;
addLog(LOG_LEVEL_ERROR,log);
log="ERR : Illegal Payload ";
log+=Payload;
log+=" ";
log+=ExtraTaskSettings.TaskDeviceName;
addLog(LOG_LEVEL_INFO,log);
success=false;
break;
}
// Get the Topic and see if it matches any of the subscriptions
String Topic=event->String1;
LoadCustomTaskSettings(event->TaskIndex, (byte*)&deviceTemplate, sizeof(deviceTemplate));
for (byte x = 0; x < 4; x++)
{
String subscriptionTopic = deviceTemplate[x];
subscriptionTopic.trim();
if (subscriptionTopic.length() == 0) continue; // skip blank subscriptions
// Now check if the incoming topic matches one of our subscriptions
if (MQTTCheckSubscription_210(Topic,subscriptionTopic))
{
UserVar[event->BaseVarIndex+x]=floatPayload; // Save the new value
// Log the event
String log =F("IMPT : [");
log += ExtraTaskSettings.TaskDeviceName;
log += F("#");
log += ExtraTaskSettings.TaskDeviceValueNames[x];
log += F("] : ");
log += floatPayload;
addLog(LOG_LEVEL_INFO, log);
// Generate event for rules processing - proposed by TridentTD
if (Settings.UseRules)
{
String RuleEvent = F("");
RuleEvent += ExtraTaskSettings.TaskDeviceName;
RuleEvent += F("#");
RuleEvent += ExtraTaskSettings.TaskDeviceValueNames[x];
RuleEvent += F("=");
RuleEvent += floatPayload;
rulesProcessing(RuleEvent);
}
success = true;
}
}
break;
}
}
return success;
}
boolean MQTTSubscribe_210()
{
// Subscribe to the topics requested by ALL calls to this plugin.
// We do this because if the connection to the broker is lost, we want to resubscribe for all instances.
char deviceTemplate[4][41];
// Loop over all tasks looking for a 210 instance
for (byte y = 0; y < TASKS_MAX; y++)
{
if (Settings.TaskDeviceNumber[y] == 210)
{
LoadCustomTaskSettings(y, (byte*)&deviceTemplate, sizeof(deviceTemplate));
// Now loop over all import variables and subscribe to those that are not blank
for (byte x = 0; x < 4; x++)
{
String subscribeTo = deviceTemplate[x];
if (subscribeTo.length() > 0)
{
if (MQTTclient_210.subscribe(subscribeTo.c_str()))
{
String log = F("IMPT : [");
LoadTaskSettings(y);
log += ExtraTaskSettings.TaskDeviceName;
log += F("#");
log += ExtraTaskSettings.TaskDeviceValueNames[x];
log += F("] subscribed to ");
log += subscribeTo;
addLog(LOG_LEVEL_INFO, log);
}
else
{
String log = F("IMPT : Error subscribing to ");
log += subscribeTo;
addLog(LOG_LEVEL_ERROR, log);
return false;
}
}
}
}
}
return true;
}
//
// handle MQTT messages
//
void mqttcallback_210(char* c_topic, byte* b_payload, unsigned int length)
{
// Here we have incomng MQTT messages from the mqtt import module
String topic = c_topic;
char cpayload[256];
strncpy(cpayload, (char*)b_payload, length);
cpayload[length] = 0;
String payload = cpayload; // convert byte to char string
payload.trim();
byte DeviceIndex = getDeviceIndex(210); // This is the device index of 210 modules -there should be one!
// We generate a temp event structure to pass to the plugins
struct EventStruct TempEvent;
TempEvent.String1 = topic; // This is the topic of the message
TempEvent.String2 = payload; // This is the payload
// Here we loop over all tasks and call each 210 plugin with function PLUGIN_IMPORT
for (byte y = 0; y < TASKS_MAX; y++)
{
if (Settings.TaskDeviceNumber[y] == 210) // if we have found a 210 device, then give it something to think about!
{
TempEvent.TaskIndex = y;
TempEvent.BaseVarIndex = y * VARS_PER_TASK; // This is the index in Uservar where values for this task are stored
Plugin_ptr[DeviceIndex](PLUGIN_IMPORT, &TempEvent, payload);
}
}
}
//
// Make a new client connection to the mqtt broker.
// For some reason this seems to failduring the call in INIT- however it succeeds later during recovery
// It would be nice to understand this....
boolean MQTTConnect_210(String clientid)
{
boolean result = false;
// Do nothing if already connected
if (MQTTclient_210.connected())return true;
IPAddress MQTTBrokerIP(Settings.Controller_IP);
// define stuff for the client - this could also be done in the intial declaration of MQTTclient_210
MQTTclient_210.setServer(MQTTBrokerIP, Settings.ControllerPort);
MQTTclient_210.setCallback(mqttcallback_210);
// Try three times for a connection
for (byte x = 1; x < 4; x++)
{
String log = "";
if ((SecuritySettings.ControllerUser[0] != 0) && (SecuritySettings.ControllerPassword[0] != 0))
result = MQTTclient_210.connect(clientid.c_str(), SecuritySettings.ControllerUser, SecuritySettings.ControllerPassword);
else
result = MQTTclient_210.connect(clientid.c_str());
if (result)
{
log = F("IMPT : Connected to MQTT broker with Client ID=");
log += clientid;
addLog(LOG_LEVEL_INFO, log);
break; // end loop if succesfull
}
else
{
log = F("IMPT : Failed to connect to MQTT broker - attempt ");
log += x;
addLog(LOG_LEVEL_ERROR, log);
}
delay(500);
}
return MQTTclient_210.connected();
}
//
// Check to see if Topic matches the MQTT subscription
//
boolean MQTTCheckSubscription_210(String Topic, String Subscription) {
String tmpTopic = Topic;
String tmpSub = Subscription;
tmpTopic.trim();
tmpSub.trim();
// Get rid of any initial /
if (tmpTopic.substring(0, 1) == "/")tmpTopic = tmpTopic.substring(1);
if (tmpSub.substring(0, 1) == "/")tmpSub = tmpSub.substring(1);
// Add trailing / if required
int lenTopic = tmpTopic.length();
if (tmpTopic.substring(lenTopic - 1, lenTopic) != "/")tmpTopic += F("/");
int lenSub = tmpSub.length();
if (tmpSub.substring(lenSub - 1, lenSub) != "/")tmpSub += F("/");
// Now get first part
int SlashTopic;
int SlashSub;
int count = 0;
String pTopic;
String pSub;
while (count < 10) {
// Get locations of the first /
SlashTopic = tmpTopic.indexOf('/');
SlashSub = tmpSub.indexOf('/');
// If no slashes found then match is OK
// If only one slash found then not OK
if ((SlashTopic == -1) && (SlashSub == -1)) return true;
if ((SlashTopic == -1) && (SlashSub != -1)) return false;
if ((SlashTopic != -1) && (SlashSub == -1)) return false;
// Get the values for the current subtopic
pTopic = tmpTopic.substring(0, SlashTopic);
pSub = tmpSub.substring(0, SlashSub);
// And strip the subtopic from the topic
tmpTopic = tmpTopic.substring(SlashTopic + 1);
tmpSub = tmpSub.substring(SlashSub + 1);
// If the subtopics match then OK - otherwise fail
if (pSub == "#") return true;
if ((pTopic != pSub) && (pSub != "+"))return false;
count = count + 1;
}
return false;
}
// ***************************************************************************
// Convert String to float - returns -999 in case of error
float string2float(String myString) {
int i, len;
float value;
len = myString.length();
char tmp[(len + 1)]; // one extra for the zero termination
byte start = 0;
// Look for decimal point - they can be anywhere but no more than one of them!
int dotIndex = myString.indexOf(".");
//Serial.println(dotIndex);
if (dotIndex != -1)
{
int dotIndex2 = (myString.substring(dotIndex + 1)).indexOf(".");
//Serial.println(dotIndex2);
if (dotIndex2 != -1)return -999.00; // Give error if there is more than one dot
}
if (myString.substring(0, 1) == "-") {
start = 1; //allow a minus in front of string
tmp[i] = '-';
}
for (i = start; i<len; i++)
{
tmp[i] = myString.charAt(i);
if (!isdigit(tmp[i]))
{
if (tmp[i] != '.')return -999;
}
}
tmp[i] = 0;
value = atof(tmp);
return value;
}
#endif