Browse Source

Added RF support, encapsulate functionalities, changes in config page

fastled
Xose Pérez 8 years ago
parent
commit
d40a6e3652
13 changed files with 1471 additions and 310 deletions
  1. +36
    -8
      data/index.html
  2. +158
    -0
      lib/RemoteSwitch/RemoteReceiver.cpp
  3. +75
    -0
      lib/RemoteSwitch/RemoteReceiver.h
  4. +263
    -0
      lib/RemoteSwitch/RemoteSwitch.cpp
  5. +197
    -0
      lib/RemoteSwitch/RemoteSwitch.h
  6. +53
    -0
      lib/RemoteSwitch/examples/Light_show/Light_show.pde
  7. +58
    -0
      lib/RemoteSwitch/examples/Remote_translator/Remote_translator.pde
  8. +52
    -0
      lib/RemoteSwitch/examples/Retransmitter/Retransmitter.pde
  9. +39
    -0
      lib/RemoteSwitch/examples/Show_received_code/Show_received_code.pde
  10. +12
    -0
      lib/RemoteSwitch/keywords.txt
  11. +25
    -0
      lib/RemoteSwitch/license.txt
  12. +7
    -1
      platformio.ini
  13. +496
    -301
      src/code.ino

+ 36
- 8
data/index.html View File

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>SONOFF Configuration</title>
<title>{appname} {appversion} - Configuration</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="stylesheet" href="index.css" /> <link rel="stylesheet" href="index.css" />
<script src="jquery-1.12.3.min.js"></script> <script src="jquery-1.12.3.min.js"></script>
@ -17,9 +17,6 @@
'dataType': 'json', 'dataType': 'json',
'data': $(this).serializeArray() 'data': $(this).serializeArray()
}).done(function(data) { }).done(function(data) {
if (data.ip) $("span.ip").html(data.ip);
if (data.network) $("span.network").html(data.network);
if (data.status) $("span.status").html(data.status);
$(":submit").prop("value", "Connect").removeAttr("disabled"); $(":submit").prop("value", "Connect").removeAttr("disabled");
}).fail(function() { }).fail(function() {
$(":submit").prop("value", "Connect").removeAttr("disabled"); $(":submit").prop("value", "Connect").removeAttr("disabled");
@ -27,6 +24,22 @@
event.preventDefault(); event.preventDefault();
}); });
function update() {
$.ajax({
'method': 'GET',
'url': '/status',
'dataType': 'json'
}).done(function(data) {
$("span.network").html(data.wifi ? data.network.toUpperCase() : "NOT CONNECTED");
$("span.ip").html(data.wifi ? "("+data.ip+")" : "");
$("span.mqtt").html(data.mqtt ? "CONNECTED" : "NOT CONNECTED");
$("span.relay").html(data.relay ? "ON" : "OFF");
});
}
update();
var updateTimer = setInterval(update, 5000);
}); });
</script> </script>
@ -36,13 +49,14 @@
<form id="config" action="/" method="post"> <form id="config" action="/" method="post">
<header> <header>
<h2>SONOFF Configuration</h2>
<div>This form lets you define the WIFI network this SONOFF should try to join.</div>
<h2>{appname} {appversion} - Configuration</h2>
<div>This form lets you define the WIFI network this device should try to join.</div>
</header> </header>
<div> <div>
<p>Connection: <span class="status">{status}</span></p>
<p>Network: <span class="network">{network}</span> (<span class="ip">{ip}</span>)</p>
<p>Wifi: <span class="network"></span> <span class="ip"></span></p>
<p>MQTT: <span class="mqtt"></span></p>
<p>Relay: <span class="relay"></span></p>
</div> </div>
<div> <div>
@ -108,6 +122,20 @@
</div> </div>
</div> </div>
<div>
<label class="desc" for="mqtt_port">RF Channel</label>
<div>
<input id="rf_channel" name="rf_channel" type="text" spellcheck="false" value="{rf_channel}" maxlength="255" tabindex="8">
</div>
</div>
<div>
<label class="desc" for="mqtt_topic">RF Device</label>
<div>
<input id="rf_device" name="rf_device" type="text" class="field text fn" value="{rf_device}" size="8" tabindex="9">
</div>
</div>
<div> <div>
<div> <div>
<input id="submit" name="submit" type="submit" value="Connect"> <input id="submit" name="submit" type="submit" value="Connect">


+ 158
- 0
lib/RemoteSwitch/RemoteReceiver.cpp View File

@ -0,0 +1,158 @@
/*
* RemoteSwitch library v2.0.0 made by Randy Simons http://randysimons.nl
* See RemoteSwitchSender.h for details.
*
* License: "Free BSD license". See license.txt
*/
#include "RemoteReceiver.h"
/************
* RemoteReceiver
************/
unsigned short RemoteReceiver::_interrupt;
volatile int RemoteReceiver::_state;
unsigned short RemoteReceiver::_minRepeats;
RemoteReceiverCallBack RemoteReceiver::_callback;
boolean RemoteReceiver::_inCallback = false;
void RemoteReceiver::init(unsigned short interrupt, unsigned short minRepeats, RemoteReceiverCallBack callback) {
_interrupt = interrupt;
_minRepeats = minRepeats;
_callback = callback;
//enable();
}
void RemoteReceiver::enable() {
_state = -1;
attachInterrupt(_interrupt, interruptHandler, CHANGE);
}
void RemoteReceiver::disable() {
detachInterrupt(_interrupt);
}
void RemoteReceiver::interruptHandler() {
static unsigned int period; //Calculated duration of 1 period
static unsigned short receivedBit; //Contains "bit" currently receiving
static unsigned long receivedCode; //Contains received code
static unsigned long previousCode; //Contains previous received code
static unsigned short repeats = 0; //The number of times the an identical code is received in a row.
static unsigned long lastChange=0; //Timestamp of previous edge
static unsigned int min1Period, max1Period, min3Period, max3Period;
unsigned long currentTime=micros();
unsigned int duration=currentTime-lastChange; //Duration = Time between edges
lastChange=currentTime;
if (_state==-1) { //Waiting for sync-signal
if (duration>3720) { //==31*120 minimal time between two edges before decoding starts.
//Sync signal received.. Preparing for decoding
period=duration/31;
receivedCode=previousCode=repeats=0;
//Allow for large error-margin. ElCheapo-hardware :(
min1Period=period*4/10; //Avoid floating point math; saves memory.
max1Period=period*16/10;
min3Period=period*23/10;
max3Period=period*37/10;
}
else {
return;
}
} else if (_state<48) { //Decoding message
//bit part durations can ONLY be 1 or 3 periods.
if (duration>=min1Period && duration<=max1Period) {
bitClear(receivedBit,_state%4); //Note: this sets the bits in reversed order! Correct order would be: 3-(_state%4), but that's overhead we don't want.
}
else if (duration>=min3Period && duration<=max3Period) {
bitSet(receivedBit,_state%4); //Note: this sets the bits in reversed order!
}
else { //Otherwise the entire sequence is invalid
_state=-1;
return;
}
if ((_state%4)==3) { //Last bit part?
//Shift
receivedCode*=3;
//Decode bit.
if (receivedBit==B1010) { //short long short long == B0101, but bits are set in reversed order, so compare to B1010
//bit "0" received
receivedCode+=0; //I hope the optimizer handles this ;)
}
else if (receivedBit==B0101) { //long short long short == B101, but bits are set in reversed order, so compare to B0101
//bit "1" received
receivedCode+=1;
}
else if (receivedBit==B0110) { //short long long short. Reversed too, but makes no difference.
//bit "f" received
receivedCode+=2;
}
else {
//Bit was rubbish. Abort.
_state=-1;
return;
}
}
} else if (_state==48) { //Waiting for sync bit part 1
//Must be 1 period.
if (duration<min1Period || duration>max1Period) {
_state=-1;
return;
}
} else { //Waiting for sync bit part 2
//Must be 31 periods.
if (duration<period*25 || duration>period*36) {
_state=-1;
return;
}
//receivedCode is a valid code!
if (receivedCode!=previousCode) {
repeats=0;
previousCode=receivedCode;
}
repeats++;
if (repeats>=_minRepeats) {
if (!_inCallback) {
_inCallback = true;
(_callback)(receivedCode, period);
_inCallback = false;
}
//Reset after callback.
_state=-1;
return;
}
//Reset for next round
receivedCode = 0;
_state=0; //no need to wait for another sync-bit!
return;
}
_state++;
return;
}
boolean RemoteReceiver::isReceiving(int waitMillis) {
unsigned long startTime=millis();
int waited; //signed int!
do {
if (_state!=-1) {
return true;
}
waited = (millis()-startTime);
} while(waited>=0 && waited <= waitMillis); //Yes, clock wraps every 50 days. And then you'd have to wait for a looooong time.
return false;
}

+ 75
- 0
lib/RemoteSwitch/RemoteReceiver.h View File

@ -0,0 +1,75 @@
/*
* RemoteSwitch library v2.0.0 made by Randy Simons http://randysimons.nl
*
* License: "Free BSD license". See license.txt
*/
#ifndef RemoteReceiver_h
#define RemoteReceiver_h
//#include "WProgram.h"
#include "Arduino.h"
typedef void (*RemoteReceiverCallBack)(unsigned long, unsigned int);
/**
* See RemoteSwitch for introduction.
*
* RemoteReceiver decodes the signal received from a 433MHz-receiver, like the "KlikAanKlikUit"-system
* as well as the signal sent by the RemoteSwtich class. When a correct signal is received,
* a user-defined callback function is called.
*
* Note that in the callback function, the interrupts are still disabled. You can enabled them, if needed.
* A call to the callback must b finished before RemoteReceiver will call the callback function again, thus
* there is no re-entrant problem.
*
* When sending your own code using RemoteSwich, disable() the receiver first.
*
* This is a pure static class, for simplicity and to limit memory-use.
*/
class RemoteReceiver {
public:
/**
* Initializes the decoder.
*
* @param interrupt The interrupt as is used by Arduino's attachInterrupt function. See attachInterrupt for details.
* @param minRepeats The number of times the same code must be received in a row before the callback is calles
* @param callback Pointer to a callback function, with signature void (*func)(unsigned long, unsigned int). First parameter is the decoded data, the second the period of the timing.
*/
static void init(unsigned short interrupt, unsigned short minRepeats, RemoteReceiverCallBack callback);
/**
* Enabled decoding. No need to call enable() after init().
*/
static void enable();
/**
* Disable decoding. You can enable decoding by calling enable();
*/
static void disable();
/**
* Tells wether a signal is being received. Since it makes no sense to transmit while another transmitter is active,
* it's best to wait for isReceiving() to false.
*
* Note: isReceiving() depends on interrupts enabled. Thus, when disabled()'ed, or when interrupts are disabled (as is
* the case in the callback), isReceiving() will not work properly.
* @param waitMillis number of milliseconds to monitor for signal.
* @return boolean If after waitMillis no signal was being processed, returns false. If before expiration a signal was being processed, returns true.
*/
static boolean isReceiving(int waitMillis = 50);
private:
static void interruptHandler();
static unsigned short _interrupt; //Radio input interrupt
volatile static int _state; //State of decoding process. There are 49 states, 1 for "waiting for signal" and 48 for decoding the 48 edges in a valid code.
static unsigned short _minRepeats;
static RemoteReceiverCallBack _callback;
static boolean _inCallback; //When true, the callback function is being executed; prevents re-entrance.
};
#endif

+ 263
- 0
lib/RemoteSwitch/RemoteSwitch.cpp View File

@ -0,0 +1,263 @@
/*
* RemoteSwitch library v2.0.0 made by Randy Simons http://randysimons.nl
* See RemoteSwitchSender.h for details.
*
* License: "Free BSD license". See license.txt
*/
#include "RemoteSwitch.h"
/************
* RemoteSwitch
************/
RemoteSwitch::RemoteSwitch(unsigned short pin, unsigned int periodusec, unsigned short repeats) {
_pin=pin;
_periodusec=periodusec;
_repeats=repeats;
pinMode(_pin, OUTPUT);
}
unsigned long RemoteSwitch::encodeTelegram(unsigned short trits[]) {
unsigned long data = 0;
//Encode data
for (unsigned short i=0;i<12;i++) {
data*=3;
data+=trits[i];
}
//Encode period duration
data |= (unsigned long)_periodusec << 23;
//Encode repeats
data |= (unsigned long)_repeats << 20;
return data;
}
void RemoteSwitch::sendTelegram(unsigned short trits[]) {
sendTelegram(encodeTelegram(trits),_pin);
}
/**
* Format data:
* pppppppp|prrrdddd|dddddddd|dddddddd (32 bit)
* p = perioud (9 bit unsigned int
* r = repeats as 2log. Thus, if r = 3, then signal is sent 2^3=8 times
* d = data
*/
void RemoteSwitch::sendTelegram(unsigned long data, unsigned short pin) {
unsigned int periodusec = (unsigned long)data >> 23;
unsigned short repeats = 1 << (((unsigned long)data >> 20) & B111);
data = data & 0xfffff; //truncate to 20 bit
//Convert the base3-code to base4, to avoid lengthy calculations when transmitting.. Messes op timings.
unsigned long dataBase4 = 0;
for (unsigned short i=0; i<12; i++) {
dataBase4<<=2;
dataBase4|=(data%3);
data/=3;
}
for (unsigned short int j=0;j<repeats;j++) {
//Sent one telegram
//Use data-var as working var
data=dataBase4;
for (unsigned short i=0; i<12; i++) {
switch (data & B11) {
case 0:
digitalWrite(pin, HIGH);
delayMicroseconds(periodusec);
digitalWrite(pin, LOW);
delayMicroseconds(periodusec*3);
digitalWrite(pin, HIGH);
delayMicroseconds(periodusec);
digitalWrite(pin, LOW);
delayMicroseconds(periodusec*3);
break;
case 1:
digitalWrite(pin, HIGH);
delayMicroseconds(periodusec*3);
digitalWrite(pin, LOW);
delayMicroseconds(periodusec);
digitalWrite(pin, HIGH);
delayMicroseconds(periodusec*3);
digitalWrite(pin, LOW);
delayMicroseconds(periodusec);
break;
case 2: //AKA: X or float
digitalWrite(pin, HIGH);
delayMicroseconds(periodusec);
digitalWrite(pin, LOW);
delayMicroseconds(periodusec*3);
digitalWrite(pin, HIGH);
delayMicroseconds(periodusec*3);
digitalWrite(pin, LOW);
delayMicroseconds(periodusec);
break;
}
//Next trit
data>>=2;
}
//Send termination/synchronisation-signal. Total length: 32 periods
digitalWrite(pin, HIGH);
delayMicroseconds(periodusec);
digitalWrite(pin, LOW);
delayMicroseconds(periodusec*31);
}
}
boolean RemoteSwitch::isSameCode(unsigned long encodedTelegram, unsigned long receivedData) {
return (receivedData==(encodedTelegram & 0xFFFFF)); //Compare the 20 LSB's
}
/************
* ActionSwitch
************/
ActionSwitch::ActionSwitch(unsigned short pin, unsigned int periodusec) : RemoteSwitch(pin,periodusec,3) {
//Call contructor
}
void ActionSwitch::sendSignal(unsigned short systemCode, char device, boolean on) {
sendTelegram(getTelegram(systemCode,device,on), _pin);
}
unsigned long ActionSwitch::getTelegram(unsigned short systemCode, char device, boolean on) {
unsigned short trits[12];
device-=65;
for (unsigned short i=0; i<5; i++) {
//bits 0-4 contain address (2^5=32 addresses)
trits[i]=(systemCode & 1)?1:2;
systemCode>>=1;
//bits 5-9 contain device. Only one trit has value 0, others have 2 (float)!
trits[i+5]=(i==device?0:2);
}
//switch on or off
trits[10]=(!on?0:2);
trits[11]=(on?0:2);
return encodeTelegram(trits);
}
/************
* BlokkerSwitch
************/
BlokkerSwitch::BlokkerSwitch(unsigned short pin, unsigned int periodusec) : RemoteSwitch(pin,periodusec,3) {
//Call contructor
}
void BlokkerSwitch::sendSignal(unsigned short device, boolean on) {
sendTelegram(getTelegram(device,on), _pin);
}
unsigned long BlokkerSwitch::getTelegram(unsigned short device, boolean on) {
unsigned short trits[12]={0};
device--;
for (unsigned short i=1; i<4; i++) {
//Bits 1-3 contain device
trits[i]=(device & 1)?0:1;
device>>=1;
}
//switch on or off
trits[8]=(on?1:0);
return encodeTelegram(trits);
}
/************
* KaKuSwitch
************/
KaKuSwitch::KaKuSwitch(unsigned short pin, unsigned int periodusec) : RemoteSwitch(pin,periodusec,3) {
//Call contructor
}
void KaKuSwitch::sendSignal(char address, unsigned short device, boolean on) {
sendTelegram(getTelegram(address, device, on), _pin);
}
unsigned long KaKuSwitch::getTelegram(char address, unsigned short device, boolean on) {
unsigned short trits[12];
address-=65;
device-=1;
for (unsigned short i=0; i<4; i++) {
//bits 0-3 contain address (2^4 = 16 addresses)
trits[i]=(address & 1)?2:0;
address>>=1;
//bits 4-8 contain device (2^4 = 16 addresses)
trits[i+4]=(device & 1)?2:0;
device>>=1;
}
//bits 8-10 seem to be fixed
trits[8]=0;
trits[9]=2;
trits[10]=2;
//switch on or off
trits[11]=(on?2:0);
return encodeTelegram(trits);
}
void KaKuSwitch::sendSignal(char address, unsigned short group, unsigned short device, boolean on) {
sendTelegram(getTelegram(address, group, on), _pin);
}
unsigned long KaKuSwitch::getTelegram(char address, unsigned short group, unsigned short device, boolean on) {
unsigned short trits[12], i;
address-=65;
group-=1;
device-=1;
//address. M3E Pin A0-A3
for (i=0; i<4; i++) {
//bits 0-3 contain address (2^4 = 16 addresses)
trits[i]=(address & 1)?2:0;
address>>=1;
}
//device. M3E Pin A4-A5
for (; i<6; i++) {
trits[i]=(device & 1)?2:0;
device>>=1;
}
//group. M3E Pin A6-A7
for (; i<8; i++) {
trits[i]=(group & 1)?2:0;
group>>=1;
}
//bits 8-10 are be fixed. M3E Pin A8/D0-A10/D2
trits[8]=0;
trits[9]=2;
trits[10]=2;
//switch on or off, M3E Pin A11/D3
trits[11]=(on?2:0);
return encodeTelegram(trits);
}

+ 197
- 0
lib/RemoteSwitch/RemoteSwitch.h View File

@ -0,0 +1,197 @@
/*
* RemoteSwitch library v2.0.0 made by Randy Simons http://randysimons.nl
*
* License: "Free BSD license". See license.txt
*/
#ifndef RemoteSwitch_h
#define RemoteSwitch_h
//#include "WProgram.h"
#include "Arduino.h"
/**
* RemoteSwitch provides a generic class for simulation of common RF remote controls, like the 'Klik aan Klik uit'-system
* (http://www.klikaanklikuit.nl/), used to remotely switch lights etc.
*
* Many of these remotes seem to use a 433MHz SAW resonator and one of these chips: LP801B, HX2262, PT2262, M3E.
* Datasheet for the HX2262/PT2262 ICs:
* http://www.princeton.com.tw/downloadprocess/downloadfile.asp?mydownload=PT2262_1.pdf
*
* Hardware required for this library: a 433MHz SAW oscillator transmitter, e.g.
* http://www.sparkfun.com/commerce/product_info.php?products_id=7815
* http://www.conrad.nl/goto/?product=130428
*
* Notes:
* - Since these chips use (and send!) tri-state inputs (low, high and floating) I use 'trits' instead of 'bits',
* when appropriate.
* - I measured the period lengths with a scope. Thus: they work for my remotes, but may fail for yours...
* A better way would be to calculate the 'target'-timings using the datasheets and the resistor-values on the remotes.
*/
class RemoteSwitch {
public:
/**
* Constructor.
*
* To obtain the correct period length, an oscilloscope is convenient, but you can also read the
* datasheet of the transmitter, measure the resistor for the oscillator and calculate the freqency.
*
* @param pin output pin on Arduino to which the transmitter is connected
* @param periodsec [0..511] Duration of one period, in microseconds. A trit is 6 periods.
* @param repeats [0..7] The 2log-Number of times the signal is repeated. The actual number of repeats will be 2^repeats. 3 would be a good start.
*/
RemoteSwitch(unsigned short pin, unsigned int periodusec, unsigned short repeats);
/**
* Encodes the data base on the current object and the given trits. The data can be reused, e.g.
* for use with the static version of sendTelegram, so you won't need to instantiate costly objects!
*
* @return The data suited for use with RemoteSwitch::sendTelegram.
*/
unsigned long encodeTelegram(unsigned short trits[]);
/**
* Send a telegram, including synchronisation-part.
*
* @param trits Array of size 12. "trits" should be either 0, 1 or 2, where 2 indicaties "float"
*/
void sendTelegram(unsigned short trits[]);
/**
* Send a telegram, including synchronisation-part. The data-param encodes the period duration, number of repeats and the actual data.
* Note: static method, which allows for use in low-mem situations.
*
* Format data:
* pppppppp|prrrdddd|dddddddd|dddddddd (32 bit)
* p = perioud (9 bit unsigned int
* r = repeats as 2log. Thus, if r = 3, then signal is sent 2^3=8 times
* d = data
*
* @param data data, period and repeats.
* @param pin Pin number of the transmitter.
*/
static void sendTelegram(unsigned long data, unsigned short pin);
/**
* Compares the data received with RemoteReceive with the data obtained by one of the getTelegram-functions.
* Period duration and repetitions are ignored by this function; only the data-payload is compared.
*
* @return true, if the codes are identical (the 20 least significant bits match)
*/
static boolean isSameCode(unsigned long encodedTelegram, unsigned long receivedData);
protected:
unsigned short _pin; //Radio output pin
unsigned int _periodusec; //oscillator period in microseconds
unsigned short _repeats; //Number over repetitions of one telegram
};
/**
* ActionSwitch simulatos a remote, as sold in the Dutch 'Action' stores. But there are many similar systems on the market.
* If your remote has setting for 5 address bits, and can control 5 devices on or off, then you can try to use the ActionSwitch
*/
class ActionSwitch: RemoteSwitch {
public:
/**
* Constructor
*
* @param pin output pin on Arduino to which the transmitter is connected
* @param periodsec Duration of one period, in microseconds. Default is 190usec
* @see RemoteSwitch
*/
ActionSwitch(unsigned short pin, unsigned int periodusec=190);
/**
* Send a on or off signal to a device.
*
* @param systemCode 5-bit addres (dip switches in remote). Range [0..31]
* @param device Device to switch. Range: [A..E] (case sensitive!)
* @param on True, to switch on. False to switch off,
*/
void sendSignal(unsigned short systemCode, char device, boolean on);
/**
* Generates the telegram (data) which can be used for RemoteSwitch::sendTelegram.
* See sendSignal for details on the parameters
*
* @return Encoded data, including repeats and period duration.
*/
unsigned long getTelegram(unsigned short systemCode, char device, boolean on);
};
/**
* BlokkerSwitch simulatos a remote, as sold in the Dutch 'Blokker' stores. But there are many similar systems on the market.
* These remotes have 4 on, 4 off buttons and a switch to switch between device 1-4 and 5-8. No futher configuration
* possible.
*/
class BlokkerSwitch: RemoteSwitch {
public:
/**
* Constructor
*
* @param pin output pin on Arduino to which the transmitter is connected
* @param periodsec Duration of one period, in microseconds. Default is 307usec
* @see RemoteSwitch
*/
BlokkerSwitch(unsigned short pin, unsigned int periodusec=230);
/**
* Send a on or off signal to a device.
*
* @param device Device to switch. Range: [1..8]
* @param on True, to switch on. False to switch off,
*/
void sendSignal(unsigned short device, boolean on);
/**
* @see RemoteSwitch::getTelegram
*/
unsigned long getTelegram(unsigned short device, boolean on);
};
/**
* KaKuSwitch simulates a KlikAanKlikUit-remote, but there are many clones.
* If your transmitter has a address dial with the characters A till P, you can try this class.
*/
class KaKuSwitch: RemoteSwitch {
public:
/**
* Constructor
*
* @param pin output pin on Arduino to which the transmitter is connected
* @param periodsec Duration of one period, in microseconds. Default is 375usec
* @see RemoteSwitch
*/
KaKuSwitch(unsigned short pin, unsigned int periodusec=375);
/**
* Send a on or off signal to a device.
*
* @param address addres (dial switches in remote). Range [A..P] (case sensitive!)
* @param group Group to switch. Range: [1..4]
* @param device Device to switch. Range: [1..4]
* @param on True, to switch on. False to switch off,
*/
void sendSignal(char address, unsigned short group, unsigned short device, boolean on);
/**
* Send a on or off signal to a device.
*
* @param address addres (dip switches in remote). Range [A..P] (case sensitive!)
* @param device device (dial switches in remote). Range [1..16]
* @param on True, to switch on. False to switch off,
*/
void sendSignal(char address, unsigned short device, boolean on);
/**
* @see RemoteSwitch::getTelegram
*/
unsigned long getTelegram(char address, unsigned short group, unsigned short device, boolean on);
/**
* @see RemoteSwitch::getTelegram
*/
unsigned long getTelegram(char address, unsigned short device, boolean on);
};
#endif

+ 53
- 0
lib/RemoteSwitch/examples/Light_show/Light_show.pde View File

@ -0,0 +1,53 @@
#include <RemoteSwitch.h>
/*
* Demo for RF remote switch transmitter.
* For details, see RemoteSwitch.h!
*
* This sketch switches some devices on and off in a loop.
*/
//Intantiate a new ActionSwitch remote, use pin 11
ActionSwitch actionSwitch(11);
//Intantiate a new KaKuSwitch remote, also use pin 11 (same transmitter!)
KaKuSwitch kaKuSwitch(11);
//Intantiate a new Blokker remote, also use pin 11 (same transmitter!)
BlokkerSwitch blokkerSwitch(11);
void setup() {
}
void loop() {
//Switch off KaKu-device 10 on address M
kaKuSwitch.sendSignal('M',10,false);
//Switch on Action-device B on system code 1.
actionSwitch.sendSignal(1,'B',true);
//Switch on Blokker-device 7.
blokkerSwitch.sendSignal(7,true);
//wait 2 seconds
delay(2000);
//Switch on KaKu-device 2 of group 3 on address M (which is the same as device 10 on address M!)
kaKuSwitch.sendSignal('M',3,2,true);
//Switch off Action-device B on system code 1.
actionSwitch.sendSignal(1,'B',false);
//Switch off Blokker-device 7.
blokkerSwitch.sendSignal(7,false);
//wait 4 seconds
delay(4000);
}

+ 58
- 0
lib/RemoteSwitch/examples/Remote_translator/Remote_translator.pde View File

@ -0,0 +1,58 @@
#include <RemoteReceiver.h>
#include <RemoteSwitch.h>
/*
* Demo for RF remote switch receiver.
* For details, see RemoteReceiver.h!
*
* This sketch "translates" a Action-remote to a Blokker-remote.
* When the A-On-button of the Action-remote is pressed, the Blokker-devices
* 5, 6 and 7 are switched on. The A-Off-button switches the devices off again.
*
* Connect the transmitter to digital pin 11, and the receiver to digital pin 2.
*/
ActionSwitch actionSwitch(11);
BlokkerSwitch blokkerSwitch(11);
//Prepare the code for switch A (system code 1) on and off, for easy comparision later.
unsigned long actionAOn = actionSwitch.getTelegram(1,'A',true);
unsigned long actionAOff = actionSwitch.getTelegram(1,'A',false);
void setup() {
//See example Show_received_code for info on this
RemoteReceiver::init(0, 3, translateCode);
}
void loop() {
}
//Callback function is called only when a valid code is received.
void translateCode(unsigned long receivedCode, unsigned int period) {
//Enabled interrupts, so RemoteReceiver::isReceiving() can be used.
interrupts();
//Compare the signals
if (RemoteSwitch::isSameCode(actionAOn, receivedCode)) {
//A-On-button pressed!
//Wait for a free ether
while(RemoteReceiver::isReceiving());
//Switch devices on
blokkerSwitch.sendSignal(5,true);
blokkerSwitch.sendSignal(6,true);
blokkerSwitch.sendSignal(7,true);
} else if (RemoteSwitch::isSameCode(actionAOff, receivedCode)) {
//A-Off-button pressed!
//Wait for a free ether
while(RemoteReceiver::isReceiving());
//Switch devices off
blokkerSwitch.sendSignal(5,false);
blokkerSwitch.sendSignal(6,false);
blokkerSwitch.sendSignal(7,false);
}
}

+ 52
- 0
lib/RemoteSwitch/examples/Retransmitter/Retransmitter.pde View File

@ -0,0 +1,52 @@
#include <RemoteReceiver.h>
#include <RemoteSwitch.h>
/*
* Demo for RF remote switch receiver.
* For details, see RemoteReceiver.h!
*
* This sketch demonstrates how to use the static version of
* RemoteReceiver::sendTelegram, which can be used in low-memory
* situations.
*
* Connect the transmitter to digital pin 11, and the receiver to digital pin 2.
*
* When run, this sketch waits for a valid code from the receiver, decodes it,
* and retransmits it after 5 seconds.
*/
void setup() {
//See example Show_received_code for info on this
RemoteReceiver::init(0, 3, showCode);
}
void loop() {
}
void showCode(unsigned long receivedCode, unsigned int period) {
//Disable the receiver; otherwise it might pick up the retransmit as well.
RemoteReceiver::disable();
//Need interrupts for delay
interrupts();
unsigned long code;
//Copy the received code.
code = receivedCode & 0xFFFFF; //truncate to 20 bits for show; receivedCode is never more than 20 bits..
//Add the period duration to the code. Range: [0..511] (9 bit)
code |= (unsigned long)period << 23;
//Add the number of repeats to the code. Range: [0..7] (3 bit). The actual number of repeats will be 2^(repeats),
//in this case 8
code |= 3L << 20;
//Wait 5 seconds before sending.
delay(5000);
//Retransmit the signal on pin 11. Note: no object was created!
RemoteSwitch::sendTelegram(code,11);
RemoteReceiver::enable();
}

+ 39
- 0
lib/RemoteSwitch/examples/Show_received_code/Show_received_code.pde View File

@ -0,0 +1,39 @@
#include <RemoteReceiver.h>
/*
* Demo for RF remote switch receiver.
* For details, see RemoteReceiver.h!
*
* This sketch shows the received signals on the serial port.
* Connect the receiver to digital pin 2.
*/
void setup() {
Serial.begin(115200);
//Initialize receiver on interrupt 0 (= digital pin 2), calls the callback "showCode"
//after 3 identical codes have been received in a row. (thus, keep the button pressed
//for a moment)
//
//See the interrupt-parameter of attachInterrupt for possible values (and pins)
//to connect the receiver.
RemoteReceiver::init(0, 3, showCode);
}
void loop() {
}
//Callback function is called only when a valid code is received.
void showCode(unsigned long receivedCode, unsigned int period) {
//Note: interrupts are disabled. You can re-enable them if needed.
//Print the received code.
Serial.print("Code: ");
Serial.print(receivedCode);
Serial.print(", period duration: ");
Serial.print(period);
Serial.println("us.");
}

+ 12
- 0
lib/RemoteSwitch/keywords.txt View File

@ -0,0 +1,12 @@
RemoteSwitch KEYWORD1
ActionSwitch KEYWORD1
BlokkerSwitch KEYWORD1
KaKuSwitch KEYWORD1
sendTelegram KEYWORD2
sendSignal KEYWORD2
isSameCode KEYWORD2
RemoteReceiver KEYWORD1
init KEYWORD2
enable KEYWORD2
disable KEYWORD2
isReceiving KEYWORD2

+ 25
- 0
lib/RemoteSwitch/license.txt View File

@ -0,0 +1,25 @@
Copyright 2010 Randy Simons. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY RANDY SIMONS ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RANDY SIMONS OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those of the
authors and should not be interpreted as representing official policies, either expressed
or implied, of Randy Simons.

+ 7
- 1
platformio.ini View File

@ -26,11 +26,17 @@ framework = arduino
board = esp01_1m board = esp01_1m
lib_install = 89 lib_install = 89
[env:node]
platform = espressif
framework = arduino
board = nodemcuv2
lib_install = 89
[env:ota] [env:ota]
platform = espressif platform = espressif
framework = arduino framework = arduino
board = esp01_1m board = esp01_1m
lib_install = 89 lib_install = 89
upload_speed = 115200 upload_speed = 115200
upload_port = "192.168.4.1"
upload_port = "192.168.1.109"
upload_flags = --auth=fibonacci --port 8266 upload_flags = --auth=fibonacci --port 8266

+ 496
- 301
src/code.ino View File

@ -25,6 +25,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <PubSubClient.h> #include <PubSubClient.h>
#include <DebounceEvent.h> #include <DebounceEvent.h>
#include <ArduinoOTA.h> #include <ArduinoOTA.h>
#include <RemoteReceiver.h>
#include "FS.h" #include "FS.h"
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -33,6 +34,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define DEBUG #define DEBUG
#define ENABLE_RF 1
#define ENABLE_OTA 1
#define ENABLE_MQTT 1
#define ENABLE_WEBSERVER 1
#define APP_NAME "Espurna" #define APP_NAME "Espurna"
#define MAX_VERSION 0 #define MAX_VERSION 0
#define MIN_VERSION 9 #define MIN_VERSION 9
@ -44,59 +50,85 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define RELAY_PIN 12 #define RELAY_PIN 12
#define LED_PIN 13 #define LED_PIN 13
#define AP_PASS "fibonacci"
#define OTA_PASS "fibonacci"
#define BUFFER_SIZE 1024
#define ADMIN_PASS "fibonacci"
#define CONFIG_PATH "/.config" #define CONFIG_PATH "/.config"
#define WIFI_CONNECT_TIMEOUT 5000 #define WIFI_CONNECT_TIMEOUT 5000
#define WIFI_RECONNECT_DELAY 5000 #define WIFI_RECONNECT_DELAY 5000
#define MQTT_RECONNECT_DELAY 10000 #define MQTT_RECONNECT_DELAY 10000
#define NETWORK_BUFFER 3 #define NETWORK_BUFFER 3
#define BUFFER_SIZE 1024
#define RF_PIN 14
#define RF_CHANNEL 31
#define RF_DEVICE 1
#define MQTT_RETAIN true
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Globals // Globals
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
ESP8266WebServer server(80);
WiFiClient client;
PubSubClient mqtt(client);
char identifier[20] = {0}; char identifier[20] = {0};
byte network = 0; byte network = 0;
String config_ssid[NETWORK_BUFFER]; String config_ssid[NETWORK_BUFFER];
String config_pass[NETWORK_BUFFER]; String config_pass[NETWORK_BUFFER];
String mqtt_server = "192.168.1.100";
String mqtt_topic = "/test/switch/{identifier}";
String mqtt_port = "1883";
char mqtt_subscribe_to[30];
char mqtt_publish_to[30];
DebounceEvent button1 = false; DebounceEvent button1 = false;
#if ENABLE_WEBSERVER
ESP8266WebServer server(80);
#endif
#if ENABLE_MQTT
WiFiClient client;
PubSubClient mqtt(client);
String mqtt_server = "192.168.1.100";
String mqtt_topic = "/test/switch/{identifier}";
String mqtt_port = "1883";
char mqtt_subscribe_status[30];
char mqtt_publish_status[30];
char mqtt_publish_ip[30];
#endif
#if ENABLE_RF
unsigned long rf_code = 0;
unsigned long rf_code_on = 0;
unsigned long rf_code_off = 0;
String rf_channel = String(RF_CHANNEL);
String rf_device = String(RF_DEVICE);
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Relay // Relay
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void switchRelayOn() { void switchRelayOn() {
#ifdef DEBUG
Serial.println("Turning the relay ON");
#endif
if (mqtt.connected()) {
mqtt.publish(mqtt_publish_to, "1");
if (!digitalRead(RELAY_PIN)) {
#ifdef DEBUG
Serial.println("Turning the relay ON");
#endif
#if ENABLE_MQTT
if (mqtt.connected()) {
mqtt.publish(mqtt_publish_status, "1", MQTT_RETAIN);
}
#endif
digitalWrite(RELAY_PIN, HIGH);
} }
digitalWrite(RELAY_PIN, HIGH);
} }
void switchRelayOff() { void switchRelayOff() {
#ifdef DEBUG
Serial.println("Turning the relay OFF");
#endif
if (mqtt.connected()) {
mqtt.publish(mqtt_publish_to, "0");
if (digitalRead(RELAY_PIN)) {
#ifdef DEBUG
Serial.println("Turning the relay OFF");
#endif
#if ENABLE_MQTT
if (mqtt.connected()) {
mqtt.publish(mqtt_publish_status, "0", MQTT_RETAIN);
}
#endif
digitalWrite(RELAY_PIN, LOW);
} }
digitalWrite(RELAY_PIN, LOW);
} }
void toggleRelay() { void toggleRelay() {
@ -111,186 +143,213 @@ void toggleRelay() {
// WebServer // WebServer
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
String getContentType(String filename) {
if (server.hasArg("download")) return "application/octet-stream";
else if (filename.endsWith(".htm")) return "text/html";
else if (filename.endsWith(".html")) return "text/html";
else if (filename.endsWith(".css")) return "text/css";
else if (filename.endsWith(".js")) return "application/javascript";
else if (filename.endsWith(".png")) return "image/png";
else if (filename.endsWith(".gif")) return "image/gif";
else if (filename.endsWith(".jpg")) return "image/jpeg";
else if (filename.endsWith(".ico")) return "image/x-icon";
else if (filename.endsWith(".xml")) return "text/xml";
else if (filename.endsWith(".pdf")) return "application/x-pdf";
else if (filename.endsWith(".zip")) return "application/x-zip";
else if (filename.endsWith(".gz")) return "application/x-gzip";
return "text/plain";
}
#if ENABLE_WEBSERVER
String getContentType(String filename) {
if (server.hasArg("download")) return "application/octet-stream";
else if (filename.endsWith(".htm")) return "text/html";
else if (filename.endsWith(".html")) return "text/html";
else if (filename.endsWith(".css")) return "text/css";
else if (filename.endsWith(".js")) return "application/javascript";
else if (filename.endsWith(".png")) return "image/png";
else if (filename.endsWith(".gif")) return "image/gif";
else if (filename.endsWith(".jpg")) return "image/jpeg";
else if (filename.endsWith(".ico")) return "image/x-icon";
else if (filename.endsWith(".xml")) return "text/xml";
else if (filename.endsWith(".pdf")) return "application/x-pdf";
else if (filename.endsWith(".zip")) return "application/x-zip";
else if (filename.endsWith(".gz")) return "application/x-gzip";
return "text/plain";
}
void handleRelayOn() {
#ifdef DEBUG
Serial.println("Request: /on");
#endif
switchRelayOn();
server.send(200, "text/plain", "ON");
}
void handleRelayOn() {
#ifdef DEBUG
Serial.println("Request: /on");
#endif
switchRelayOn();
server.send(200, "text/plain", "ON");
}
void handleRelayOff() {
#ifdef DEBUG
Serial.println("Request: /off");
#endif
switchRelayOff();
server.send(200, "text/plain", "OFF");
}
void handleRelayOff() {
#ifdef DEBUG
Serial.println("Request: /off");
#endif
switchRelayOff();
server.send(200, "text/plain", "OFF");
}
bool handleFileRead(String path) {
bool handleFileRead(String path) {
#ifdef DEBUG
Serial.println("Request: " + path);
#endif
#ifdef DEBUG
Serial.println("Request: " + path);
#endif
if (path.endsWith("/")) path += "index.html";
String contentType = getContentType(path);
String pathWithGz = path + ".gz";
if (path.endsWith("/")) path += "index.html";
String contentType = getContentType(path);
String pathWithGz = path + ".gz";
if (SPIFFS.exists(pathWithGz)) path = pathWithGz;
if (SPIFFS.exists(path)) {
File file = SPIFFS.open(path, "r");
size_t sent = server.streamFile(file, contentType);
size_t contentLength = file.size();
file.close();
return true;
}
return false;
if (SPIFFS.exists(pathWithGz)) path = pathWithGz;
if (SPIFFS.exists(path)) {
File file = SPIFFS.open(path, "r");
size_t sent = server.streamFile(file, contentType);
size_t contentLength = file.size();
file.close();
return true;
} }
return false;
void handleHome() {
}
#ifdef DEBUG
Serial.println("Request: /index.html");
#endif
void handleHome() {
String filename = "/index.html";
String content = "";
char buffer[BUFFER_SIZE];
// Read file in chunks
File file = SPIFFS.open(filename, "r");
int size = file.size();
while (size > 0) {
size_t len = std::min(BUFFER_SIZE-1, size);
file.read((uint8_t *) buffer, len);
buffer[len] = 0;
content += buffer;
size -= len;
}
file.close();
#ifdef DEBUG
Serial.println("Request: /index.html");
#endif
// Replace placeholders
content.replace("{appname}", APP_NAME);
content.replace("{appversion}", String(MAX_VERSION) + String(".") + String(MIN_VERSION));
content.replace("{ssid0}", config_ssid[0]);
content.replace("{pass0}", config_pass[0]);
content.replace("{ssid1}", config_ssid[1]);
content.replace("{pass1}", config_pass[1]);
content.replace("{ssid2}", config_ssid[2]);
content.replace("{pass2}", config_pass[2]);
#if ENABLE_MQTT
content.replace("{mqtt_server}", mqtt_server);
content.replace("{mqtt_port}", mqtt_port);
content.replace("{mqtt_topic}", mqtt_topic);
#endif
#if ENABLE_RF
content.replace("{rf_channel}", rf_channel);
content.replace("{rf_device}", rf_device);
#endif
String filename = "/index.html";
String content = "";
char buffer[BUFFER_SIZE];
// Read file in chunks
File file = SPIFFS.open(filename, "r");
int size = file.size();
while (size > 0) {
size_t len = std::min(BUFFER_SIZE-1, size);
file.read((uint8_t *) buffer, len);
buffer[len] = 0;
content += buffer;
size -= len;
}
file.close();
// Serve content
String contentType = getContentType(filename);
server.send(200, contentType, content);
// Replace placeholders
if (WiFi.status() == WL_CONNECTED) {
content.replace("{status}", "Client + Acces Point");
content.replace("{network}", config_ssid[network]);
content.replace("{ip}", WiFi.localIP().toString());
} else {
content.replace("{status}", "Acces Point");
content.replace("{network}", "");
content.replace("{ip}", "");
} }
content.replace("{ssid0}", config_ssid[0]);
content.replace("{pass0}", config_pass[0]);
content.replace("{ssid1}", config_ssid[1]);
content.replace("{pass1}", config_pass[1]);
content.replace("{ssid2}", config_ssid[2]);
content.replace("{pass2}", config_pass[2]);
content.replace("{mqtt_server}", mqtt_server);
content.replace("{mqtt_port}", mqtt_port);
content.replace("{mqtt_topic}", mqtt_topic);
// Serve content
String contentType = getContentType(filename);
server.send(200, contentType, content);
}
void handleSave() {
void handleSave() {
#ifdef DEBUG
Serial.println("Request: /save");
#endif
#ifdef DEBUG
Serial.println("Request: /save");
#endif
config_ssid[0] = server.arg("ssid0");
config_pass[0] = server.arg("pass0");
config_ssid[1] = server.arg("ssid1");
config_pass[1] = server.arg("pass1");
config_ssid[2] = server.arg("ssid2");
config_pass[2] = server.arg("pass2");
#if ENABLE_MQTT
mqtt_server = server.arg("mqtt_server");
mqtt_port = server.arg("mqtt_port");
mqtt_topic = server.arg("mqtt_topic");
#endif
#if ENABLE_RF
rf_channel = server.arg("rf_channel");
rf_device = server.arg("rf_device");
#endif
server.send(202, "text/json", "{}");
saveConfig();
#if ENABLE_RF
rfBuildCodes();
#endif
network = 0;
wifiSetup(true);
config_ssid[0] = server.arg("ssid0");
config_pass[0] = server.arg("pass0");
config_ssid[1] = server.arg("ssid1");
config_pass[1] = server.arg("pass1");
config_ssid[2] = server.arg("ssid2");
config_pass[2] = server.arg("pass2");
mqtt_server = server.arg("mqtt_server");
mqtt_port = server.arg("mqtt_port");
mqtt_topic = server.arg("mqtt_topic");
saveConfig();
network = 0;
wifiSetup();
delay(100);
String output = "{";
output += "\"status\": \"";
if (WiFi.status() == WL_CONNECTED) {
output += "Client + Acces Point";
} else {
output += "Acces Point";
} }
output += "\", \"ip\": \"";
if (WiFi.status() == WL_CONNECTED) {
output += WiFi.localIP().toString();
void handleStatus() {
#ifdef DEBUG
//Serial.println("Request: /status");
#endif
String output = "{ ";
output += "\"wifi\": ";
output += (WiFi.status() == WL_CONNECTED) ? "1": "0";
if ((WiFi.status() == WL_CONNECTED)) {
output += ", \"network\": \"";
output += WiFi.SSID();
output += "\", \"ip\": \"";
output += WiFi.localIP().toString();
output += "\"";
}
#if ENABLE_MQTT
output += ", \"mqtt\": ";
output += (mqtt.connected()) ? "1": "0";
#endif
output += ", \"relay\": ";
output += (digitalRead(RELAY_PIN)) ? "1": "0";
output += " }";
server.send(200, "text/json", output);
} }
output += "\" }";
server.send(200, "text/json", output);
}
void webServerSetup() {
void webServerSetup() {
// Relay control
server.on("/relay/on", HTTP_GET, handleRelayOn);
server.on("/relay/off", HTTP_GET, handleRelayOff);
// Relay control
server.on("/on", HTTP_GET, handleRelayOn);
server.on("/off", HTTP_GET, handleRelayOff);
// Configuration page
server.on("/save", HTTP_POST, handleSave);
server.on("/status", HTTP_GET, handleStatus);
server.on("/", HTTP_GET, handleHome);
server.on("/index.html", HTTP_GET, handleHome);
// Configuration page
server.on("/save", HTTP_POST, handleSave);
server.on("/", HTTP_GET, handleHome);
server.on("/index.html", HTTP_GET, handleHome);
// Anything else
server.onNotFound([]() {
// Anything else
server.onNotFound([]() {
// Hidden files
if (server.uri().startsWith("/.")) {
server.send(403, "text/plain", "Forbidden");
return;
}
// Hidden files
if (server.uri().startsWith("/.")) {
server.send(403, "text/plain", "Forbidden");
return;
}
// Existing files in SPIFFS
if (!handleFileRead(server.uri())) {
server.send(404, "text/plain", "NotFound");
return;
}
// Existing files in SPIFFS
if (!handleFileRead(server.uri())) {
server.send(404, "text/plain", "NotFound");
return;
}
});
});
// Run server
server.begin();
// Run server
server.begin();
}
}
void webServerLoop() {
server.handleClient();
}
void webServerLoop() {
server.handleClient();
}
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Wifi modes
// Wifi
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
char * getIdentifier() { char * getIdentifier() {
@ -305,13 +364,35 @@ char * getIdentifier() {
return identifier; return identifier;
} }
void wifiSetup() {
void wifiSetup(bool force) {
// Disconnect MQTT
if (mqtt.connected()) mqtt.disconnect();
if ((!force) && (WiFi.status() == WL_CONNECTED)) return;
#if ENABLE_MQTT
if (mqtt.connected()) mqtt.disconnect();
#endif
#if ENABLE_RF
RemoteReceiver::disable();
#endif
// STA mode
WiFi.mode(WIFI_AP_STA); WiFi.mode(WIFI_AP_STA);
#ifdef DEBUG
WiFi.printDiag(Serial);
#endif
// SoftAP mode
WiFi.softAP(getIdentifier(), ADMIN_PASS);
#ifdef DEBUG
Serial.print("AP Mode: ");
Serial.print(getIdentifier());
Serial.print("/");
Serial.print(ADMIN_PASS);
Serial.print(", IP address: ");
Serial.println(WiFi.softAPIP());
#endif
// STA mode
if (config_ssid[network].length() > 0) { if (config_ssid[network].length() > 0) {
char ssid[config_ssid[network].length()+1]; char ssid[config_ssid[network].length()+1];
@ -341,6 +422,7 @@ void wifiSetup() {
#endif #endif
if (WiFi.status() == WL_CONNECTED) { if (WiFi.status() == WL_CONNECTED) {
WiFi.setAutoConnect(true);
#ifdef DEBUG #ifdef DEBUG
Serial.println(WiFi.localIP()); Serial.println(WiFi.localIP());
#endif #endif
@ -353,16 +435,8 @@ void wifiSetup() {
} }
if (WiFi.status() != WL_CONNECTED) WiFi.mode(WIFI_AP);
WiFi.softAP(getIdentifier(), AP_PASS);
#ifdef DEBUG
Serial.print("AP Mode: ");
Serial.print(getIdentifier());
Serial.print("/");
Serial.print(AP_PASS);
Serial.print(", IP address: ");
Serial.println(WiFi.softAPIP());
#if ENABLE_RF
RemoteReceiver::enable();
#endif #endif
} }
@ -371,7 +445,10 @@ void wifiLoop() {
static unsigned long timeout = millis(); static unsigned long timeout = millis();
if (WiFi.status() != WL_CONNECTED) { if (WiFi.status() != WL_CONNECTED) {
if (timeout < millis()) { if (timeout < millis()) {
wifiSetup();
#if ENABLE_RF
//RemoteReceiver::disable();
#endif
wifiSetup(false);
timeout = millis() + WIFI_RECONNECT_DELAY; timeout = millis() + WIFI_RECONNECT_DELAY;
} }
} }
@ -381,97 +458,169 @@ void wifiLoop() {
// MQTT // MQTT
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void buildTopics() {
#if ENABLE_MQTT
// Replace identifier
String base = mqtt_topic;
base.replace("{identifier}", getIdentifier());
void buildTopics() {
// Get publish topic
base.toCharArray(mqtt_publish_to, base.length()+1);
mqtt_publish_to[base.length()+1] = 0;
String tmp;
// Get subscribe topic
String subscribe = base + "/set";
subscribe.toCharArray(mqtt_subscribe_to, subscribe.length()+1);
mqtt_subscribe_to[subscribe.length()+1] = 0;
// Replace identifier
String base = mqtt_topic;
base.replace("{identifier}", getIdentifier());
}
// Get publish status topic
base.toCharArray(mqtt_publish_status, base.length()+1);
mqtt_publish_status[base.length()+1] = 0;
void mqttCallback(char* topic, byte* payload, unsigned int length) {
// Get publish ip topic
tmp = base + "/ip";
tmp.toCharArray(mqtt_publish_ip, tmp.length()+1);
mqtt_publish_ip[tmp.length()+1] = 0;
#ifdef DEBUG
Serial.print("MQTT message ");
Serial.print(topic);
Serial.print(" => ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
#endif
// Get subscribe status topic
tmp = base + "/set";
tmp.toCharArray(mqtt_subscribe_status, tmp.length()+1);
mqtt_subscribe_status[tmp.length()+1] = 0;
if ((char)payload[0] == '1') {
switchRelayOn();
} else {
switchRelayOff();
} }
}
void mqttCallback(char* topic, byte* payload, unsigned int length) {
void mqttConnect() {
#ifdef DEBUG
Serial.print("MQTT message ");
Serial.print(topic);
Serial.print(" => ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
#endif
if (!mqtt.connected()) {
if ((char)payload[0] == '1') {
switchRelayOn();
} else {
switchRelayOff();
}
char buffer[mqtt_server.length()+1];
mqtt_server.toCharArray(buffer, mqtt_server.length()+1);
mqtt.setServer(buffer, mqtt_port.toInt());
}
#ifdef DEBUG
Serial.print("Connecting to MQTT broker: ");
#endif
void mqttConnect() {
if (mqtt.connect(getIdentifier())) {
if (!mqtt.connected()) {
buildTopics();
char buffer[mqtt_server.length()+1];
mqtt_server.toCharArray(buffer, mqtt_server.length()+1);
mqtt.setServer(buffer, mqtt_port.toInt());
#ifdef DEBUG #ifdef DEBUG
Serial.println("connected!");
Serial.print("Subscribing to ");
Serial.println(mqtt_subscribe_to);
Serial.print("Connecting to MQTT broker: ");
#endif #endif
mqtt.publish(mqtt_publish_to, "HOLA");
mqtt.subscribe(mqtt_subscribe_to);
if (mqtt.connect(getIdentifier())) {
buildTopics();
} else {
#ifdef DEBUG
Serial.println("connected!");
Serial.print("Subscribing to ");
Serial.println(mqtt_subscribe_status);
#endif
#ifdef DEBUG
Serial.print("failed, rc=");
Serial.println(mqtt.state());
#endif
String ipString = WiFi.localIP().toString();
char ip[ipString.length()+1];
ipString.toCharArray(ip, ipString.length()+1);
mqtt.publish(mqtt_publish_ip, ip, MQTT_RETAIN);
mqtt.subscribe(mqtt_subscribe_status);
} else {
#ifdef DEBUG
Serial.print("failed, rc=");
Serial.println(mqtt.state());
#endif
}
} }
}
}
}
void mqttSetup() {
mqtt.setCallback(mqttCallback);
}
void mqttSetup() {
mqtt.setCallback(mqttCallback);
}
void mqttLoop() {
static unsigned long timeout = millis();
if (WiFi.status() == WL_CONNECTED) {
if (!mqtt.connected()) {
if (timeout < millis()) {
mqttConnect();
timeout = millis() + MQTT_RECONNECT_DELAY;
void mqttLoop() {
static unsigned long timeout = millis();
if (WiFi.status() == WL_CONNECTED) {
if (!mqtt.connected()) {
if (timeout < millis()) {
mqttConnect();
timeout = millis() + MQTT_RECONNECT_DELAY;
}
} }
if (mqtt.connected()) mqtt.loop();
} }
if (mqtt.connected()) mqtt.loop();
} }
}
#endif
// -----------------------------------------------------------------------------
// RF
// -----------------------------------------------------------------------------
#if ENABLE_RF
void rfLoop() {
if (rf_code == 0) return;
Serial.print("RF code: ");
Serial.println(rf_code);
if (rf_code == rf_code_on ) switchRelayOn();
if (rf_code == rf_code_off ) switchRelayOff();
rf_code = 0;
}
void rfBuildCodes() {
unsigned long code = 0;
// channel
unsigned int channel = rf_channel.toInt();
for (byte i = 0; i < 5; i++) {
code *= 3;
if (channel & 1) code += 1;
channel >>= 1;
}
// device
unsigned int device = rf_device.toInt();
for (byte i = 0; i < 5; i++) {
code *= 3;
if (device != i) code += 2;
}
// status
code *= 9;
rf_code_off = code + 2;
rf_code_on = code + 6;
Serial.print("RF code ON: ");
Serial.println(rf_code_on);
Serial.print("RF code OFF: ");
Serial.println(rf_code_off);
}
void rfCallback(unsigned long code, unsigned int period) {
rf_code = code;
}
void rfSetup() {
rfBuildCodes();
RemoteReceiver::init(RF_PIN, 3, rfCallback);
RemoteReceiver::enable();
}
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Configuration // Configuration
@ -486,9 +635,15 @@ bool saveConfig() {
file.println("pass1=" + config_pass[1]); file.println("pass1=" + config_pass[1]);
file.println("ssid2=" + config_ssid[2]); file.println("ssid2=" + config_ssid[2]);
file.println("pass2=" + config_pass[2]); file.println("pass2=" + config_pass[2]);
file.println("mqtt_server=" + mqtt_server);
file.println("mqtt_port=" + mqtt_port);
file.println("mqtt_topic=" + mqtt_topic);
#if ENABLE_MQTT
file.println("mqtt_server=" + mqtt_server);
file.println("mqtt_port=" + mqtt_port);
file.println("mqtt_topic=" + mqtt_topic);
#endif
#if ENABLE_RF
file.println("rf_channel=" + rf_channel);
file.println("rf_device=" + rf_device);
#endif
file.close(); file.close();
return true; return true;
} }
@ -525,9 +680,15 @@ bool loadConfig() {
else if (line.startsWith("pass1=")) config_pass[1] = line.substring(6); else if (line.startsWith("pass1=")) config_pass[1] = line.substring(6);
else if (line.startsWith("ssid2=")) config_ssid[2] = line.substring(6); else if (line.startsWith("ssid2=")) config_ssid[2] = line.substring(6);
else if (line.startsWith("pass2=")) config_pass[2] = line.substring(6); else if (line.startsWith("pass2=")) config_pass[2] = line.substring(6);
else if (line.startsWith("mqtt_server=")) mqtt_server = line.substring(12);
else if (line.startsWith("mqtt_port=")) mqtt_port = line.substring(10);
else if (line.startsWith("mqtt_topic=")) mqtt_topic = line.substring(11);
#if ENABLE_MQTT
else if (line.startsWith("mqtt_server=")) mqtt_server = line.substring(12);
else if (line.startsWith("mqtt_port=")) mqtt_port = line.substring(10);
else if (line.startsWith("mqtt_topic=")) mqtt_topic = line.substring(11);
#endif
#if ENABLE_RF
else if (line.startsWith("rf_channel=")) rf_channel = line.substring(11);
else if (line.startsWith("rf_device=")) rf_device = line.substring(10);
#endif
if (end < 0) break; if (end < 0) break;
start = end + 1; start = end + 1;
end = content.indexOf("\n", start); end = content.indexOf("\n", start);
@ -543,53 +704,63 @@ bool loadConfig() {
// OTA // OTA
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void OTASetup() {
#if ENABLE_OTA
// Port defaults to 8266
ArduinoOTA.setPort(8266);
void OTASetup() {
// Hostname defaults to esp8266-[ChipID]
ArduinoOTA.setHostname(getIdentifier());
// Port defaults to 8266
ArduinoOTA.setPort(8266);
// No authentication by default
ArduinoOTA.setPassword((const char *) OTA_PASS);
// Hostname defaults to esp8266-[ChipID]
ArduinoOTA.setHostname(getIdentifier());
ArduinoOTA.onStart([]() {
#ifdef DEBUG
Serial.println("OTA - Start");
#endif
});
// No authentication by default
ArduinoOTA.setPassword((const char *) ADMIN_PASS);
ArduinoOTA.onEnd([]() {
#ifdef DEBUG
Serial.println("OTA - End");
#endif
});
ArduinoOTA.onStart([]() {
#ifdef ENABLE_RF
RemoteReceiver::disable();
#endif
#ifdef DEBUG
Serial.println("OTA - Start");
#endif
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
#ifdef DEBUG
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
#endif
});
ArduinoOTA.onEnd([]() {
#ifdef DEBUG
Serial.println("OTA - End");
#endif
#ifdef ENABLE_RF
RemoteReceiver::enable();
#endif
});
ArduinoOTA.onError([](ota_error_t error) {
#ifdef DEBUG
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
#endif
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
#ifdef DEBUG
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
#endif
});
ArduinoOTA.begin();
ArduinoOTA.onError([](ota_error_t error) {
#ifdef DEBUG
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
#endif
});
}
ArduinoOTA.begin();
void OTALoop() {
ArduinoOTA.handle();
}
}
void OTALoop() {
ArduinoOTA.handle();
}
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Hardware (buttons, LEDs,...) // Hardware (buttons, LEDs,...)
@ -599,7 +770,10 @@ void hardwareSetup() {
Serial.begin(115200); Serial.begin(115200);
pinMode(RELAY_PIN, OUTPUT); pinMode(RELAY_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT); pinMode(LED_PIN, OUTPUT);
pinMode(RF_PIN, INPUT_PULLUP);
button1 = DebounceEvent(BUTTON_PIN); button1 = DebounceEvent(BUTTON_PIN);
//switchRelayOff();
SPIFFS.begin();
} }
void blink(unsigned long delayOff, unsigned long delayOn) { void blink(unsigned long delayOff, unsigned long delayOn) {
@ -647,23 +821,44 @@ void welcome() {
} }
void setup() { void setup() {
hardwareSetup(); hardwareSetup();
SPIFFS.begin();
delay(5000); delay(5000);
welcome(); welcome();
OTASetup();
switchRelayOff();
#if ENABLE_OTA
OTASetup();
#endif
loadConfig(); loadConfig();
wifiSetup();
webServerSetup();
mqttSetup();
wifiSetup(false);
#if ENABLE_WEBSERVER
webServerSetup();
#endif
#if ENABLE_MQTT
mqttSetup();
#endif
#if ENABLE_RF
rfSetup();
#endif
} }
void loop() { void loop() {
OTALoop();
#if ENABLE_OTA
OTALoop();
#endif
wifiLoop(); wifiLoop();
webServerLoop();
mqttLoop();
#if ENABLE_MQTT
mqttLoop();
#endif
#if ENABLE_RF
rfLoop();
#endif
#if ENABLE_WEBSERVER
webServerLoop();
#endif
hardwareLoop(); hardwareLoop();
delay(1);
delay(10);
} }

Loading…
Cancel
Save