mariusmotea / diyHue

Philips Hue emulator that is able to control multiple types of lights
Other
627 stars 107 forks source link

Any way for a local switch/rotary encoder on the ESP? #107

Closed erazor666 closed 6 years ago

erazor666 commented 6 years ago

Say you want a wired switch/dimmer connected to some gpio to dim for example the "generic dimmable light". Is this possible?

kurniawan77 commented 6 years ago

Would like to know too. Like a button shield on a wemos for sk6812. Definitely WAF (wife accaptence factor) guaranteed!

mariusmotea commented 6 years ago

It is possible, but question is how many gpio pins must be sacrificed for this future. We can use one or two. With one the behavior must be:

with two:

erazor666 commented 6 years ago

The first one sounds best of course. This would be a definite WAF thing, however gpio should not be wasted lightly that is true. A rotary encoder "wheel" combined with a button would also be the most elegant solution.i'm guessing that would use 2 gpios. The button uses one and the "wheel' uses one. Guess this could be an alternate sketch or option you enable in the start of it. Maybe it sounds like a step backwards since this is wifi based and all. On some lights i build this option would be handy.

mariusmotea commented 6 years ago

Until you pasted you comment i made something quick (i take some code from switch sketch). The new code portion start from else so you can identify the place.

  if (in_transition) {
    delay(6);
    in_transition = false;
  } else {
    if (digitalRead(button1_pin) == HIGH) {
      int i = 0;
      while (digitalRead(button1_pin) == HIGH && i < 30) {
        delay(20);
        i++;
      }
      for (int light = 0; light < lightsCount; light++) {
        if (i < 30) {
          // there was a short press
          light_state[light] = true;
        }
        else {
          // there was a long press
          bri[light] += 56;
          if (bri[light] > 255) {
            // don't increase the brightness more then maximum value
            bri[light] = 255;
          }
        }
      }
    }
    if (digitalRead(button2_pin) == HIGH) {
      int i = 0;
      while (digitalRead(button2_pin) == HIGH && i < 30) {
        delay(20);
        i++;
      }
      for (int light = 0; light < lightsCount; light++) {
        if (i < 30) {
          // there was a short press
          light_state[light] = false;
        }
        else {
          // there was a long press
          bri[light] -= 56;
          if (bri[light] < 1) {
            // don't decrease the brightness less than minimum value.
            bri[light] = 1;
          }
        }
      }
    }
  }

i believe we can use TX and RX pins, that are available on all lights, except neopixel ones where we have all other GPIO pins available.

Don't forget to declare in header:

define button1_pin 1

define button2_pin 3

and on last stage of setup phase (becasue wifi manager initiate the pins for serial debugging) add:

  pinMode(button1_pin, INPUT);
  pinMode(button2_pin, INPUT);
erazor666 commented 6 years ago

Woha, you are a real magician. I will test this and report back, I have a testing rig made just yesterday :)

erazor666 commented 6 years ago

Oh and by the way, will this be reported to the bridge? So it didplays the current dim level and such? (This is a luxury thing of course, but very neat if it does)

mariusmotea commented 6 years ago

Must be, because when hue application made a request to get lights status, the emulator will fetch all lights and update its config.

BTW if brightness change steps are too big, just replace +/- 56 value with something smaller.

erazor666 commented 6 years ago

I have a switch that commands a ww strip via http commands (espeasy). The bridge picks up the state change (seen in the official app) however HA does not pick up that state change when i use the switch. This can be a HA issue of course, just dont know where to attack it yet...

mariusmotea commented 6 years ago

Not really, maybe HA grab entire configuration from a /api/ request every time, and when this happen the function to grab lights status is not executed. Try this: under the line: if len(url_pices) == 3 or (len(url_pices) == 4 and url_pices[3] == ""): #print entire config add this line: syncWithLights()

mariusmotea commented 6 years ago

Any feedback? Today i have some free time and i can do things more faster.

erazor666 commented 6 years ago

i'm gonna start the testing of the local switch now. i'll edit when i get it going edit1: It compiles with the added code at least, time for some wires edit2: they sorta work but not; i can find them in hue app. however they come up as unreachable and the button does not do anything at the moment. I will post complete code

Oh shit, i had a Common Ground not present, gotta test again

Didnt seem to fix it, when i press them in the light setup dialog in hue app, they are supposed to flash once, they just turn on and never off again. Also they light up at full brightness and slowly just fade out without input. Also seem really "laggy" and these lights are usually very responsive. Seems like its running the button code by itself, i can see it stepping down the dim level without input, and still show as 'unreachable' in hue app.

Button pins shouldnt be 1 & 2 instead of 1 & 3?

erazor666 commented 6 years ago
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>
#include <EEPROM.h>
#include "pwm.c"

#define button1_pin 1
#define button2_pin 3
#define lightsCount 2 

const uint32_t period = 1024;

//define pins
uint32 io_info[lightsCount][3] = {
  // MUX, FUNC, PIN
  //{PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12, 12},
  //{PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15, 15},
  //{PERIPHS_IO_MUX_MTCK_U, FUNC_GPIO13, 13},
  //{PERIPHS_IO_MUX_MTMS_U, FUNC_GPIO14, 14},
  {PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4 ,  4},
  {PERIPHS_IO_MUX_GPIO5_U, FUNC_GPIO5 ,  5},
};

// initial duty: all off
uint32 pwm_duty_init[lightsCount] = {0};

// if you want to setup static ip uncomment these 3 lines and line 72
//IPAddress strip_ip ( 192,  168,   10,  95);
//IPAddress gateway_ip ( 192,  168,   10,   1);
//IPAddress subnet_mask(255, 255, 255,   0);

uint8_t scene;
bool light_state[lightsCount], in_transition;
int transitiontime[lightsCount], bri[lightsCount];
float step_level[lightsCount], current_bri[lightsCount];
byte mac[6];

ESP8266WebServer server(80);

void handleNotFound() {
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
}

void apply_scene(uint8_t new_scene,  uint8_t light) {
  if ( new_scene == 0) {
    bri[light] = 144;
  } else if ( new_scene == 1) {
    bri[light] = 254;
  } else if ( new_scene == 2) {
    bri[light] = 1;
  }
}

void lightEngine() {
  for (int i = 0; i < lightsCount; i++) {
    if (light_state[i]) {
      if (bri[i] != current_bri[i]) {
        in_transition = true;
        current_bri[i] += step_level[i];
        if ((step_level[i] > 0.0 && current_bri[i] > bri[i]) || (step_level[i] < 0.0 && current_bri[i] < bri[i])) current_bri[i] = bri[i];
        pwm_set_duty((int)(current_bri[i] * 4), i);
        pwm_start();
      }
    } else {
      if (current_bri[i] != 0 ) {
        in_transition = true;
        current_bri[i] -= step_level[i];
        if (current_bri[i] < 0) current_bri[i] = 0;
        pwm_set_duty((int)(current_bri[i] * 4), i);
        pwm_start();
      }
    }
  }
  if (in_transition) {
    delay(6);
    in_transition = false;
  } else {
    if (digitalRead(button1_pin) == HIGH) {
      int i = 0;
      while (digitalRead(button1_pin) == HIGH && i < 30) {
        delay(20);
        i++;
      }
      for (int light = 0; light < lightsCount; light++) {
        if (i < 30) {
          // there was a short press
          light_state[light] = true;
        }
        else {
          // there was a long press
          bri[light] += 56;
          if (bri[light] > 255) {
            // don't increase the brightness more then maximum value
            bri[light] = 255;
            }
          }
        }
      }
    }
    if (digitalRead(button2_pin) == HIGH) {
      int i = 0;
      while (digitalRead(button2_pin) == HIGH && i < 30) {
        delay(20);
        i++;
      }
      for (int light = 0; light < lightsCount; light++) {
        if (i < 30) {
          // there was a short press
          light_state[light] = false;
        }
        else {
          // there was a long press
          bri[light] -= 56;
          if (bri[light] < 1) {
            // don't decrease the brightness less than minimum value.
            bri[light] = 1;
          }
        }
      }
    }
  }

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

  //WiFi.config(strip_ip, gateway_ip, subnet_mask);

  for (uint8_t ch = 0; ch < lightsCount; ch++) {
    pinMode(io_info[ch][2], OUTPUT);
  }

  pwm_init(period, pwm_duty_init, lightsCount, io_info);
  pwm_start();

  for (uint8_t light = 0; light < lightsCount; light++) {
    apply_scene(EEPROM.read(2), light);
    step_level[light] = bri[light] / 150.0;
  }

  if (EEPROM.read(1) == 1 || (EEPROM.read(1) == 0 && EEPROM.read(0) == 1)) {
    for (int i = 0; i < lightsCount; i++) {
      light_state[i] = true;
    }
    for (int j = 0; j < 200; j++) {
      lightEngine();
    }
  }
  WiFiManager wifiManager;
  wifiManager.autoConnect("New Hue Light");
  if (! light_state[0])  {
    // Show that we are connected
    pwm_set_duty(100, 1);
    pwm_start();
    delay(500);
    pwm_set_duty(0, 1);
    pwm_start();
    pinMode(button1_pin, INPUT);
    pinMode(button2_pin, INPUT);
  }

  WiFi.macAddress(mac);

  // Port defaults to 8266
  // ArduinoOTA.setPort(8266);

  // Hostname defaults to esp8266-[ChipID]
  // ArduinoOTA.setHostname("myesp8266");

  // No authentication by default
  // ArduinoOTA.setPassword((const char *)"123");

  ArduinoOTA.begin();

  pinMode(LED_BUILTIN, OUTPUT);     // Initialize the LED_BUILTIN pin as an output
  digitalWrite(LED_BUILTIN, HIGH);  // Turn the LED off by making the voltage HIGH

  server.on("/switch", []() {
    server.send(200, "text/plain", "OK");
    float transitiontime = 4;
    int button;
    for (uint8_t i = 0; i < server.args(); i++) {
      if (server.argName(i) == "button") {
        button = server.arg(i).toInt();
      }
    }
    for (int i = 0; i < lightsCount; i++) {
      if (button == 1000) {
        if (light_state[i] == false) {
          light_state[i] = true;
          scene = 0;
        } else {
          apply_scene(scene, i);
          scene++;
          if (scene == 11) {
            scene = 0;
          }
        }
      } else if (button == 2000) {
        if (light_state[i] == false) {
          bri[i] = 30;
          light_state[i] = true;
        } else {
          bri[i] += 30;
        }
        if (bri[i] > 255) bri[i] = 255;
      } else if (button == 3000 && light_state[i] == true) {
        bri[i] -= 30;
        if (bri[i] < 1) bri[i] = 1;
      } else if (button == 4000) {
        light_state[i] = false;
      }
        if (light_state[i]) {
          step_level[i] = ((float)bri[i] - current_bri[i]) / transitiontime;
        } else {
          step_level[i] = current_bri[i] / transitiontime;
        }
    }
  });

  server.on("/set", []() {
    uint8_t light;
    float transitiontime = 4;
    for (uint8_t i = 0; i < server.args(); i++) {
      if (server.argName(i) == "light") {
        light = server.arg(i).toInt() - 1;
      }
      else if (server.argName(i) == "on") {
        if (server.arg(i) == "True" || server.arg(i) == "true") {
          light_state[light] = true;
          if (EEPROM.read(1) == 0 && EEPROM.read(0) == 0) {
            EEPROM.write(0, 1);
          }
        }
        else {
          light_state[light] = false;
          if (EEPROM.read(1) == 0 && EEPROM.read(0) == 1) {
            EEPROM.write(0, 0);
          }
        }
        EEPROM.commit();
      }
      else if (server.argName(i) == "bri") {
        light_state[light] = true;
        if (server.arg(i).toInt() != 0)
          bri[light] = server.arg(i).toInt();
      }
      else if (server.argName(i) == "bri_inc") {
        bri[light] += server.arg(i).toInt();
        if (bri[light] > 255) bri[light] = 255;
        else if (bri[light] < 0) bri[light] = 0;
      }
      else if (server.argName(i) == "alert" && server.arg(i) == "select") {
        if (light_state[light]) {
          current_bri[light] = 0;
        } else {
          current_bri[light] = 255;
        }
      }
      else if (server.argName(i) == "transitiontime") {
        transitiontime = server.arg(i).toInt();
      }
    }
    server.send(200, "text/plain", "OK, bri:" + (String)bri[light] + ", state:" + light_state[light]);
    transitiontime *= 16;
    if (light_state[light]) {
      step_level[light] = (bri[light] - current_bri[light]) / transitiontime;
    } else {
      step_level[light] = current_bri[light] / transitiontime;
    }
  });

  server.on("/get", []() {
    uint8_t light;
    if (server.hasArg("light"))
      light = server.arg("light").toInt() - 1;
    String power_status;
    power_status = light_state[light] ? "true" : "false";
    server.send(200, "text/plain", "{\"on\": " + power_status + ", \"bri\": " + (String)bri[light] + "}");
  });

  server.on("/detect", []() {
    server.send(200, "text/plain", "{\"hue\": \"bulb\",\"lights\": " + (String)lightsCount + ",\"modelid\": \"LWB010\",\"mac\": \"" + String(mac[5], HEX) + ":"  + String(mac[4], HEX) + ":" + String(mac[3], HEX) + ":" + String(mac[2], HEX) + ":" + String(mac[1], HEX) + ":" + String(mac[0], HEX) + "\"}");
  });

  server.on("/", []() {
    float transitiontime = 4;
    if (server.hasArg("startup")) {
      if (  EEPROM.read(1) != server.arg("startup").toInt()) {
        EEPROM.write(1, server.arg("startup").toInt());
        EEPROM.commit();
      }
    }

    for (int light = 0; light < lightsCount; light++) {
      if (server.hasArg("scene")) {
        if (server.arg("bri") == "" && server.arg("hue") == "" && server.arg("ct") == "" && server.arg("sat") == "") {
          if (  EEPROM.read(2) != server.arg("scene").toInt()) {
            EEPROM.write(2, server.arg("scene").toInt());
            EEPROM.commit();
          }
          apply_scene(server.arg("scene").toInt(), light);
        } else {
          if (server.arg("bri") != "") {
            bri[light] = server.arg("bri").toInt();
          }
        }
      } else if (server.hasArg("on")) {
        if (server.arg("on") == "true") {
          light_state[light] = true; {
            if (EEPROM.read(1) == 0 && EEPROM.read(0) == 0) {
              EEPROM.write(0, 1);
            }
          }
        } else {
          light_state[light] = false;
          if (EEPROM.read(1) == 0 && EEPROM.read(0) == 1) {
            EEPROM.write(0, 0);
          }
        }
        EEPROM.commit();
      } else if (server.hasArg("alert")) {
        if (light_state[light]) {
          current_bri[light] = 0;
        } else {
          current_bri[light] = 255;
        }
      }
      if (light_state[light]) {
        step_level[light] = ((float)bri[light] - current_bri[light]) / transitiontime;
      } else {
        step_level[light] = current_bri[light] / transitiontime;
      }
    }
    if (server.hasArg("reset")) {
      ESP.reset();
    }

    String http_content = "<!doctype html>";
    http_content += "<html>";
    http_content += "<head>";
    http_content += "<meta charset=\"utf-8\">";
    http_content += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
    http_content += "<title>Light Setup</title>";
    http_content += "<link rel=\"stylesheet\" href=\"https://unpkg.com/purecss@0.6.2/build/pure-min.css\">";
    http_content += "</head>";
    http_content += "<body>";
    http_content += "<fieldset>";
    http_content += "<h3>Light Setup</h3>";
    http_content += "<form class=\"pure-form pure-form-aligned\" action=\"/\" method=\"post\">";
    http_content += "<div class=\"pure-control-group\">";
    http_content += "<label for=\"power\"><strong>Power</strong></label>";
    http_content += "<a class=\"pure-button"; if (light_state[0]) http_content += "  pure-button-primary"; http_content += "\" href=\"/?on=true\">ON</a>";
    http_content += "<a class=\"pure-button"; if (!light_state[0]) http_content += "  pure-button-primary"; http_content += "\" href=\"/?on=false\">OFF</a>";
    http_content += "</div>";
    http_content += "<div class=\"pure-control-group\">";
    http_content += "<label for=\"startup\">Startup</label>";
    http_content += "<select onchange=\"this.form.submit()\" id=\"startup\" name=\"startup\">";
    http_content += "<option "; if (EEPROM.read(1) == 0) http_content += "selected=\"selected\""; http_content += " value=\"0\">Last state</option>";
    http_content += "<option "; if (EEPROM.read(1) == 1) http_content += "selected=\"selected\""; http_content += " value=\"1\">On</option>";
    http_content += "<option "; if (EEPROM.read(1) == 2) http_content += "selected=\"selected\""; http_content += " value=\"2\">Off</option>";
    http_content += "</select>";
    http_content += "</div>";
    http_content += "<div class=\"pure-control-group\">";
    http_content += "<label for=\"scene\">Scene</label>";
    http_content += "<select onchange = \"this.form.submit()\" id=\"scene\" name=\"scene\">";
    http_content += "<option "; if (EEPROM.read(2) == 0) http_content += "selected=\"selected\""; http_content += " value=\"0\">Relax</option>";
    http_content += "<option "; if (EEPROM.read(2) == 1) http_content += "selected=\"selected\""; http_content += " value=\"1\">Bright</option>";
    http_content += "<option "; if (EEPROM.read(2) == 2) http_content += "selected=\"selected\""; http_content += " value=\"2\">Nightly</option>";
    http_content += "</select>";
    http_content += "</div>";
    http_content += "<br>";
    http_content += "<div class=\"pure-control-group\">";
    http_content += "<label for=\"state\"><strong>State</strong></label>";
    http_content += "</div>";
    http_content += "<div class=\"pure-control-group\">";
    http_content += "<label for=\"bri\">Bri</label>";
    http_content += "<input id=\"bri\" name=\"bri\" type=\"text\" placeholder=\"" + (String)bri[0] + "\">";
    http_content += "</div>";

    http_content += "<div class=\"pure-controls\">";
    http_content += "<span class=\"pure-form-message\"><a href=\"/?alert=1\">alert</a> or <a href=\"/?reset=1\">reset</a></span>";
    http_content += "<label for=\"cb\" class=\"pure-checkbox\">";
    http_content += "</label>";
    http_content += "<button type=\"submit\" class=\"pure-button pure-button-primary\">Save</button>";
    http_content += "</div>";
    http_content += "</fieldset>";
    http_content += "</form>";
    http_content += "</body>";
    http_content += "</html>";

    server.send(200, "text/html", http_content);

  });

  server.onNotFound(handleNotFound);

  server.begin();
}

void loop() {
  ArduinoOTA.handle();
  server.handleClient();
  lightEngine();
}
mariusmotea commented 6 years ago

I believe the onboard serial adapter may keep the "button" pressed. If you use just 2 lights, then you have gpio12 to gpio15 free, so you can try to use them. Ensure you use pull down resistors, otherwise you must enable internal resistors with following code:

  pinMode(button1_pin, INPUT);
  pinMode(button2_pin, INPUT);
  digitalWrite(button1_pin, LOW);
  digitalWrite(button2_pin, LOW);
erazor666 commented 6 years ago

Here follows the sketch with the modifications you suggested, its still behaving exactly the same (button code seems to be running by itself, lights show up as unreachable, still sort of controlable however not working as they should) Have not added any resistors as of now, if the internal pullup can handle it.

I added:

pinMode(button1_pin, INPUT); pinMode(button2_pin, INPUT); digitalWrite(button1_pin, LOW); digitalWrite(button2_pin, LOW);

And changed:

define button1_pin 13

define button2_pin 14

Complete sketch:

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>
#include <EEPROM.h>
#include "pwm.c"

#define button1_pin 13
#define button2_pin 14
#define lightsCount 2 

const uint32_t period = 1024;

//define pins
uint32 io_info[lightsCount][3] = {
  // MUX, FUNC, PIN
  //{PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12, 12},
  //{PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15, 15},
  //{PERIPHS_IO_MUX_MTCK_U, FUNC_GPIO13, 13},
  //{PERIPHS_IO_MUX_MTMS_U, FUNC_GPIO14, 14},
  {PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4 ,  4},
  {PERIPHS_IO_MUX_GPIO5_U, FUNC_GPIO5 ,  5},
};

// initial duty: all off
uint32 pwm_duty_init[lightsCount] = {0};

// if you want to setup static ip uncomment these 3 lines and line 72
//IPAddress strip_ip ( 192,  168,   10,  95);
//IPAddress gateway_ip ( 192,  168,   10,   1);
//IPAddress subnet_mask(255, 255, 255,   0);

uint8_t scene;
bool light_state[lightsCount], in_transition;
int transitiontime[lightsCount], bri[lightsCount];
float step_level[lightsCount], current_bri[lightsCount];
byte mac[6];

ESP8266WebServer server(80);

void handleNotFound() {
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
}

void apply_scene(uint8_t new_scene,  uint8_t light) {
  if ( new_scene == 0) {
    bri[light] = 144;
  } else if ( new_scene == 1) {
    bri[light] = 254;
  } else if ( new_scene == 2) {
    bri[light] = 1;
  }
}

void lightEngine() {
  for (int i = 0; i < lightsCount; i++) {
    if (light_state[i]) {
      if (bri[i] != current_bri[i]) {
        in_transition = true;
        current_bri[i] += step_level[i];
        if ((step_level[i] > 0.0 && current_bri[i] > bri[i]) || (step_level[i] < 0.0 && current_bri[i] < bri[i])) current_bri[i] = bri[i];
        pwm_set_duty((int)(current_bri[i] * 4), i);
        pwm_start();
      }
    } else {
      if (current_bri[i] != 0 ) {
        in_transition = true;
        current_bri[i] -= step_level[i];
        if (current_bri[i] < 0) current_bri[i] = 0;
        pwm_set_duty((int)(current_bri[i] * 4), i);
        pwm_start();
      }
    }
  }
  if (in_transition) {
    delay(6);
    in_transition = false;
  } else {
    if (digitalRead(button1_pin) == HIGH) {
      int i = 0;
      while (digitalRead(button1_pin) == HIGH && i < 30) {
        delay(20);
        i++;
      }
      for (int light = 0; light < lightsCount; light++) {
        if (i < 30) {
          // there was a short press
          light_state[light] = true;
        }
        else {
          // there was a long press
          bri[light] += 56;
          if (bri[light] > 255) {
            // don't increase the brightness more then maximum value
            bri[light] = 255;
            }
          }
        }
      }
    }
    if (digitalRead(button2_pin) == HIGH) {
      int i = 0;
      while (digitalRead(button2_pin) == HIGH && i < 30) {
        delay(20);
        i++;
      }
      for (int light = 0; light < lightsCount; light++) {
        if (i < 30) {
          // there was a short press
          light_state[light] = false;
        }
        else {
          // there was a long press
          bri[light] -= 56;
          if (bri[light] < 1) {
            // don't decrease the brightness less than minimum value.
            bri[light] = 1;
          }
        }
      }
    }
  }

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

  //WiFi.config(strip_ip, gateway_ip, subnet_mask);

  for (uint8_t ch = 0; ch < lightsCount; ch++) {
    pinMode(io_info[ch][2], OUTPUT);
  }

  pwm_init(period, pwm_duty_init, lightsCount, io_info);
  pwm_start();

  for (uint8_t light = 0; light < lightsCount; light++) {
    apply_scene(EEPROM.read(2), light);
    step_level[light] = bri[light] / 150.0;
  }

  if (EEPROM.read(1) == 1 || (EEPROM.read(1) == 0 && EEPROM.read(0) == 1)) {
    for (int i = 0; i < lightsCount; i++) {
      light_state[i] = true;
    }
    for (int j = 0; j < 200; j++) {
      lightEngine();
    }
  }
  WiFiManager wifiManager;
  wifiManager.autoConnect("New Hue Light");
  if (! light_state[0])  {
    // Show that we are connected
    pwm_set_duty(100, 1);
    pwm_start();
    delay(500);
    pwm_set_duty(0, 1);
    pwm_start();
    pinMode(button1_pin, INPUT);
    pinMode(button2_pin, INPUT);
    digitalWrite(button1_pin, LOW);
    digitalWrite(button2_pin, LOW);
  }

  WiFi.macAddress(mac);

  // Port defaults to 8266
  // ArduinoOTA.setPort(8266);

  // Hostname defaults to esp8266-[ChipID]
  // ArduinoOTA.setHostname("myesp8266");

  // No authentication by default
  // ArduinoOTA.setPassword((const char *)"123");

  ArduinoOTA.begin();

  pinMode(LED_BUILTIN, OUTPUT);     // Initialize the LED_BUILTIN pin as an output
  digitalWrite(LED_BUILTIN, HIGH);  // Turn the LED off by making the voltage HIGH

  server.on("/switch", []() {
    server.send(200, "text/plain", "OK");
    float transitiontime = 4;
    int button;
    for (uint8_t i = 0; i < server.args(); i++) {
      if (server.argName(i) == "button") {
        button = server.arg(i).toInt();
      }
    }
    for (int i = 0; i < lightsCount; i++) {
      if (button == 1000) {
        if (light_state[i] == false) {
          light_state[i] = true;
          scene = 0;
        } else {
          apply_scene(scene, i);
          scene++;
          if (scene == 11) {
            scene = 0;
          }
        }
      } else if (button == 2000) {
        if (light_state[i] == false) {
          bri[i] = 30;
          light_state[i] = true;
        } else {
          bri[i] += 30;
        }
        if (bri[i] > 255) bri[i] = 255;
      } else if (button == 3000 && light_state[i] == true) {
        bri[i] -= 30;
        if (bri[i] < 1) bri[i] = 1;
      } else if (button == 4000) {
        light_state[i] = false;
      }
        if (light_state[i]) {
          step_level[i] = ((float)bri[i] - current_bri[i]) / transitiontime;
        } else {
          step_level[i] = current_bri[i] / transitiontime;
        }
    }
  });

  server.on("/set", []() {
    uint8_t light;
    float transitiontime = 4;
    for (uint8_t i = 0; i < server.args(); i++) {
      if (server.argName(i) == "light") {
        light = server.arg(i).toInt() - 1;
      }
      else if (server.argName(i) == "on") {
        if (server.arg(i) == "True" || server.arg(i) == "true") {
          light_state[light] = true;
          if (EEPROM.read(1) == 0 && EEPROM.read(0) == 0) {
            EEPROM.write(0, 1);
          }
        }
        else {
          light_state[light] = false;
          if (EEPROM.read(1) == 0 && EEPROM.read(0) == 1) {
            EEPROM.write(0, 0);
          }
        }
        EEPROM.commit();
      }
      else if (server.argName(i) == "bri") {
        light_state[light] = true;
        if (server.arg(i).toInt() != 0)
          bri[light] = server.arg(i).toInt();
      }
      else if (server.argName(i) == "bri_inc") {
        bri[light] += server.arg(i).toInt();
        if (bri[light] > 255) bri[light] = 255;
        else if (bri[light] < 0) bri[light] = 0;
      }
      else if (server.argName(i) == "alert" && server.arg(i) == "select") {
        if (light_state[light]) {
          current_bri[light] = 0;
        } else {
          current_bri[light] = 255;
        }
      }
      else if (server.argName(i) == "transitiontime") {
        transitiontime = server.arg(i).toInt();
      }
    }
    server.send(200, "text/plain", "OK, bri:" + (String)bri[light] + ", state:" + light_state[light]);
    transitiontime *= 16;
    if (light_state[light]) {
      step_level[light] = (bri[light] - current_bri[light]) / transitiontime;
    } else {
      step_level[light] = current_bri[light] / transitiontime;
    }
  });

  server.on("/get", []() {
    uint8_t light;
    if (server.hasArg("light"))
      light = server.arg("light").toInt() - 1;
    String power_status;
    power_status = light_state[light] ? "true" : "false";
    server.send(200, "text/plain", "{\"on\": " + power_status + ", \"bri\": " + (String)bri[light] + "}");
  });

  server.on("/detect", []() {
    server.send(200, "text/plain", "{\"hue\": \"bulb\",\"lights\": " + (String)lightsCount + ",\"modelid\": \"LWB010\",\"mac\": \"" + String(mac[5], HEX) + ":"  + String(mac[4], HEX) + ":" + String(mac[3], HEX) + ":" + String(mac[2], HEX) + ":" + String(mac[1], HEX) + ":" + String(mac[0], HEX) + "\"}");
  });

  server.on("/", []() {
    float transitiontime = 4;
    if (server.hasArg("startup")) {
      if (  EEPROM.read(1) != server.arg("startup").toInt()) {
        EEPROM.write(1, server.arg("startup").toInt());
        EEPROM.commit();
      }
    }

    for (int light = 0; light < lightsCount; light++) {
      if (server.hasArg("scene")) {
        if (server.arg("bri") == "" && server.arg("hue") == "" && server.arg("ct") == "" && server.arg("sat") == "") {
          if (  EEPROM.read(2) != server.arg("scene").toInt()) {
            EEPROM.write(2, server.arg("scene").toInt());
            EEPROM.commit();
          }
          apply_scene(server.arg("scene").toInt(), light);
        } else {
          if (server.arg("bri") != "") {
            bri[light] = server.arg("bri").toInt();
          }
        }
      } else if (server.hasArg("on")) {
        if (server.arg("on") == "true") {
          light_state[light] = true; {
            if (EEPROM.read(1) == 0 && EEPROM.read(0) == 0) {
              EEPROM.write(0, 1);
            }
          }
        } else {
          light_state[light] = false;
          if (EEPROM.read(1) == 0 && EEPROM.read(0) == 1) {
            EEPROM.write(0, 0);
          }
        }
        EEPROM.commit();
      } else if (server.hasArg("alert")) {
        if (light_state[light]) {
          current_bri[light] = 0;
        } else {
          current_bri[light] = 255;
        }
      }
      if (light_state[light]) {
        step_level[light] = ((float)bri[light] - current_bri[light]) / transitiontime;
      } else {
        step_level[light] = current_bri[light] / transitiontime;
      }
    }
    if (server.hasArg("reset")) {
      ESP.reset();
    }

    String http_content = "<!doctype html>";
    http_content += "<html>";
    http_content += "<head>";
    http_content += "<meta charset=\"utf-8\">";
    http_content += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
    http_content += "<title>Light Setup</title>";
    http_content += "<link rel=\"stylesheet\" href=\"https://unpkg.com/purecss@0.6.2/build/pure-min.css\">";
    http_content += "</head>";
    http_content += "<body>";
    http_content += "<fieldset>";
    http_content += "<h3>Light Setup</h3>";
    http_content += "<form class=\"pure-form pure-form-aligned\" action=\"/\" method=\"post\">";
    http_content += "<div class=\"pure-control-group\">";
    http_content += "<label for=\"power\"><strong>Power</strong></label>";
    http_content += "<a class=\"pure-button"; if (light_state[0]) http_content += "  pure-button-primary"; http_content += "\" href=\"/?on=true\">ON</a>";
    http_content += "<a class=\"pure-button"; if (!light_state[0]) http_content += "  pure-button-primary"; http_content += "\" href=\"/?on=false\">OFF</a>";
    http_content += "</div>";
    http_content += "<div class=\"pure-control-group\">";
    http_content += "<label for=\"startup\">Startup</label>";
    http_content += "<select onchange=\"this.form.submit()\" id=\"startup\" name=\"startup\">";
    http_content += "<option "; if (EEPROM.read(1) == 0) http_content += "selected=\"selected\""; http_content += " value=\"0\">Last state</option>";
    http_content += "<option "; if (EEPROM.read(1) == 1) http_content += "selected=\"selected\""; http_content += " value=\"1\">On</option>";
    http_content += "<option "; if (EEPROM.read(1) == 2) http_content += "selected=\"selected\""; http_content += " value=\"2\">Off</option>";
    http_content += "</select>";
    http_content += "</div>";
    http_content += "<div class=\"pure-control-group\">";
    http_content += "<label for=\"scene\">Scene</label>";
    http_content += "<select onchange = \"this.form.submit()\" id=\"scene\" name=\"scene\">";
    http_content += "<option "; if (EEPROM.read(2) == 0) http_content += "selected=\"selected\""; http_content += " value=\"0\">Relax</option>";
    http_content += "<option "; if (EEPROM.read(2) == 1) http_content += "selected=\"selected\""; http_content += " value=\"1\">Bright</option>";
    http_content += "<option "; if (EEPROM.read(2) == 2) http_content += "selected=\"selected\""; http_content += " value=\"2\">Nightly</option>";
    http_content += "</select>";
    http_content += "</div>";
    http_content += "<br>";
    http_content += "<div class=\"pure-control-group\">";
    http_content += "<label for=\"state\"><strong>State</strong></label>";
    http_content += "</div>";
    http_content += "<div class=\"pure-control-group\">";
    http_content += "<label for=\"bri\">Bri</label>";
    http_content += "<input id=\"bri\" name=\"bri\" type=\"text\" placeholder=\"" + (String)bri[0] + "\">";
    http_content += "</div>";

    http_content += "<div class=\"pure-controls\">";
    http_content += "<span class=\"pure-form-message\"><a href=\"/?alert=1\">alert</a> or <a href=\"/?reset=1\">reset</a></span>";
    http_content += "<label for=\"cb\" class=\"pure-checkbox\">";
    http_content += "</label>";
    http_content += "<button type=\"submit\" class=\"pure-button pure-button-primary\">Save</button>";
    http_content += "</div>";
    http_content += "</fieldset>";
    http_content += "</form>";
    http_content += "</body>";
    http_content += "</html>";

    server.send(200, "text/html", http_content);

  });

  server.onNotFound(handleNotFound);

  server.begin();
}

void loop() {
  ArduinoOTA.handle();
  server.handleClient();
  lightEngine();
}
mariusmotea commented 6 years ago

The code is identical with original one, the differences appear only if conditions if (digitalRead(button1_pin) == HIGH) and if (digitalRead(button2_pin) == HIGH) are true. I will create a prototype today and test this, but i suspect the issue is at hardware level here.

erazor666 commented 6 years ago

I'm not sure whats going on with them, the weird thing is the behavior in hue app. That the light shows up as unreachable and still sort of works. This modification seems to somewhat break the sketch. Its using wemos d1 mini as controller.

mariusmotea commented 6 years ago

Is not strange for me and i will tell you why. In order to have a successful http request the function server.handleClient() must run from loop, but this is not executed fast enough because i add:

      while (digitalRead(button2_pin) == HIGH && i < 30) {
        delay(20);
        i++;

This delay made made internal webserver to not be so reliable and ger request have a lower timeout than put request in my pyhthon script.

mariusmotea commented 6 years ago

Did you connect some 10k resistors of these pins to ground to be sure there are pulled down, maybe internal resistors are not working?

mariusmotea commented 6 years ago

I perform this test, i have exactly the same behavior like you described. After i connect my multi-meter to measure the voltage to the pin, it drop instant to 0V and the light start to work normally. So i believe internal pull down resistors are not working, you need to connect any resistor with value up to 500K to ground. I made some improvements on code:

  if (in_transition) {
    delay(6);
    in_transition = false;
  } else {
    if (digitalRead(button1_pin) == HIGH) {
      int i = 0;
      while (digitalRead(button1_pin) == HIGH && i < 30) {
        delay(20);
        i++;
      }
      for (int light = 0; light < lightsCount; light++) {
        if (i < 30) {
          // there was a short press
          light_state[light] = true;
        }
        else {
          // there was a long press
          bri[light] += 56;
          if (bri[light] > 255) {
            // don't increase the brightness more then maximum value
            bri[light] = 255;
          }
        }
      }
    } else if (digitalRead(button2_pin) == HIGH) {
      int i = 0;
      while (digitalRead(button2_pin) == HIGH && i < 30) {
        delay(20);
        i++;
      }
      for (int light = 0; light < lightsCount; light++) {
        if (i < 30) {
          // there was a short press
          light_state[light] = false;
        }
        else {
          // there was a long press
          bri[light] -= 56;
          if (bri[light] < 1) {
            // don't decrease the brightness less than minimum value.
            bri[light] = 1;
          }
        }
      }
    }
  }

remove:

  digitalWrite(button1_pin, LOW);
  digitalWrite(button2_pin, LOW);

because i found them useless

erazor666 commented 6 years ago

Awesome, i will test this out when i get a spare moment today. I have resistors in all sizes and shapes :)

mariusmotea commented 6 years ago

In code i found one issue while changing the brightness, i will update the code once i successfully test this.

erazor666 commented 6 years ago

Is it a good idea to have this in the official code, commented out if it interferes with the normal functioning in any way. Or another sketch with this functionality enabled. Maybe some wiring diagrams or pcb layouts.

mariusmotea commented 6 years ago

i made a test with SK6812 neopixel ring and is working. This is the commit https://github.com/mariusmotea/diyHue/commit/41f76e20156e148da82b0b205a06069d96464eed I will try to do the same for others, hope to not insert bugs.

erazor666 commented 6 years ago

Maybe commit a 2nd sketch with this functionality if there is danger of introducing bugs, not all users want/need this function i guess.

mariusmotea commented 6 years ago

by default it will be disabled, if you need it then #define use_hardware_switch false must be changed to true.

erazor666 commented 6 years ago

I have a test rig for the single color stipe running, now, is it much modification to get that updated? (For testing the button code) Edit: this would be the "generic dimmable" one

mariusmotea commented 6 years ago

That is already done, it is located in develop branch. Remember to change define use_hardware_switch to true. Maybe you can test also with tx and rx pins (gpio 1 and 3) that are free in most of projects.

erazor666 commented 6 years ago

Hello, Buttons work now. Tested them a bit, Gonna build a PCB with the buttons on for further testing. its only on breadboard stage at the moment, but this is pretty much perfect for the lights that need a local button. Well done sir :)

EDIT: I maybe found a bug, when you have the light at say 80% and you wanna dim to 40% (just example %) you hold the OFF/down button till you have the desired brightness, when you let go of the button the light turns off instead staying on the requested %. If you turn the light on again it is at the correct dim level. Dont know if its a bug with my hardware here or its code related? It is also possible the ON button has the same bug, but not able to see it since the light is allready on.

EDIT2: Is there any way to have the switch activate on "low" instead? Seems less complicated, only need the gpio and gnd on the switch. No resistor or vcc needed in that case. On espeasy i always use these, push button active low type deals. But are there are reasons that "active high" switches are better or needed ?

mariusmotea commented 6 years ago

Hi,

I did not saw your edits. True, reading the code in case of long press, when released it will trigger also the short press. It can be fixed. If you want to have trigger for low, then:

    if (digitalRead(button1_pin) == LOW) {
      int i = 0;
      while (digitalRead(button1_pin) == LOW && i < 30) {
        delay(20);
        i++;
      }
.....
mariusmotea commented 6 years ago

I guess this will work:

  } else if (use_hardware_switch == true) {
    bool long_press;
    if (digitalRead(button1_pin) == HIGH) {
      int i = 0;
      while (digitalRead(button1_pin) == HIGH && i < 30) {
        delay(20);
        i++;
      }
      for (int light = 0; light < lightsCount; light++) {
        if (i < 30) {
          // there was a short press
          if (long_press) {
            long_press = false;
          } else {
            light_state[light] = true;
          }
        }
        else {
          // there was a long press
          bri[light] += 56;
          if (bri[light] > 254) {
            // don't increase the brightness more then maximum value
            bri[light] = 254;
          }
          long_press = true;
        }
        process_lightdata(light, 4);
      }
    } else if (digitalRead(button2_pin) == HIGH) {
      int i = 0;
      while (digitalRead(button2_pin) == HIGH && i < 30) {
        delay(20);
        i++;
      }
      for (int light = 0; light < lightsCount; light++) {
        if (i < 30) {
          // there was a short press
          if (long_press) {
            long_press = false;
          } else {
            light_state[light] = false;
          }
        }
        else {
          // there was a long press
          bri[light] -= 56;
          if (bri[light] < 1) {
            // don't decrease the brightness less than minimum value.
            bri[light] = 1;
          }
          long_press = true;
        }
        process_lightdata(light, 4);
      }
    }
  }
erazor666 commented 6 years ago

I see, low switches seem simpler to implement in pcb and such. Not knowing much about the subtle differences, are there advantages or drawbacks on high vs low? Only ever used low ones as mentioned. I'll test it in a little while. Is the code bit simple to fix also?