Compare commits

...

16 Commits
1.0 ... master

Author SHA1 Message Date
7c31b15de2 Publish guestureenabled on change and state updates 2020-10-11 21:25:05 +02:00
01575e2937 Reduced minimum allowed brightness to 5% 2020-10-11 10:10:40 +02:00
f9a92bceca Added guesture toggle, saved in config 2020-10-10 23:11:14 +02:00
fbcac7c884 Fix for self toggling light #6 by lowering the largest accepted distance.
Apparently with brighter LEDs the usable range decreased at the far end.
Also added an improvement on the detection of toggling to be less sensitive. This helps when dimming to not toggle accidentally.
2020-07-15 19:57:53 +02:00
a4f8106009 Added debug information 2020-07-15 14:51:50 +02:00
f5ea07c90a Changes to availability topic 2020-07-15 12:14:19 +02:00
469af4820e Added both lamps to OTA configuration in platformIO 2020-07-15 12:12:13 +02:00
2a5f61101a changed config topic 2020-07-05 14:15:24 +02:00
a7857a3e45 updated readme 2020-07-05 13:57:57 +02:00
04c1f802a8 Added debug topic
For now the current configuration is published on this topic at boot
Further it could be used for any kind of debug information
2020-07-05 13:48:10 +02:00
9e553b4f67 mqtt reconnect fix 2020-07-03 18:28:34 +02:00
ae5641b188 WiFi fix - possibly solved
Tried this fix on one lamp for about a day now without disconnects. Mqtt though does disconnect but the lamp is reachable on the LAN, can be updated etc.
2020-07-03 17:48:41 +02:00
1fdd69fd2d Updated Readme with availability topic 2020-07-02 18:25:50 +02:00
0297c6dab5 New lower "lowest" brightness 2020-07-02 18:23:57 +02:00
0c7633528a Added availability topic 2020-07-02 18:16:40 +02:00
38a34e075d Updated README 2020-06-19 22:20:18 +02:00
7 changed files with 154 additions and 38 deletions

View File

@ -33,7 +33,12 @@
"tuple": "cpp", "tuple": "cpp",
"type_traits": "cpp", "type_traits": "cpp",
"utility": "cpp", "utility": "cpp",
"typeinfo": "cpp" "typeinfo": "cpp",
"complex": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"iomanip": "cpp",
"iostream": "cpp"
}, },
"editor.formatOnSave": true, "editor.formatOnSave": true,
} }

View File

@ -1,8 +1,21 @@
# Bedside table # Bedside table
An esp8266 powered **IKEA Tvärfot** with a Neopixel ring and a proximity sensor. An esp8266 powered **IKEA Tvärfot** with a Neopixel ring and a proximity sensor. The lamp has the following functionality:
- Hold your hand still above the lamp for > 1s to toggle ON/OFF.
- Raise or lower your hand above the lamp to dim it.
- Set color, brightness and toggle it ON/OFF through mqtt e.g. using Home Assistant.
- Initial configuration is done over Serial. Configuration can be updated through mqtt later.
- Color and brightness will be stored in flash every time the lamp is turned off. This way it will retain these settings on powercycle or reboot.
![](https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fwww.ikea.com%2Fbe%2Ffr%2Fimages%2Fproducts%2Ftvaerfot-table-lamp-black-white__0772756_PE756088_S5.JPG%3Ff%3Dxxs&f=1&nofb=1) ![](https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fwww.ikea.com%2Fbe%2Ffr%2Fimages%2Fproducts%2Ftvaerfot-table-lamp-black-white__0772756_PE756088_S5.JPG%3Ff%3Dxxs&f=1&nofb=1)
## LED strip
A NeoPixel strip with 7 diods is used for the light and connects to **D8**. This could really be any DIO. For this change the variable ***ledPin*** in ***main.cpp***.
To power the LED strip connect it to the 5V supply. The power output on the ESP8266 may not be enough if connected to the *raw* pin when powering from the USB port. The recommendation is to power it in parallel to the ESP by e.g. supplying the ESP with power on the raw pin while using the same for powering the LED.
## Distance Sensor
To measure the distance an IR based sensor from Sharp (2Y0A21) is used. This sensor is connected to the analog input **A0**. This is the only analog input on the ESP8266.
To power the Sharp sensor connect it to any free 3.3V pin on the ESP.
## Writing a configuration ## Writing a configuration
Open a Serial Monitor at baudrate 9600 and paste the following json configuration adapted to your needs. Open a Serial Monitor at baudrate 9600 and paste the following json configuration adapted to your needs.
Before sending press the on board button "flash" or connect a button to D0. You will now have 10s to send the configuration. The onboard LED will light up as long as the board reads Serial input. If successful the new configuration should be shown in the terminal. Before sending press the on board button "flash" or connect a button to D0. You will now have 10s to send the configuration. The onboard LED will light up as long as the board reads Serial input. If successful the new configuration should be shown in the terminal.
@ -17,6 +30,7 @@ It is also possible to send the same json configuration on mqtt using topic *lig
"mqttuser": "mymqtt_user", "mqttuser": "mymqtt_user",
"mqttpass": "7j%!fs#", "mqttpass": "7j%!fs#",
"mqttport": "1883", "mqttport": "1883",
"guestureEnabled" : "true"
} }
``` ```
@ -42,6 +56,15 @@ An identical json package is both sent and received to set and get the status of
### Configuration topic ### Configuration topic
See *Writing a configuration*. This can also be done using mqtt at topic ***light/bedlamp/relay/0/config*** See *Writing a configuration*. This can also be done using mqtt at topic ***light/bedlamp/relay/0/config***
### Debug topic
Meant for debugging there is a topic ***light/bedlamp/debug***. This topic will output:
* The configuration of the lamp at boot
* Uptime (s) is sent repeatedly at every *state* update (default each 120s)
* All incoming topics and payloads
### Guesture Enable topic
Topic to activate or deactivate guestures for controlling the lamp ***light/bedlamp/guestureenabled/set***. Payload **1** or **0**.
## Home Assistant ## Home Assistant
Adjust the name and topics to suite your configuration Adjust the name and topics to suite your configuration
``` ```
@ -50,6 +73,9 @@ Adjust the name and topics to suite your configuration
schema: json schema: json
state_topic: light/bedlamp/relay/0 state_topic: light/bedlamp/relay/0
command_topic: light/bedlamp/relay/0/set command_topic: light/bedlamp/relay/0/set
availability_topic: light/bedlamp/status
payload_available: 1
payload_not_available: 0
brightness: true brightness: true
rgb: true rgb: true
``` ```

View File

@ -13,6 +13,7 @@ public:
char mqttUser[10]; char mqttUser[10];
char mqttPass[20]; char mqttPass[20];
int mqttPort; int mqttPort;
bool gestureEnabled;
uint8_t brightness; uint8_t brightness;
std::array<uint8_t, 3> color; std::array<uint8_t, 3> color;
} Data; } Data;
@ -21,6 +22,10 @@ public:
OutTopic, OutTopic,
InTopic, InTopic,
ConfigTopic, ConfigTopic,
AvailabilityTopic,
DebugTopic,
InGuestureEnabled,
OutGuestureEnabled,
}; };
static Config& Instance(); static Config& Instance();

View File

@ -13,9 +13,16 @@ platform = espressif8266
board = esp12e board = esp12e
framework = arduino framework = arduino
[env:esp8266 OTA] [env:esp8266 OTA Right]
platform = espressif8266 platform = espressif8266
board = esp12e board = esp12e
framework = arduino framework = arduino
upload_protocol = espota upload_protocol = espota
upload_port = bedlamp-philip upload_port = bedlamp-right
[env:esp8266 OTA Left]
platform = espressif8266
board = esp12e
framework = arduino
upload_protocol = espota
upload_port = bedlamp-left

View File

@ -65,7 +65,20 @@ String Config::getMqttTopic(MqttTopic topic)
return String("light/" + String(data.hostname) + "/relay/0/set"); return String("light/" + String(data.hostname) + "/relay/0/set");
break; break;
case (MqttTopic::ConfigTopic): case (MqttTopic::ConfigTopic):
return String("light/" + String(data.hostname) + "/relay/0/config"); return String("light/" + String(data.hostname) + "/config");
break;
case (MqttTopic::AvailabilityTopic):
return String("light/" + String(data.hostname) + "/status");
break;
case (MqttTopic::DebugTopic):
return String("light/" + String(data.hostname) + "/debug");
break;
case (MqttTopic::InGuestureEnabled):
return String("light/" + String(data.hostname) +
"/guestureenabled/set");
break;
case (MqttTopic::OutGuestureEnabled):
return String("light/" + String(data.hostname) + "/guestureenabled");
break; break;
} }
return {}; return {};

View File

@ -22,6 +22,8 @@ const int sensorPin = A0;
const int ledPin = D8; const int ledPin = D8;
const int ledCount = 7; const int ledCount = 7;
const int publishInterval = 120;
Config& config = Config::Instance(); Config& config = Config::Instance();
EasyButton button(buttonPin); EasyButton button(buttonPin);
@ -33,7 +35,7 @@ PubSubClient mqttClient(wifiClient);
WebOTA webOTA(82); WebOTA webOTA(82);
Ticker tickerLedStatus, tickerMqttCyclic, tickerMqttReconnect; Ticker tickerPublishStatus;
typedef struct { typedef struct {
float distance; // Distance in mm float distance; // Distance in mm
@ -53,6 +55,7 @@ void setConfig(String jsonConfig)
String mqttUser = obj["mqttuser"]; String mqttUser = obj["mqttuser"];
String mqttPass = obj["mqttpass"]; String mqttPass = obj["mqttpass"];
int mqttPort = obj["mqttport"]; int mqttPort = obj["mqttport"];
bool gestureEnabled = obj["gestureEnabled"];
Serial.printf("Parsed hostname: %s, ssid: %s, pass: %s\n", Serial.printf("Parsed hostname: %s, ssid: %s, pass: %s\n",
hostname.c_str(), hostname.c_str(),
ssid.c_str(), ssid.c_str(),
@ -72,6 +75,8 @@ void setConfig(String jsonConfig)
sizeof(config.data.mqttPass)); sizeof(config.data.mqttPass));
config.data.mqttPort = mqttPort; config.data.mqttPort = mqttPort;
config.data.gestureEnabled = gestureEnabled;
config.write(); config.write();
Serial.println("rebooting..."); Serial.println("rebooting...");
@ -79,6 +84,29 @@ void setConfig(String jsonConfig)
} }
} }
String configToString()
{
DynamicJsonDocument doc(1024);
auto jsonConfig = doc["config"];
jsonConfig["hostname"] = config.data.hostname;
jsonConfig["ssid"] = config.data.ssid;
jsonConfig["pass"] = config.data.pass;
jsonConfig["mqttserver"] = config.data.mqttServer;
jsonConfig["mqttuser"] = config.data.mqttUser;
jsonConfig["mqttpass"] = config.data.mqttPass;
jsonConfig["mqttport"] = config.data.mqttPort;
jsonConfig["guestureenabled"] = config.data.gestureEnabled;
jsonConfig["brightness"] = config.data.brightness;
JsonArray color = jsonConfig.createNestedArray("color");
color.add(config.data.color.at(0));
color.add(config.data.color.at(1));
color.add(config.data.color.at(2));
String output;
serializeJson(doc, output);
return output;
}
void checkSerial() void checkSerial()
{ {
String s = Serial.readString(); String s = Serial.readString();
@ -97,9 +125,7 @@ void onPressed()
unsigned long t = millis(); unsigned long t = millis();
// Stop all tickers // Stop all tickers
tickerLedStatus.detach(); tickerPublishStatus.detach();
tickerMqttCyclic.detach();
tickerMqttReconnect.detach();
while (millis() - t < 10000) while (millis() - t < 10000)
{ {
@ -112,12 +138,25 @@ void onPressed()
void mqttPublishState() void mqttPublishState()
{ {
if (mqttClient.connected()) if (mqttClient.connected())
{
mqttClient.publish( mqttClient.publish(
config.getMqttTopic(Config::MqttTopic::OutTopic).c_str(), config.getMqttTopic(Config::MqttTopic::OutTopic).c_str(),
myLed.get().c_str()); myLed.get().c_str());
mqttClient.publish(
config.getMqttTopic(Config::MqttTopic::AvailabilityTopic).c_str(),
"1");
mqttClient.publish(
config.getMqttTopic(Config::MqttTopic::OutGuestureEnabled).c_str(),
String(config.data.gestureEnabled).c_str());
auto uptime = millis() / 1000;
mqttClient.publish(
config.getMqttTopic(Config::MqttTopic::DebugTopic).c_str(),
String("{ \"uptime\":" + String(uptime) + "}").c_str());
}
} }
void mqttReconnect() void mqttConnect()
{ {
if (!mqttClient.connected()) if (!mqttClient.connected())
{ {
@ -132,31 +171,43 @@ void mqttReconnect()
config.getMqttTopic(Config::MqttTopic::InTopic).c_str()); config.getMqttTopic(Config::MqttTopic::InTopic).c_str());
mqttClient.subscribe( mqttClient.subscribe(
config.getMqttTopic(Config::MqttTopic::ConfigTopic).c_str()); config.getMqttTopic(Config::MqttTopic::ConfigTopic).c_str());
mqttClient.subscribe(
config.getMqttTopic(Config::MqttTopic::InGuestureEnabled)
.c_str());
} else } else
{ {
Serial.print("failed, rc="); Serial.printf("failed, rc=%d\n", mqttClient.state());
Serial.print(mqttClient.state());
Serial.println(" try again in 5 seconds");
tickerMqttReconnect.once(5, mqttReconnect);
} }
} }
} }
void mqttCallback(char* topic, byte* payload, unsigned int length) void mqttCallback(char* topic, byte* payload, unsigned int length)
{ {
String msg(reinterpret_cast<char const*>(payload)); const String sTopic(topic);
String msg;
msg.concat(reinterpret_cast<char*>(payload), length);
Serial.printf("Message arrived [%s] \n", topic); Serial.printf("Message arrived [%s] \n", topic);
Serial.println(msg.c_str()); Serial.println(msg.c_str());
if (String(topic) == config.getMqttTopic(Config::InTopic)) if (sTopic == config.getMqttTopic(Config::InTopic))
{ {
myLed.set(msg); myLed.set(msg);
mqttPublishState(); mqttPublishState();
} else if (String(topic) == config.getMqttTopic(Config::ConfigTopic)) } else if (sTopic == config.getMqttTopic(Config::ConfigTopic))
{ {
setConfig(msg); setConfig(msg);
} else if (sTopic == config.getMqttTopic(Config::InGuestureEnabled))
{
config.data.gestureEnabled = (msg == "1");
config.write();
mqttClient.publish(
config.getMqttTopic(Config::MqttTopic::OutGuestureEnabled).c_str(),
String(config.data.gestureEnabled).c_str());
} }
mqttClient.publish(
config.getMqttTopic(Config::MqttTopic::DebugTopic).c_str(),
String("{\"received\": { \"" + sTopic + "\": " + msg + " } }").c_str());
} }
void offCallback() void offCallback()
@ -189,6 +240,7 @@ void setup()
config.load(); config.load();
myLed.setState( myLed.setState(
MyLed::State({config.data.color, config.data.brightness, true})); MyLed::State({config.data.color, config.data.brightness, true}));
WiFi.setAutoReconnect(true);
WiFi.begin(config.data.ssid, config.data.pass); WiFi.begin(config.data.ssid, config.data.pass);
WiFi.hostname(config.data.hostname); WiFi.hostname(config.data.hostname);
Serial.print("Connecting"); Serial.print("Connecting");
@ -210,9 +262,12 @@ void setup()
ArduinoOTA.setHostname(config.data.hostname); ArduinoOTA.setHostname(config.data.hostname);
ArduinoOTA.begin(); ArduinoOTA.begin();
tickerLedStatus.attach(120, mqttPublishState); tickerPublishStatus.attach(publishInterval, mqttPublishState);
tickerMqttCyclic.attach(60 * 5, mqttReconnect); mqttConnect();
mqttReconnect();
mqttClient.publish(
config.getMqttTopic(Config::MqttTopic::DebugTopic).c_str(),
configToString().c_str());
webOTA.setup(&WiFi); webOTA.setup(&WiFi);
} }
@ -234,7 +289,7 @@ SensorData getDist()
// https://global.sharp/products/device/lineup/data/pdf/datasheet/gp2y0a21yk_e.pdf // https://global.sharp/products/device/lineup/data/pdf/datasheet/gp2y0a21yk_e.pdf
const int maxVal = 780; // 60 mm const int maxVal = 780; // 60 mm
const int minVal = 173; // 800 mm const int minVal = 173; // 800 mm
const int maxValidDist = 610; const int maxValidDist = 480;
const int minValidDist = 70; const int minValidDist = 70;
long dist = map(analogRead(sensorPin), maxVal, minVal, 60, 800); long dist = map(analogRead(sensorPin), maxVal, minVal, 60, 800);
@ -244,7 +299,7 @@ SensorData getDist()
float normalizedDistance = float normalizedDistance =
(static_cast<float>(dist) - static_cast<float>(minValidDist)) / (static_cast<float>(dist) - static_cast<float>(minValidDist)) /
static_cast<float>(maxValidDist - minValidDist); static_cast<float>(maxValidDist - minValidDist);
// Serial.printf("Norm dist: %f\n", normalizedDistance); // Serial.printf("Dist: %d, normalized: %f\n", dist, normalizedDistance);
return {.distance = static_cast<float>(dist), return {.distance = static_cast<float>(dist),
.distanceNormalized = normalizedDistance, .distanceNormalized = normalizedDistance,
@ -261,7 +316,7 @@ void evalDist(SensorData data)
static float lastDist = data.distance; static float lastDist = data.distance;
const float stillThreshold = 20.0f; // Distance in mm const float stillThreshold = 15.0f; // Distance in mm
const float releaseThreshold = const float releaseThreshold =
70.0f; // If value increases more than this it is considered a release 70.0f; // If value increases more than this it is considered a release
@ -326,19 +381,17 @@ void evalDist(SensorData data)
void slowLoop() void slowLoop()
{ {
if (WiFi.status() != WL_CONNECTED) if (config.data.gestureEnabled)
{ {
Serial.println("WiFi connection lost. Trying to reconnect..."); SensorData data = getDist();
WiFi.reconnect(); evalDist(data);
} }
SensorData data = getDist();
evalDist(data);
myLed.run(); myLed.run();
mqttClient.loop(); if (!mqttClient.loop())
mqttConnect();
webOTA.run(); webOTA.run();
} }

View File

@ -2,8 +2,10 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <limits>
namespace { namespace {
const float minBrightness = 0.2; const float minBrightness = 0.05;
} }
MyLed::MyLed(int ledPin, int ledCount, bool effects) MyLed::MyLed(int ledPin, int ledCount, bool effects)
@ -56,7 +58,7 @@ void MyLed::toggle()
void MyLed::set(String jsonState) void MyLed::set(String jsonState)
{ {
DynamicJsonDocument doc(1024); DynamicJsonDocument doc(2048);
deserializeJson(doc, jsonState); deserializeJson(doc, jsonState);
JsonObject obj = doc.as<JsonObject>(); JsonObject obj = doc.as<JsonObject>();
Serial.println(jsonState.c_str()); Serial.println(jsonState.c_str());
@ -107,7 +109,7 @@ MyLed::State MyLed::getState()
void MyLed::setBrightness(float brightness) void MyLed::setBrightness(float brightness)
{ {
brightness = max(minBrightness, brightness); brightness = min(max(minBrightness, brightness), 1.0f);
if (_useEffects) if (_useEffects)
{ {
@ -151,8 +153,13 @@ void MyLed::registerOffCallback(CallbackFunc callback)
void MyLed::apply() void MyLed::apply()
{ {
_strip.fill(_strip.Color(static_cast<uint8_t>(_color.at(0) * _brightness), uint8_t a = 254;
static_cast<uint8_t>(_color.at(1) * _brightness), _strip.fill(
static_cast<uint8_t>(_color.at(2) * _brightness))); _strip.Color(min(static_cast<uint8_t>(_color.at(0) * _brightness),
std::numeric_limits<uint8_t>::max()),
min(static_cast<uint8_t>(_color.at(1) * _brightness),
std::numeric_limits<uint8_t>::max()),
min(static_cast<uint8_t>(_color.at(2) * _brightness),
std::numeric_limits<uint8_t>::max())));
_strip.show(); _strip.show();
} }