スポンサーリンク

Making an Alexa compatible dimmable ceiling light using ESP32 and Daiso’s LED fluorescent light-Program

スポンサーリンク

日本語版はこちら

Last time, I created a circuit for an Alexa-compatible dimmable ceiling light.

In this article, I’d like to create a program for ESP32.

スポンサーリンク

Required Functions

The features required to program an Alexa-enabled dimmable ceiling light are as follows.

  • Alexa-enabled
  • Set up a wireless LAN access point from your phone
  • Translate Alexa’s instructions into brightness control signals
  • Reconnect when the wireless LAN connection is lost
  • When the power is turned on, it lights up at 100%, and when the microcontroller is restarted, it lights up at its past brightness

Alexa-enabled

To enable Alexa with ESP32, we use a very convenient library called Espalexa that we used in the previous experiment.

The Alexa part of the program looks like this.

//Networking
#include <WiFi.h>
#include <Preferences.h>

//Alexa
#include <Espalexa.h>
Espalexa espalexa;

// Secure the brightness information
#include <EEPROM.h>
int dim;  //A value between 0 and 100

void setup() {
  EEPROM.begin(4);

  //Alexa setting
  espalexa.addDevice("Denki", lightChanged, dim*2.55);
  espalexa.begin();
}

void loop() {
  espalexa.loop();
  delay(1);
}

//Alexa callback functions
void lightChanged(uint8_t brightness) {
  dim = brightness / 2.55; //Convert alexa value (max 255) to % (max 100)
  EEPROM.write(0, dim); //Storing values in EEPROM
  EEPROM.commit();
  Serial.printf("Alexa:%d->dim:%d[%%]\n", brightness, dim );
}

Line 17: Allow the device name “Denki” to be controlled from Alexa

Line 18: Launch ESPAlexa

After that, we always keep ESPalexa running in a loop.

When Alexa changes the brightness, the call Back function on line 27 is executed.

Save the brightness set in this variable to the dim variable.

The range of values given by Alexa is from 0 for lights out to 255 for maximum brightness.

Programmatically I want to treat it as 0-100%, so I divide it by 2.55 and assign it to the dim variable.

Also, on line 29, the current brightness is stored in EEPROM. The reason for this will be explained later.

Set up a wireless LAN access point from your phone

I’m using WiFiManager, which I used when I made an alarm clock-like IN-14 Nixie tube table clock.

If the library fails to connect to the access point at startup, the ESP32 itself becomes the access point.

When you connect to this access point from your phone, you can set up the SSID and password for your wireless LAN.

The program is very simple and looks like this.

//WifiManager related
#include <DNSServer.h>
#include <WebServer.h>
#include "WiFiManager.h"
#define DeviceName "AlexaLight" //access point name
#define WMPassword "password"   //password
WiFiManager wm;

void setup() {
  //WiFi Settings Portal Activation If you can't connect to WiFi, activate the portal for 10 minutes only
  wm.setDebugOutput(false);
  wm.setConfigPortalTimeout(10 * 60);
  bool ret = wm.autoConnect(DeviceName, WMPassword);
}

Declare class on line 7
In line 12 enable WiFiManager for 10 minutes at startup

Line 13 activates the portal and automatically connects it. The SSID name to connect from your phone is “AlexaLight” defined in line 5 and the password is “password” defined in line 6. These can be changed at will.

Convert Alexa’s instruction to brightness control signal

The brightness specified by Alexa is stored in the global variable dim with values between 0 and 100.

When dim is 0, the Solid State Relay (S0) is turned off to turn off the lighting, and when dim is non-zero, it is turned on.

When the dim is between 1 and 255, three Photo-MOS Relays (S1, S2, S3) are set up appropriately to adjust the brightness in eight steps.

This time, when the dim value is changed, I want the brightness to slowly change to the specified brightness, instead of immediately becoming that brightness.

The program for this part of the program is as follows.

//GPIO
#define LED1_S0 26
#define LED1_S1 25
#define LED1_S2 33
#define LED1_S3 32
#define LED2_S0 13
#define LED2_S1 12
#define LED2_S2 14
#define LED2_S3 27

//task handle
TaskHandle_t taskHandle[10];
enum task { dimControl };

void setup() {
  //IO pin initialization
  pinMode(LED1_S0, OUTPUT);
  pinMode(LED1_S1, OUTPUT);
  pinMode(LED1_S2, OUTPUT);
  pinMode(LED1_S3, OUTPUT);
  pinMode(LED2_S0, OUTPUT);
  pinMode(LED2_S1, OUTPUT);
  pinMode(LED2_S2, OUTPUT);
  pinMode(LED2_S3, OUTPUT);

  digitalWrite(LED1_S0, LOW);
  digitalWrite(LED1_S1, LOW);
  digitalWrite(LED1_S2, LOW);
  digitalWrite(LED1_S3, LOW);
  digitalWrite(LED2_S0, LOW);
  digitalWrite(LED2_S1, LOW);
  digitalWrite(LED2_S2, LOW);
  digitalWrite(LED2_S3, LOW);

  //Brightness Control Task
  xTaskCreatePinnedToCore(dimControlTask, "dimControl", 4096, NULL, 0, &taskHandle[dimControl], 0);
} 

//Set the LED brightness to 0 to 8
void LEDDimm( int value )
{
  int s0 = LOW , s1 = LOW , s2 = LOW , s3 = LOW;
  if ( value < 0 )
    value = 0;
  if ( value > 8 )
    value = 8;

  switch ( value ) {
    case 0:
      s0 = LOW; s1 = LOW; s2 = LOW; s3 = LOW;
      break;
    case 1:
      s0 = HIGH;  s1 = LOW; s2 = LOW; s3 = LOW;
      break;
    case 2:
      s0 = HIGH;  s1 = HIGH;  s2 = LOW; s3 = LOW;
      break;
    case 3:
      s0 = HIGH;  s1 = LOW; s2 = HIGH;  s3 = LOW;
      break;
    case 4:
      s0 = HIGH;  s1 = HIGH;  s2 = HIGH;  s3 = LOW;
      break;
    case 5:
      s0 = HIGH;  s1 = LOW; s2 = LOW; s3 = HIGH;
      break;
    case 6:
      s0 = HIGH;  s1 = HIGH;  s2 = LOW; s3 = HIGH;
      break;
    case 7:
      s0 = HIGH;  s1 = LOW; s2 = HIGH;  s3 = HIGH;
      break;
    case 8:
      s0 = HIGH;  s1 = HIGH;  s2 = HIGH;  s3 = HIGH;
      break;
    default:
      s0 = LOW; s1 = LOW; s2 = LOW; s3 = LOW;
  }

  if ( s0 == LOW )
  {
    digitalWrite(S0, s0);
    digitalWrite(LED1_S0, s0);
    digitalWrite(LED2_S0, s0);
  }

  //Erase once
  digitalWrite(LED1_S3, LOW);
  digitalWrite(LED1_S2, LOW);
  digitalWrite(LED1_S1, LOW);
  digitalWrite(LED2_S3, LOW);
  digitalWrite(LED2_S2, LOW);
  digitalWrite(LED2_S1, LOW);

  //Set brightness
  digitalWrite(LED1_S1, s1);
  digitalWrite(LED1_S2, s2);
  digitalWrite(LED1_S3, s3);
  digitalWrite(LED2_S1, s1);
  digitalWrite(LED2_S2, s2);
  digitalWrite(LED2_S3, s3);
  digitalWrite(S1, s1);
  digitalWrite(S2, s2);
  digitalWrite(S3, s3);

  if ( s0 == HIGH )
  {
    digitalWrite(S0, s0);
    digitalWrite(LED1_S0, s0);
    digitalWrite(LED2_S0, s0);
  }
}

/********* Task *********/
//The task to change the brightness smoothly
void dimControlTask(void* arg)
{
  int nowDim = 0;
  double now_dim = 0;
  int gosa_dim = 0;
  int out = 0;
  int led_out_old = -1;
  double setDim = 0;
  double targetDim = 0;

  while (1)
  {
    // 0 to 100% is classified into 8 levels by error diffusion
    now_dim += round(setDim) - gosa_dim;
    out = round( now_dim / 12.5 );
    gosa_dim = round(out * 12.5);

    targetDim = ceil( dim / 12.5 ) * 12.5;
    if ( setDim < targetDim )
      setDim += 0.25;
    if ( setDim > targetDim )
      setDim -= 0.25;
    if ( setDim < 0 )
      setDim = 0;
    if ( setDim > 100 )
      setDim = 100;

    //Change the brightness if it is different from the past value
    if( led_out_old != out )
    {
      LEDDimm( out );
      Serial.printf("setDim=%3.2f out=%d targetDim=%3.2f\n", setDim, out, targetDim);
    }
    led_out_old = out;

    delay(1);
  }
}

The LEDDimm function converts values from 0 to 8 into control signals for S0,S1,S2,S3. ON and OFF of S1 to S3 do not change at the same time, but IO changes in the order described in the program.

Therefore, during the change, the past state and the updated state are mixed, and the brightness becomes unintended.

That’s why I turn off all the lights and then turn on S0 after I finish changing S1, S2 and S3.

In order to achieve a slow change in brightness to the desired brightness, the brightness change uses a task.

On line 36, the task dimControlTask is invoked to change the brightness.

dimControlTask adds and subtracts little by little from the current value toward the value specified by dim.

Since the dimming circuit in this project has only eight brightness levels, I used the error diffusion of sigma-delta to pseudo-detailedly express fine gradation and smooth changes in brightness.

Reconnect when the wireless LAN connection is lost

If it’s always running, I think the wireless LAN may be cut off before you know it, so you should always monitor to see if it’s connected and reconnect if it is cut off.

//task handle
TaskHandle_t taskHandle[10];
enum task { dimControl, wifiConnect };

void setup() {
  //Task to reconnect when the wireless LAN goes out
  xTaskCreatePinnedToCore(wifiConnectTask, "wifiConnect", 4096, NULL, 1, &taskHandle[wifiConnect], 1);
}

//Task to reconnect when the wireless LAN goes out
void wifiConnectTask(void* arg)
{
  int wiFiStatus = WL_DISCONNECTED;
  int connecting = 0;

  while (1)
  {
    //No wireless LAN connection
    if ( WiFi.status() != WL_CONNECTED && connecting == 0)
    {
      digitalWrite(WiFiEnable, LOW);

      //Connect to wireless LAN
      WiFi.mode(WIFI_STA);
      WiFi.begin();

      //Display the recorded SSID
      char wifi_ssid[100] = {};
      Preferences preferences;
      preferences.begin("nvs.net80211", true);
      preferences.getBytes("sta.ssid", wifi_ssid, sizeof(wifi_ssid));
      Serial.printf("Connecting to %s.\n", &wifi_ssid[4]);

      connecting = 1;
    }

    //Waiting for connection
    if ( connecting > 0 )
    {
      connecting++;
      Serial.printf("*");

      //WiFi LED blinks
      if ( digitalRead(WiFiEnable) == LOW )
        digitalWrite(WiFiEnable, HIGH );
      else
        digitalWrite(WiFiEnable, LOW );

      //Reconnect if you can't connect after waiting for 30 seconds
      if ( connecting > 2 * 30 )
      {
        WiFi.disconnect();
        connecting = 0;
        Serial.printf("\n");
        delay(5);
      }
    }

    //Wireless LAN connected
    if ( WiFi.status() == WL_CONNECTED )
    {
      //WiFi connection was established
      if ( wiFiStatus != WL_CONNECTED )
      {
        Serial.print("\nWiFi connected\r\nIP address: ");
        Serial.println(WiFi.localIP());
        digitalWrite(WiFiEnable, HIGH); //WiFiLED点灯
        wiFiStatus = WL_CONNECTED;
      }
      connecting = 0;
    }
    delay(500);
  }
}

Tasks are used for constant monitoring.

The task is launched on line 7. wifiConnectTask is a task to connect only if the wireless LAN is disconnected.

It lights up at 100% when the power is turned on and lights up at the past brightness when the microcontroller is restarted

When you turn on the switch on the wall, you want the lighting to turn on.

Also, the microcontroller may restart on its own due to some problems, such as the watchdog timer running.

I would be surprised if the lights came on by themselves when I was asleep at night when it rebooted.

On the other hand, it’s also annoying if it reboots and goes off on its own when the lights are on.

So, store the current brightness in the EEPROM so that when it is rebooted, it will light up at the past brightness.

What you need to do here is to know what caused it to boot.

You need the ability to know what caused the boot, whether it was a power-on boot or a reset boot due to a watchdog timer, brownout, etc.

In fact, there is a way to know what the reset factor is, but when I’ve tried it, I’ve been unable to distinguish between a power-on reset and a true watchdog reset because the reset is also caused by the watchdog timer when the power is on.

So we added the following circuit.

When the power is turned on, electricity slowly builds up in the capacitor.

For about 5 seconds or so, leading the GPIO 36 pin will be recognized as LOW.

After that, the capacitor is filled with electricity and becomes HIGH.

If the microcontroller is reset during the process, the capacitor is already full and the GPIO pin is in a HIGH state.

When the power is turned off, the electricity stored in the capacitor is discharged via the diodes.

Therefore, by checking the state of the GPIO pins during program setup, you can see that the program is powered on if it’s LOW and rebooted if it’s HIGH.

//GPIO
#define POWERON 36

void setup() {
  //Initialize IO pins
  pinMode(POWERON, INPUT);
  
  //If the power is on, set the brightness to 100%, if it's a soft reset, set it to past brightness.
  EEPROM.begin(4);
  if( digitalRead(POWERON) == LOW )
  {
    Serial.printf("Power ON");
    dim = 100;  //Set the brightness to maximum when using PowerOnReset
    EEPROM.write(0, dim); //Storing values in EEPROM
    EEPROM.commit();
  }
  else
  {
    Serial.printf("RESET");
    dim = EEPROM.read(0); //Set the brightness as stored in the EEPROM.
  }
  Serial.printf(" dim:%d\n", dim);
}

At startup, the POWER-ON pin is checked, if it is LOW, it is set to 100% brightness, and if it is HIGH, the EEPROM value is read and set in the dim variable.

スポンサーリンク

The program is completed

The above program has been combined to complete the program for the Alexa-compatible ceiling light with a dimming function.

The entire program is Keep here

Setup

When you write a program to ESP32, WiFi Manager starts first. Click here to learn how to set up WiFi from your phone.

Once connected to your home’s wireless network, you can add the device through the Alexa app. Click here for instructions on how to set it up.

In this program, the name of the device is “Denki”. After setup, you can change the name to something easier to say, such as “Lighting” or “Light”.

Operation check

Now, when you’re done with the settings up to this point, let’s turn the lights on and off in Alexa.

Say to your Amazon Echo, “Alexa, turn on the light” or “Alexa, turn off the light”.

It doesn’t light up instantly, but rather it glows on and off in stages. The operation is perfect.

“Alexa, darken the electricity” “Alexa, brighten the electricity” “Alexa, turn the electricity 50%” will also respond.

Now the software is complete. Next time, I would like to incorporate it into an old fluorescent-type ceiling light and convert it into an Alexa-compatible ceiling light with a dimming function.

2020.7.23 added Click here to continue

https://kohacraft.com/archives/202007231436.html

End of addition

コメント