This is the code running in the ESP8266 to control the fan. (Easier to read if you copy it to a language sensitive editor. E.g. Arduino IDE or Notepad++)
/**************************************************************************
Started with Adafruit Oled example.
RDL Changes...
- remove calling most of their demos.
- add setting a few points. - removed.
- It works on ESP!
- Changed to ESP_Oled_Demo
Saved 01
- Added BME280 Temperature and Humidity (printing on Serial).
Saved 02
- Added Web, OTA, UDP response. (Not tested OTA update, led on/off).
Saved 03
- Added 2 Leds, tested them on D4, D8, D7, D6.
- Fns clrScr, scrWr, scrW2 added testing writing to simulate oled scrolling.
Saved 04
- Added more display text.
- Added code to read Dallas DS18B20 * 2. (not tested).
Saved 05
- Added display of values on screen.
Saved 06
??
Saved 07
- a few tweaks, mainly format.
Saved 08
- Web-page items for ard_count, temperature, humid, press.
- Message protocol expanded for more vars.
- New on,off buttons, and reflected state.
- On, Off buttons drive D0 fan control
Saved 09
- Correct initial state of FanOn display.
Saved 10
- Removed dead code.
Saved 11
- WiFi P9 Member (was Guest)
- Some web-page reformatting.
- Fan stuck signal on web page.
- Coloured fan state on web-page.
Saved 12
Saved to ESP_Fan_Speed
- Commented out Dallas, BME270, OLEd.
- Fan on/off web buttons working.
- Fan speed web buttons working.
- Red led toggle web button working.
- Green led flashing with fan turning (can't keep up at hi speed).
Saved 01
- From ESP_Interrupt_Test copied interrupt and timer. Now fan flashes
green led, timer flashes red led, console prints fan speed.
Saved 02
- Fan SL on web-page, Fan Speed on Web-Page.
Saved 03
- UDP broadcast each 15 sec.
- UDP response to A with the same msg.
Saved 04
***************************************/
// Import required libraries
// For WiFi and OTA
#include <Arduino.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <AsyncElegantOTA.h>
#include <WiFiUdp.h>
// For timer interrupts
#include <Ticker.h>
#define pM pinMode
#define dR digitalRead
#define aW analogWrite
#define dW digitalWrite
#define rcv_pkt_size 100 // examples had another value, WMini crashed with that
const boolean TT = true;
const boolean FF = false;
float DTTemp0;
float DTTemp1;
// Network credentials
const char* ssid1 = "PLATF9RM Hove Member"; const char* password1 = "???";
const char* ssid0 = "MyWiFiNet"; const char* password0 = "???";
WiFiUDP Udp;
unsigned int localUdpPort = 4210; // local port to listen on
char incomingPacket[255]; // buffer for incoming packets
char replyPacket[] = "Hi there! received your message :-)"; // a reply string to send back
int secs = 0; // 0..59
int mins = 0; // 0..59
int hors = 0; // 0..23
int days = 0;
bool ledState = 0;
const int fanOn_Pin = D5; // fan on-off
const int fanSL_Pin = D6;
const int fanPV_Pin = D4; // Pulse input
const int ledGr_Pin = D2;
const int ledRd_Pin = D3;
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<title>ESP Fan Speed Control</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<style>
html {font-family: Arial, Helvetica, sans-serif;text-align: left;}
h1 {font-size: 1.8rem; color: white;}
h2{font-size: 1.5rem; font-weight: bold; color: #143642;}
.topnav {overflow: hidden; background-color: #143642;}
body {margin: 0;}
.content {padding: 30px; max-width: 600px; margin: 0 auto;}
.card {
background-color: #F8F7F9;;
box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
padding-top:10px;
padding-bottom:20px;
}
.button {
padding: 12px 20px;
font-size: 24px;
text-align: center;
outline: none;
color: #fff;
background-color: #0f8b8d;
border: none;
border-radius: 5px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
/*.button:hover {background-color: #0f8b8d}*/
.button:active {
background-color: #0f8b8d;
box-shadow: 2 2px #CDCDCD;
transform: translateY(2px);
}
.state {
padding-right: 35px;
font-size: 2.0rem;
color:#4c4c4c;
font-weight: bold;
}
.r1 {
margin: 0;
padding-top: 0px;
padding-bottom: 0px;
font-size: 1.5rem;
color:#50D010;
}
</style>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
</head>
<body>
<div class="topnav">
<h1>ESP8266 Fan Speed Control</h1>
</div>
<div class="content">
<div class="card">
<p><button id="button" class="button">Toggle Red LED.</button> <span id="stat1" class="state">%STATE%</span></p>
<p><button id="buttn2" class="button">Fan Off</button>
<button id="buttn3" class="button">Fan On</button>
<span id="FN" class="state">%FanOn%</span></p>
<p>
<button id="ButFS1" class="button">Fan Speed 1</button>
<button id="ButFS2" class="button">2</button>
<button id="ButFS3" class="button">3</button>
<button id="ButFS4" class="button">4</button>
<button id="ButFS5" class="button">5</button>
<span id="FSL" class="state">%FanSL%</span>
<span id="FSPV" class="state">%FanPV%</span>
</p>
<p class="r1"><span>ESP Count: </span> <span id="EC">EC</span></p>
<p class="r1"><span>EC mod 57: </span> <span id="T0">T0</span></p>
<p class="r1"><span>EC mod 31: </span> <span id="T1">T1</span></p>
<p>More info and source code at:</p>
<p>sites.google.com/view/processing-and-arduino-notes.</p>
<p>Thats a reference, but not a link, because the link made an error.</p>
<p>You need internet, not ESP WiFi.</p>
<p>On that page, near the end, see 'ESP8266 Fan Control'.</p>
</div>
</div>
<script>
var gateway = `ws://${window.location.hostname}/ws`;
var websocket;
window.addEventListener('load', onLoad);
function initWebSocket() {
console.log('G ' + gateway);
console.log('Trying to open a WebSocket connection...');
websocket = new WebSocket(gateway);
websocket.onopen = onOpen;
websocket.onclose = onClose;
websocket.onmessage = onMessage; // <-- add this line
}
function onOpen(event) {console.log('Connection opened');}
function onClose(event) {console.log('Connection closed'); setTimeout(initWebSocket, 2000);}
function onMessage(event) {
var state;
var ary;
var id;
var colour;
//console.log(event.data);
if (event.data == "1") {state = "ON" ;
document.getElementById('stat1').innerHTML = state;
}
if (event.data == "0") {state = "OFF";
document.getElementById('stat1').innerHTML = state;
}
ary = event.data.split(",");
id = 'EC'; if (ary[0] == id) {document.getElementById(id).innerHTML = ary[1];}
id = 'T0'; if (ary[0] == id) {document.getElementById(id).innerHTML = ary[1];}
id = 'T1'; if (ary[0] == id) {document.getElementById(id).innerHTML = ary[1];}
id = 'FN'; if (ary[0] == id) {document.getElementById(id).innerHTML = ary[1];}
id = 'FSL'; if (ary[0] == id) {document.getElementById(id).innerHTML = ary[1];}
id = 'FSPV'; if (ary[0] == id) {document.getElementById(id).innerHTML = ary[1];}
}
function onLoad(event) {
initWebSocket();
initButton();
}
function initButton() {
document.getElementById('button').addEventListener('click', toggle);
document.getElementById('buttn3').addEventListener('click', On_);
document.getElementById('buttn2').addEventListener('click', Off);
document.getElementById('ButFS1').addEventListener('click', ButFS1);
document.getElementById('ButFS2').addEventListener('click', ButFS2);
document.getElementById('ButFS3').addEventListener('click', ButFS3);
document.getElementById('ButFS4').addEventListener('click', ButFS4);
document.getElementById('ButFS5').addEventListener('click', ButFS5);
}
function toggle(){websocket.send('toggle');}
function On_(){websocket.send('On_');}
function Off(){websocket.send('Off');}
function ButFS1(){websocket.send('FS1');}
function ButFS2(){websocket.send('FS2');}
function ButFS3(){websocket.send('FS3');}
function ButFS4(){websocket.send('FS4');}
function ButFS5(){websocket.send('FS5');}
</script>
</body>
</html>)rawliteral";
// these relic for smaller fan on other project?
int fanSenseState0 = 0;
int fanSenseState1 = 0; // 0 = normal, 1 = stuck
Ticker blinker;
// SWiF2-80P Fan (Yellow)
int fanOn = 0; // User on/off
int fan_state; // 0/1 as fan goes round
int led_state;
int fan_SL;
int fan_count;
int fan_speed;
ICACHE_RAM_ATTR void fan_ISR() {
// 2 per rev
fan_state = (~fan_state) & 1;
dW(ledGr_Pin, fan_state);
//if (fan_state) {fan_count++;} // 1 per rev
fan_count++;
}
ICACHE_RAM_ATTR void timer_ISR() {
// each 2 seconds
led_state = (~led_state) & 1;
dW(ledRd_Pin, led_state);
fan_speed = fan_count * 15;
Serial.print("Fan Count: ");
Serial.print(fan_count);
Serial.print(" /2Sec "); // half revs per 2 sec.
Serial.print(fan_speed);
Serial.print(" R/min");
Serial.println();
ws.textAll("FSPV," + String(fan_speed)); // R/Min
fan_count = 0;
}
void notifyClients() {
ws.textAll(String(ledState));
}
void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
AwsFrameInfo *info = (AwsFrameInfo*)arg;
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
data[len] = 0;
if (strcmp((char*)data, "toggle") == 0) {
ledState = !ledState;
notifyClients();
}
if (strcmp((char*)data, "Off") == 0) {
fanOn = 0;
dW(fanOn_Pin, 0);
ws.textAll("FN,0");
}
if (strcmp((char*)data, "On_") == 0) {
fanOn = 1;
dW(fanOn_Pin, 1);
ws.textAll("FN,1");
}
if (strcmp((char*)data, "FS1") == 0) {
aW(fanSL_Pin, 1);
fan_SL = 1;
ws.textAll("FSL,1");
}
if (strcmp((char*)data, "FS2") == 0) {
aW(fanSL_Pin, 430);
fan_SL = 2;
ws.textAll("FSL,2");
}
if (strcmp((char*)data, "FS3") == 0) {
aW(fanSL_Pin, 590);
fan_SL = 3;
ws.textAll("FSL,3");
}
if (strcmp((char*)data, "FS4") == 0) {
aW(fanSL_Pin, 680);
fan_SL = 4;
ws.textAll("FSL,4");
}
if (strcmp((char*)data, "FS5") == 0) {
aW(fanSL_Pin, 1023);
fan_SL = 5;
ws.textAll("FSL,5");
}
}
}
void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,
void *arg, uint8_t *data, size_t len) {
switch (type) {
case WS_EVT_CONNECT:
Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
break;
case WS_EVT_DISCONNECT:
Serial.printf("WebSocket client #%u disconnected\n", client->id());
break;
case WS_EVT_DATA:
handleWebSocketMessage(arg, data, len);
break;
case WS_EVT_PONG:
case WS_EVT_ERROR:
break;
}
}
void initWebSocket()
{
ws.onEvent(onEvent);
server.addHandler(&ws);
}
String processor(const String& var)
{
Serial.println(var);
if (var == "STATE")
{
if (ledState) {
return "ON" ;
}
else {
return "OFF";
}
}
if (var == "FanOn") {
return String(fanOn);
}
if (var == "FanPV") {
return String(fan_speed);
}
if (var == "FanSL") {
return String(fan_SL);
}
return String();
}
void set_udp_msg(char msg[]) {
// going to assume the msg we've been given is long enough
char fMsg[80];
int ix;
sprintf(fMsg, "%s", __FILE__);
ix = 0;
while (1) {
if (fMsg[ix] == '.') {
fMsg[ix] = 0; break;
}
ix++;
}
sprintf(msg, "%s,%d,%d,%d,%d,%d,%d", fMsg, hors, mins, secs, fanOn, fan_SL, fan_speed);
}
void UDP_Brd() {
// UDP Broadcast
char msg[120]; // crude guess at length
IPAddress ipa(0, 0, 0, 0);
ipa[0] = WiFi.localIP()[0];
ipa[1] = WiFi.localIP()[1];
ipa[2] = WiFi.localIP()[2];
ipa[3] = 255;
set_udp_msg(msg);
Udp.beginPacket(ipa, 8123); // Port??
Udp.write(msg);
Udp.endPacket();
}
int iOn; // 0 or 1
int udpCount = 0; // count udp receive
void doUDP() {
// If there's any UDP message, receive and send response
char packetBuffer[rcv_pkt_size];
char msg[10 * 12]; // crude assume bytes * values
char cmdCh;
int packetSize = Udp.parsePacket();
if (packetSize)
{
udpCount++;
IPAddress remote = Udp.remoteIP();
// Print...
Serial.print(" Rcv size: ");
Serial.print(packetSize);
Serial.print("From: ");
for (int i = 0; i < 4; i++)
{
Serial.print(remote[i], DEC);
if (i < 3) {
Serial.print(".");
}
}
Serial.print(", port ");
Serial.print(Udp.remotePort());
// read the packet into packetBufffer
Udp.read(packetBuffer, rcv_pkt_size);
Serial.print("Contents: ~");
Serial.print(packetBuffer);
Serial.print("~");
cmdCh = packetBuffer[0];
if (cmdCh == 'A') {
// send a reply, to the IP address and port that sent us the packet we received
set_udp_msg(msg);
Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
Udp.write(msg);
Udp.endPacket();
}
}
}
void print_addr()
{
// Print the IP address to serial
Serial.printf("Listening on IP %s, UDP port %d. ", WiFi.localIP().toString().c_str(), localUdpPort);
Serial.println();
}
// State values.
int actCont; // counter controlling request and read of BME280
/*
We don't know if int is 16 or 31 bit. Board dependent.
unsigned 16 bit max is 65535
if seconds then 1092 minutes
18 hours
millis() is unsiged long, overflow is 50 days
*/
void setup() {
bool status;
int tries;
delay(2000);
Serial.begin(9600);
delay(500);
pM(fanOn_Pin , OUTPUT);
pM(fanSL_Pin , OUTPUT);
pM(ledGr_Pin , OUTPUT);
pM(ledRd_Pin , OUTPUT);
pM(fanPV_Pin , INPUT_PULLUP);
WiFi.mode(WIFI_AP_STA);
// Connect to Wi-Fi - try 2 nets in turn
if (WiFi.status() != WL_CONNECTED) {
WiFi.begin(ssid0, password0, 10);
tries = 0;
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
Serial.print("Connecting to WiFi...~");
Serial.print(" ");
Serial.print(ssid0);
Serial.print(" ");
Serial.print(tries);
Serial.println();
tries++;
if (tries > 5) {
break;
}
}
}
if (WiFi.status() != WL_CONNECTED) {
WiFi.begin(ssid1, password1);
tries = 0;
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
Serial.print("Connecting to WiFi...");
Serial.print(" ");
Serial.print(ssid1);
Serial.print(" ");
Serial.print(tries);
Serial.println();
tries++;
if (tries > 5) {
break;
}
}
}
// Print ESP Local IP Address
Serial.println(WiFi.localIP());
initWebSocket();
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send_P(200, "text/html", index_html, processor);
});
// Start ElegantOTA
AsyncElegantOTA.begin(&server);
// Start server
server.begin();
Udp.begin(localUdpPort);
// Set the interrupt for fan and timer
blinker.attach_ms(2000, timer_ISR);
//attachInterrupt(digitalPinToInterrupt(D4), fan_ISR, RISING);
attachInterrupt(D4, fan_ISR, RISING);
delay(4000); // what for?
}
long lopCount = 0;
int sec0 = 0;
int sec1;
int sw1Stat0 = -2;
int sw1Stat1;
int sw2Stat0 = -2;
int sw2Stat1;
int serCount;
void loop()
{
int port_ix;
int new_in;
int in_val_ix;
char timeMsg[50];
char uMsg[50];
char fMsg[50];
int ix;
// Calculate dd hh:mm:ss
sec1 = millis() / 1000;
if (sec1 != sec0) {
secs++;
if (secs >= 60) {
mins++;
if (mins >= 60) {
hors++;
if (hors >= 24) {
days++;
hors = 0;
}
mins = 0;
}
secs = 0;
}
sec0 = sec1;
}
sprintf(timeMsg, "%02d:%02d:%02d", hors, mins, secs);
if (lopCount % 100 == 0)
{
// each second...
if (actCont % 2 == 0) {
ws.textAll("EC," + String(actCont));
ws.textAll("T0," + String(actCont % 57));
ws.textAll("T1," + String(actCont % 31));
}
if (actCont % 20 == 0)
{
Serial.printf("%s %s %s\n", __FILE__, __DATE__, __TIME__);
print_addr();
}
if (actCont % 15 == 0) {
UDP_Brd();
}
actCont++;
}
if (lopCount % 100 == 0 && actCont % 2 == 0)
{
Serial.print(lopCount);
Serial.print(" ");
if (actCont % (2 * 3) == 2 * 3 - 1) {
Serial.println();
}
if (actCont % (2 * 30) == 0)
{
Serial.println();
Serial.print(__FILE__);
Serial.print(" ");
Serial.print(__DATE__);
Serial.print(" ");
Serial.print(__TIME__);
Serial.println();
}
}
if (lopCount % 20 == 0)
{
// copy the web-driven state to the led
dW(ledRd_Pin, ledState);
ws.cleanupClients();
}
if (lopCount % 10 == 0) {
// Any udp action required?
doUDP();
}
//fanSenseState1 = dR(SpedPin);
if (fanSenseState1 != fanSenseState0) {
ws.textAll("F1," + String(fanSenseState1));
serCount++;
if (serCount > 80) {
serCount = 0;
Serial.println();
}
fanSenseState0 = fanSenseState1;
}
int period = 600;
if (lopCount % (period / 2) == 0) {
}
// Copy speed pulse sense pin to led.
dW(ledGr_Pin, dR(fanPV_Pin));
lopCount++;
delay(10);
}