Zvonko Bockaj
Published © CC BY-NC

WeMos ESP8266 Remote PC Switch

Turn your PC on/off with an Android smartphone, using the WeMos ESP8266 module connected to CloudMQTT service via WiFi.

IntermediateFull instructions provided6 hours38,899
WeMos ESP8266 Remote PC Switch

Things used in this project

Hardware components

Wemos D1 Mini
Espressif Wemos D1 Mini
Great module with ESP8266. Features integrated 3V3 regulator, important PU/PD resistors, CH340G USB-UART and software jump to bootloader from Arduino IDE.
×1
USB 2.0 Motherboard adapter cable
Needed if you want to power everything from your motherboard USB header, otherwise not crucial.
×1
Solderless Breadboard Full Size
Solderless Breadboard Full Size
You could also fit it on a half-size (30 rows) breadboard without much effort.
×1
DS18B20 Programmable Resolution 1-Wire Digital Thermometer
Maxim Integrated DS18B20 Programmable Resolution 1-Wire Digital Thermometer
Just as an additional feature, not needed for basic functionality. DS1
×1
BC337 NPN transistor
Any generic NPN should do, but please check pinout before connecting (CBE used here). Q1, Q2.
×2
LED (generic)
LED (generic)
Just for testing, any LED will do, I used green and red. LED1, LED2.
×2
Resistor 2.21k ohm
Resistor 2.21k ohm
2k2, exact value not that crucial for any resistor in this project. R1, R3, R8.
×3
Resistor 6.81k ohm
6k8. R2, R5, R9, R10.
×4
Resistor 475 ohm
Resistor 475 ohm
470R. R4, R6.
×2
Resistor 22.1k ohm
Resistor 22.1k ohm
22k. R7.
×1
Jumper wires (generic)
Jumper wires (generic)
Or you could use 0.6 mm single core wire, I got mine from ebay.
×1
Female/Female Jumper Wires
Female/Female Jumper Wires
You need 2x2 wires to connect to your PC's power switch and LED pins.
×1
Male Header 40 Position 1 Row (0.1")
Male Header 40 Position 1 Row (0.1")
You need 4x2 pins, or you could just use jumper wires. J1, J2, J3, J4.
×1

Software apps and online services

Arduino IDE
Arduino IDE
CloudMQTT
Android MQTT Dash

Story

Read more

Schematics

EspPcSwitch schematic

EspPcSwitch breadboard circuit

Code

EspPcSwitch code

Arduino
Copy to Arduino IDE and upload, don't forget to insert your own credentials for WiFi and CloudMQTT.
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <OneWire.h>
#include <DallasTemperature.h>

// Wemos D1 mini pins compared to ESP GPIO
// source: https://www.wemos.cc/product/d1-mini.html
// Wemos|ESP
// TX   TXD
// RX   RXD
// A0   A0
// D0   GPIO16, doesn't have interrupt/pwm
// D1   GPIO5/SCL
// D2   GPIO4/SDA
// D3   GPIO0, 10k PU, ESP special (set to 0 during reset to enter bootloader)
// D4   GPIO2, 10k PU, LED, ESP special (should be high during reset)
// D5   GPIO14/SCK
// D6   GPIO12/MISO
// D7   GPIO13/MOSI
// D8   GPIO15/SS, 10k PD, ESP special (should be low during reset)
// G    GND
// 5V   -
// 3V3  3.3V
// RST  RST

// note: using ESP GPIO numbers rather than Wemos for easier portability to other ESP versions
#define GPIO_OUT_SW     12  // D6 // used as output, for toggling transistor that turns on/off the pc
#define GPIO_IN_STATUS  14  // D5 // used as input for PC on/off status
#define GPIO_ONEWIRE    0   // D3 // 1wire in/out pin

#define OUT_STATE_ACTIVE    1   // use define so it's simple to change if signal should be active high or low (NPN or PNP BJT)
#define OUT_STATE_INACTIVE  (!OUT_STATE_ACTIVE)

#define OUT_TOGGLE_DURATION_MS  300 // how long to keep the pwr pin (GPIO_OUT_SW) active when triggered

#define IN_STATUS_INVERTED  true // true if reading 1 on input actually means the PC is off

#define WIFI_SSID   "Your SSID here"
#define WIFI_PASS   "Your password here"

#define CLOUDMQTT_SERVER "m20.cloudmqtt.com"
#define CLOUDMQTT_PORT   Your port here
#define CLOUDMQTT_USER   "Your username here"
#define CLOUDMQTT_PASS   "Your password here"

#define MQTT_CLIENT_ID   "EspPcSwitch"  // note: if you have multiple devices, assign them different ID's
#define MQTT_USE_RETAIN   false         // set to true if you want last msg for all topics retained on server, so you get it automatically on client connect

#define TOPIC_OUT_CONN        "esp/pcsw/conn"
#define TOPIC_OUT_PC_STATUS   "esp/pcsw/status"
#define TOPIC_OUT_TEMP        "esp/pcsw/temp"
#define TOPIC_IN_PC_STATE     "esp/pcsw/state"
#define TOPIC_IN_SYNC         "esp/pcsw/sync"

#define PUB_PERIODIC_MS     1000 * 60 * 10  // publish data periodically every 10 minutes
#define PUB_TEMP_THRESHOLD  2.0f            // besides periodically, temperature will be transmitted if changed more than this
#define PUB_MIN_MS          1000            // don't publish more often that this, even on status/temp change

#define DEBOUNCE_STATUS_MS    2000    // 2 second debounce for PC on/off status
#define TEMP_REFRESH_MS       10000   // periodic read of 1wire temperature

// enum to cycle through all data that is to be sent periodically
typedef enum
{
  PUB_DATA_PC_STATUS =  0,
  PUB_DATA_TEMP      =  1,
  PUB_DATA_MAX       =  2  
} PubData_e;

// setup instances for Wifi and 1Wire
WiFiClient espClient;
PubSubClient client(espClient);
OneWire oneWire(GPIO_ONEWIRE); 
DallasTemperature tempSensors(&oneWire);

// variables
char mqtt_msg[50];
uint16_t connect_cnt = 0;
uint16_t sync_cnt = 0;

uint8_t current_pc_status = 255;
uint8_t last_read_status = 255;
uint8_t last_published_status = 255;
long last_status_unstable_ms = 0;

float current_temp = -127.0;
float last_published_temp = -127.0;
long last_temp_request_ms = 0;

long last_published_ms = 0;
PubData_e data_to_publish = PUB_DATA_PC_STATUS;
uint8_t is_syncing = 0;

///////////////////////////////////////////////////////////////////////////////
/// Init functions
///////////////////////////////////////////////////////////////////////////////
void Wifi_Connect (void) 
{
  Serial.println(); // before this we have crap from ESP bootloader
  Serial.print("Connecting to ");
  Serial.println(WIFI_SSID);

  // connect to Wifi network
  WiFi.begin(WIFI_SSID, WIFI_PASS);

  while (WiFi.status() != WL_CONNECTED) 
  {
    delay(500);
    Serial.print(".");
  }

  Serial.println();
  Serial.print("WiFi connected! IP: ");
  Serial.println(WiFi.localIP());
}

void OneWire_Start (void)
{
  Serial.print ("Initializing 1wire...");
  tempSensors.begin (); // init and start 1wire scan
  Serial.print("done! Sensors found: ");
  Serial.println(tempSensors.getDeviceCount());

  tempSensors.setWaitForConversion(false); // make temperature conversion async (don't wait for finish)
  tempSensors.requestTemperatures(); // initialize first temperature conversion
  last_temp_request_ms = millis (); 
}

///////////////////////////////////////////////////////////////////////////////
/// Setup and main loop
///////////////////////////////////////////////////////////////////////////////
void setup() 
{
  Serial.begin(115200, SERIAL_8N1);

  pinMode (GPIO_OUT_SW, OUTPUT);
  digitalWrite (GPIO_OUT_SW, OUT_STATE_INACTIVE); 
  pinMode (GPIO_IN_STATUS, INPUT);
  delay (500);

  // connect to Wifi
  Wifi_Connect();

  // setup MQTT
  client.setServer(CLOUDMQTT_SERVER, CLOUDMQTT_PORT);
  client.setCallback(Subscription_Callback);

  // start 1wire
  OneWire_Start ();
}

void loop() 
{  
  // handle MQTT connection
  if (!client.connected()) 
  {
    connect_cnt++;
    Mqtt_Reconnect();  
    last_published_ms = last_status_unstable_ms = millis(); // init time here, it takes a while to connect
  }
  client.loop(); 

  // handle temperature reading
  if ((millis () - last_temp_request_ms) > TEMP_REFRESH_MS)
  {  
    // get temperature from first index (since we have only one sensor)   
    current_temp = tempSensors.getTempCByIndex(0);
    Serial.print("Temperature read: "); 
    Serial.println(current_temp);

    // request new temperature conversion, which can take up to 750 ms (DS18B20)
    // we don't wait here for that since we setup async conversion
    tempSensors.requestTemperatures();     
    last_temp_request_ms = millis ();
  }
  
  long now_ms = millis();
  
  // read current input status
  uint8_t tmp_status = digitalRead (GPIO_IN_STATUS);
  // invert if needed
  if (IN_STATUS_INVERTED) tmp_status = !tmp_status;

  // check if unstable
  if (tmp_status != last_read_status)
  {
    last_status_unstable_ms = now_ms;
    last_read_status = tmp_status;
  }

  // check if debounce period elapsed, save new status
  if ((now_ms - last_status_unstable_ms) > DEBOUNCE_STATUS_MS)
  {
    if (current_pc_status != tmp_status)
    {
      current_pc_status = tmp_status;
      Serial.print ("PC status changed to ");
      Serial.println (current_pc_status);
    }
  }

  // limit minimal MQTT sending interval, publish data
  if ((now_ms - last_published_ms) > PUB_MIN_MS)
  {
    // send on PC status change
    if (current_pc_status != last_published_status)
    {
      Publish_PcStatus (current_pc_status);
      last_published_ms = now_ms;
    }
    // or on temperature change
    else if (fabs (current_temp - last_published_temp) > PUB_TEMP_THRESHOLD)
    {
      Publish_Temperature (current_temp);      
      last_published_ms = now_ms;
    }
    // check if we were syncing data
    else if (is_syncing)
    {
      char tmp_str[20];

      // increment counter and publish status
      sync_cnt++;
      snprintf (tmp_str, sizeof (tmp_str), "Synced(%d)", sync_cnt);
      Publish_Connection (tmp_str);
      is_syncing = 0; // sync complete at this point, everything that needed to be sent is above      
    }
    // send periodically
    else if ((now_ms - last_published_ms) > PUB_PERIODIC_MS) 
    {  
      // cycle through data to publish periodically 
      if (data_to_publish >= PUB_DATA_MAX) data_to_publish = PUB_DATA_PC_STATUS;
        
      if (data_to_publish == PUB_DATA_PC_STATUS) Publish_PcStatus (current_pc_status);
      else if (data_to_publish == PUB_DATA_TEMP) Publish_Temperature (current_temp);    

      data_to_publish = (PubData_e) (data_to_publish + 1);  
      last_published_ms = now_ms;
    }
  }
}

///////////////////////////////////////////////////////////////////////////////
/// MQTT functions
///////////////////////////////////////////////////////////////////////////////
void Mqtt_Reconnect() 
{
  char tmp_str[30];
  uint32_t millis_start = millis ();
  
  // Loop until we're reconnected
  while (!client.connected()) 
  {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect(MQTT_CLIENT_ID, CLOUDMQTT_USER, CLOUDMQTT_PASS)) 
    {      
      // Once connected, publish an announcement...
      snprintf (tmp_str, sizeof (tmp_str), "Connected(%d)", connect_cnt);
      Serial.println(tmp_str);      
      Publish_Connection (tmp_str);
      // ... and resubscribe
      client.subscribe (TOPIC_IN_PC_STATE);
      client.subscribe (TOPIC_IN_SYNC);
      break;
    } 
    else 
    {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
    
    // restart ESP if we cannot connect for too long
    if ((millis () - millis_start) > 2 * 60000)
    {
      Serial.println ("Cannot connect to MQTT, restarting...");  
      ESP.restart ();
    }
  }
}

void Publish_Connection (char * text)
{
  // report to terminal for debug
  snprintf (mqtt_msg, sizeof (mqtt_msg), "Publishing %s %s", TOPIC_OUT_CONN, text);
  Serial.println (mqtt_msg);
  
  // publish to MQTT server
  client.publish (TOPIC_OUT_CONN, text, MQTT_USE_RETAIN);   
}

void Publish_PcStatus (uint8_t pc_status)
{
  // report to terminal for debug
  snprintf (mqtt_msg, sizeof (mqtt_msg), "Publishing %s %s", TOPIC_OUT_PC_STATUS, String(pc_status).c_str());
  Serial.println (mqtt_msg);
  
  // publish to MQTT server
  client.publish (TOPIC_OUT_PC_STATUS, String(pc_status).c_str(), MQTT_USE_RETAIN); 

  // save last published status
  last_published_status = pc_status;  
}

void Publish_Temperature (float temp)
{
  int16_t temp_i16;
  char temp_string[10];
  
  // temperature value is in degrees C as float, we'll report with 
  // only one decimal since accuracy of sensor is +-0.5 degC

  // convert to integer to avoid float sprintf issues
  if (temp >= 0.0)
  {
    temp_i16 = (temp + 0.05f) * 10; // add 0.05f to round up, x10 to keep one decimal in integer
  }
  else
  {
    temp_i16 = (temp - 0.05f) * 10;
  }

  // write integer temperature to string 
  snprintf (temp_string, sizeof (temp_string), "%d.%01d", temp_i16 / 10, abs (temp_i16) % 10); 

  // report to terminal for debug
  snprintf (mqtt_msg, sizeof (mqtt_msg), "Publishing %s %s", TOPIC_OUT_TEMP, temp_string);
  Serial.println (mqtt_msg);
  
  // publish to MQTT server
  client.publish (TOPIC_OUT_TEMP, temp_string, MQTT_USE_RETAIN);

  // remember last sent
  last_published_temp = temp;
}

void Subscription_Callback(char* topic, byte* payload, unsigned int length) 
{
  // report to terminal for debug
  Serial.print("MQTT msg arrived: ");
  Serial.print(topic);
  Serial.print(" ");
  for (int i = 0; i < length; i++) 
  {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  // check if we received our IN topic for state change
  if (strcmp (TOPIC_IN_PC_STATE, topic) == 0)
  {
    uint8_t target_state = current_pc_status; 

    // check for valid values
    if ((char)payload[0] == '1')
    {
      target_state = 1;
    }
    else if ((char) payload[0] == '0')
    {
      target_state = 0;
    }

    // toggle output (PC power switch) if we need to change state
    if (target_state != current_pc_status)
    {
      Serial.print ("Toggling state! target|current=");
      Serial.print (target_state);
      Serial.print ("|");
      Serial.println (current_pc_status);
      TogglePcState ();
    }
  }
  // check for sync topic
  else if (strcmp (TOPIC_IN_SYNC, topic) == 0)
  {
    // we should get '1' as sync request
    if ((char) payload[0] == '1')
    {
      is_syncing = 1;    
      // invalidate last sent data, triggering resend
      last_published_status = 255;
      last_published_temp = -127.0;  
    }
  }
}

void TogglePcState (void)
{
  digitalWrite (GPIO_OUT_SW, OUT_STATE_ACTIVE);
  delay (OUT_TOGGLE_DURATION_MS);
  digitalWrite (GPIO_OUT_SW, OUT_STATE_INACTIVE);
  Serial.println ("Out state toggled!");
}

Credits

Zvonko Bockaj

Zvonko Bockaj

2 projects • 3 followers
Embedded developer, hardware and firmware.

Comments