[waiting for update] Teensy Opening/Closing Valve Over WiFi Using Just 1 Pin

BriComp

Well-known member
I have developed a project where I turn a valve on/off switched by a Sonoff Basic2, commanded by an ESP32-C3 over WiFi controlled by just ONE Teensy pin.
Below is a schematic picture of the bit used.
TeensyLC-ESP32C3-2-SonoffBasic.jpg
The ESP32-C3 in the form of a T-01C3 sends a control message to the Sonoff Basic2 to turn it's Relay and Led on or off.
In order to do this the Sonoff MUST have it's re-programmed to accept ESP_Now commands.
The code for this firmware is shown below. To re-program it follow the method shown here.

During operation the ESP32-C3 is sent to DeepSleep. It is woken up by the Teensy, by the Teense setting a pin low (the ESP32-C3 can use high or low). I have found by experiment that an ESP32-C3 takes circa 43.5ms to become fully compos mentis.
Therefore we can put our wakeup pin LOW for ~10ms to start the ESP32-C3 wakeup process, then decide whether we want to have the Sonoff turn it's relay (and led) on or off. If we want it off we leave the WakeUp Pin LOW for ~70ms more so that the ESP32 will have time to read the pin and determine it as a LOW and send a message to the Sonoff to turn off. If we want the Sonoff to turn on we set the Wakeup pin HIGH after the 10ms initial pulse.

Below is the software for the three devices:

Teensy Code:

Code:
// Visual Micro is in vMicro>General>Tutorial Mode
// 
/*
    Name:       Demo_Teensy_Control_Sonoff_Basic.ino
    Created:	02/Jly/2023 16:06:10
    Author:     Robert E Bridges
*/

#define debugz

#define teensyESP32_C3_WakeUpPin 23
#define teensyEsp32WakeupLevel   LOW        // MUST BE THE SAME AS ESP32 LEVEL

bool    sonoffOn = false;
#ifdef debug
void Delay(uint32_t howLong) {
    uint32_t t = millis() + howLong;
    while (millis() < t) {
        if (Serial1.available()) Serial.write(Serial1.read());
    }
}
#endif

void TurnSonoff(bool on) {
    sonoffOn = on;
    digitalWriteFast( teensyESP32_C3_WakeUpPin, teensyEsp32WakeupLevel );
#ifdef debug
    if (on) Delay(70); else Delay(10);
#else
    if (on) delay(70); else delay(10);
#endif
    digitalWriteFast( teensyESP32_C3_WakeUpPin, !teensyEsp32WakeupLevel );
    digitalWriteFast(LED_BUILTIN, on);
}

void setup()
{
#ifdef debug
    Serial.begin(115200);
    Serial1.begin(115200);
#endif
    pinMode(LED_BUILTIN, OUTPUT);
    pinMode( teensyESP32_C3_WakeUpPin, OUTPUT );
    digitalWriteFast( teensyESP32_C3_WakeUpPin, !teensyEsp32WakeupLevel );
}

void loop()
{
    TurnSonoff(!sonoffOn);      // Will turn Sonoff On/Off every 10 seconds
#ifdef debug
    Delay(10000);
#else
    delay(10000);
#endif
}


Sonoff Code:

Code:
#include <ESP8266WiFi.h>
#include <espnow.h>
#include <SimpleSonoff.h>

#define printMacAddressz

#ifdef printMacAddress

#define printData
#include <HardwareSerial.h>
HardwareSerial mySerial(0);

#endif

#define sonoffRelay     12
#define sonoffLed       13
#define sonoffButton    0

uint8_t sonoffState;

bool    relayState;
bool    ledState;
bool    errorOccured = false;

void HandleStateSettings() {
    relayState = ( sonoffState & turnRelayOn );
    ledState  = (( sonoffState & turnLedOn ) == 0 );   // led is active low, ie low to turn it on
#ifdef printData
    mySerial.print("relayState: ");  mySerial.println(relayState);
    mySerial.print("ledState  : ");  mySerial.println(ledState);
#endif
    digitalWrite( sonoffRelay, relayState );
    digitalWrite( sonoffLed,   ledState     );
}

void ToggleState() {
    sonoffState = (( ~sonoffState) & 0b00000011 );
    HandleStateSettings();
}

// Callback function that will be executed when data is received
void OnDataRecv(uint8_t* mac, uint8_t* incomingData, uint8_t len) {
    memcpy(&myData, incomingData, sizeof(myData));

    if ((myData.command & toggleState) > 0) {
        ToggleState();
    } else
    {
        HandleStateSettings();
        sonoffState = myData.command;
    }
}

void setup() {

#ifdef printMacAddress
    mySerial.begin(115200);
    mySerial.println();
    mySerial.print("Sonoff(ESP8266) Board MAC Address:  ");
    mySerial.println(WiFi.macAddress());
#endif
    pinMode( sonoffLed,    OUTPUT );
    pinMode( sonoffRelay,  OUTPUT );
    pinMode( sonoffButton, INPUT);
    sonoffState = 0;   // all off

    HandleStateSettings();

    errorOccured = true;

    WiFi.disconnect();
    ESP.eraseConfig();

    // Set device as a Wi-Fi Station
    if (WiFi.mode(WIFI_STA)) {
        WiFi.disconnect();

        // Init ESP-NOW
        if (esp_now_init() == 0) {
            // Once ESPNow is successfully Init, we will register for recv CB to
            // get recv packer info
            if (esp_now_set_self_role(ESP_NOW_ROLE_SLAVE) == 0) {
                if (esp_now_register_recv_cb(OnDataRecv) == 0) {
                    errorOccured = false;
                }
            }
        }
    }
}

uint8_t count = 0;

void loop() {
    if ( errorOccured){
        digitalWrite(sonoffLed, LOW);
        delay(1000);
        digitalWrite(sonoffLed, HIGH);
        delay(1000);
    } else 
    {
        count = 0;
        while (digitalRead(sonoffButton) == 0 && count < 5) {
            count++;
            delay(35);
        }
        if (count >= 5) {
            ToggleState();
            while (digitalRead(sonoffButton) == 0) {} // wait for button to be released
        }
    }
}


ESP32-C3 Code:

Code:
/*

One pin from Teensy Controls a SonOff WiFi relay

*/

#include <esp_now.h>
#include <WiFi.h>
#include <esp_sleep.h>
#include <SimpleSonoff.h>

/**************************************************
*  Configure the next 5 lines to suit your system *
***************************************************/

#define ESP32_C3_Wakeup_Pin     2                                           // The pin on ESP32_C3 which will be triggered to wake up ESP32
#define ESPWakeupLevel          ESP_GPIO_WAKEUP_GPIO_LOW                    // MUST BE THE SAME ON TEENSY
#define timeoutTime             10000                                       // Timeout for sending data in ms
uint8_t broadcastAddress[]    = { 0x40, 0xF5, 0x20, 0xFE, 0xA1, 0x88 };     // MAC address of Sonoff to be turned ON/OFF
#define ledPin                  3

#define espDeepSleepStartFailure            8
#define espDeepSleepEnableGpioWakeupFailure 7
#define WiFiModeFailure                     6
#define espNowInitFailure                   5
#define espNowRegisterSendCbFailure         4
#define espNowSendFailure                   3
#define espNowAddPeerFailure                2
#define notReceived                         1
#define received                            0

enum wakeupCauseType {
    gpioFromSleep,
    resetOrColdBoot
};

struct errType {
    uint8_t level;
    uint8_t state;
};

errType err;

esp_now_peer_info_t peerInfo;
struct_message      sonoffMessage;

uint8_t             whatCausedStartup;
bool                turnSonoffOn;
uint32_t            timeout;

// Callback when data received by sonoff
void OndataReceived(const uint8_t* mac_addr, esp_now_send_status_t sendStatus) {
    err.level = sendStatus;   // 0 = received
}

uint8_t DetermineWakeUp() {
    uint8_t t;

    esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause();
    if (wakeup_cause == ESP_SLEEP_WAKEUP_GPIO) t = gpioFromSleep; else t = resetOrColdBoot;
    return t;
}

void StartWiFiAndSendMessage() {

    err.level = notReceived;
    err.state = 0;

    if (WiFi.mode(WIFI_STA)) {

        WiFi.disconnect();
        if (esp_now_init() == ESP_OK) {

            if (esp_now_register_send_cb(OndataReceived) == ESP_OK) {

                memcpy(peerInfo.peer_addr, broadcastAddress, 6);
                peerInfo.channel = commsChannel;
                peerInfo.encrypt = false;

                err.state = esp_now_add_peer(&peerInfo);
                if (err.state == ESP_OK) {

                    if (turnSonoffOn == HIGH)
                        sonoffMessage.command = turnRelayOn + turnLedOn;
                    else
                        sonoffMessage.command = turnRelayOff + turnLedOff;

                    err.state = esp_now_send(broadcastAddress, (uint8_t*)&sonoffMessage, sizeof(sonoffMessage));

                    if (err.state == ESP_OK)
                        err.level = notReceived;
                    else
                        err.level = espNowSendFailure;
                }
                else err.level = espNowAddPeerFailure;
            }
            else err.level = espNowRegisterSendCbFailure;
        }
        else err.level = espNowInitFailure;
    }
    else err.level = WiFiModeFailure;

}

void setup() {
    /****************************************************
    ** MUST READ wakeUpPin as soon as programme starts **
    **     to determine if turn Sonoff ON or OFF       **
    *****************************************************/

    pinMode(ESP32_C3_Wakeup_Pin, INPUT_PULLUP);
    turnSonoffOn = digitalRead(ESP32_C3_Wakeup_Pin);

    whatCausedStartup = DetermineWakeUp();
    if (whatCausedStartup == gpioFromSleep) {       // Not a Cold Start or Reset
        StartWiFiAndSendMessage();                  // sets err.level and err.state in function as appropriate
    }
    else {
        err.level = received;                       // Wakeup NOT from gpio, probably initial start up 
    }
                                                    // so go to sleep until woken up properly.
    if (err.level < 2) {                            // = received or notReceived
        if (err.level == notReceived) {
            timeout = millis() + timeoutTime;       // 10 seconds
            while ((err.level == notReceived) && (millis() < timeout)) {};       // Wait for Sonoff to receive data
        }
        if (err.level == received) {
            if (esp_deep_sleep_enable_gpio_wakeup((uint64_t)1 << ESP32_C3_Wakeup_Pin, ESPWakeupLevel) == ESP_OK) {
                esp_deep_sleep_start();
                err.level = espDeepSleepStartFailure;      // SHOULD NEVER GET HERE
            }
            else err.level = espDeepSleepEnableGpioWakeupFailure;
        }
    }
    pinMode(ledPin, OUTPUT);
    if (err.level > 0) err.level--;
}

void FlashLed(uint8_t numTimes, uint16_t delBetween) {
    for (uint8_t i = 0; i < numTimes; i++) {
        digitalWrite(ledPin, HIGH);
        delay(delBetween);
        digitalWrite(ledPin, LOW);
        delay(delBetween);
    }
}

void DisplayErrorState() {
    uint32_t tim = millis() + 10000;        // Indicate state for 10 seconds then reboot
    
    while (millis() < tim) {                // If Error LONG ON, MEDIUM OFF followed by SHORT flashes for ERROR CODE

        digitalWrite(ledPin, HIGH);
        delay(500);
        if (err.level > 0) delay(500);

        digitalWrite(ledPin, LOW);
        delay(500);        
        FlashLed(err.level, 200);
        if (err.state > 0) {
            delay(1000);
            FlashLed(err.state, 200);
        }
    }
}

void loop() {
    DisplayErrorState();
    ESP.restart();
}


SimpleSonoff.h Code used by both ESP32-C3 and Sonoff.

Code:
#pragma once

#pragma pack (push, 1)

typedef struct struct_message {
    uint8_t command;
} struct_message;

struct_message myData;

#define turnRelayOn  0b00000001
#define turnRelayOff 0b00000000
#define turnLedOn    0b00000010
#define turnLedOff   0b00000000
#define toggleState  0b00000100

#pragma pack (pop)

#define commsChannel 1
 
This one is now in the queue for a blog article. Quick question though, is the Teensy's main purpose just to keep the ESP in a low power mode when not needed? I guess I'm just struggling to understand the project as a whole. Any extra explanation could really help so we get the article writeup correct.

Also another photo of the project actually installed and working could really help for a good article to show what it's really doing in practice. The code and technical detail is of course good, but explaining the real end-use application helps put it all in perspective for an article read by people not (yet) focused on the implementation.
 
That post was to show how to be able to (cheaply) remotely control mains (240V in UK 110V elsewhere) voltage from a Teensy.

The project is still ongoing, which is intended to have whole house control of central heating down to individual radiator level.
The Teensy seen here will sit on the wall monitoring temperature and humidity and remotely turn the radiator on/off using the Sonoff Basic which will apply power to a radiator valve.

As mentioned the whole project is intended to be low cost whilst providing the maximum of control and comfort
The Sonoff basic can be purchased for $3.50, the ESP32-C3 for $3.56 ( or an ESP8266 for < 1$).

The wall mounted thermostat is intended to be battery/mains powered, hence the use of low power mode and an LC.
 
Back
Top