The Luke Roberts lamp can be controlled over Bluetooth BLE or using a cloud based API
To enable integration into smarthome systems like Iobroker, Home Assistant or Openhab the Cloud REST API can be used. However I also want to control my lamp the traditional way (Wall switches, rotary dimmer..). This was my main motivation - I want to control the lamp using a rotary dimmer not from my smartphone.
Therefore, I implemented a ESP32 based bridge that talks to the lamp using BLE. The gateway uses MQTT for control and status messages. A extremly simply REST API is also included but currently only supports sending commands and not querying the current status.
The lamp doesn't offer a public API to query the current values of brightness, colortemperatur or other parameters. Only the currently selected scene can be queried over BLE
Therefore, the state is kept the gateways memory. Because every scene has different brightness levels I created a translation map for the 7 Built-in scenes (just by trial and error) to know the initial brightness when switching to a new scene..
There is an undocumented API to query the current brightness and color temperature of the the downlight. Since it's an undocumented API things may break after a firmware update.
Only an ESP32 is required to control the lamp using MQTT or HTTP
All configuration settings are done in platformio_usersettings.ini. Make sure to rename the sample and modify the values for your environment
If you attach a rotary switch to the controller you need to define GPIO pins used
;Set pin A for the CLK signal
-DROTARY_PIN_A=GPIO_NUM_26
;Set pin B for the DT
-DROTARY_PIN_A=GPIO_NUM_26
-DROTARY_STEP_VALUE=5
ROTARY_STEP_VALUE defines the increase/decrease for a detected rotary moves. When the time between 2 events is below 60 ms the value of ROTARY_STEP_VALUE is doubled and if the time is below 30 ms the effective step value is ROTARY_STEP_VALUE*4. Default value is 5.
If the rotary has a switch define the pin for the switch (of course it can be a seperate switch as well)
-DROTARY_BUTTON_PIN=GPIO_NUM_25
The brightness is controlled by turning the rotary encoder. The switch toggles power. Keeping the switch pressed cycles through the scenes
The brightness can also be controlled using buttons BUTTON_UP_PIN defines which pin is used increase the brightness and BUTTON_DOWN_PIN defines the pin to decrease the brightness. Also define ROTARY_BUTTON_PIN if you want a power toggle button.
Example :
-DBUTTON_DOWN_PIN=GPIO_NUM_15
-DBUTTON_UP_PIN=GPIO_NUM_17
If you have only 2 buttons you can redefine the default actions to turn on power when clicking button 1 and of with button 2. A long press on a buttons dims up or down.
Example :
-DBUTTON_DOWN_PIN=GPIO_NUM_15
-DBUTTON_UP_PIN=GPIO_NUM_17
-BUTTON_UP_CLICK_ACTION=kPowerOn
-BUTTON_DOWN_CLICK_ACTION=kPowerOff
-DBUTTON_UP_PIN_LONG_PRESS_ACTION=kChangeBrightness
-DBUTTON_UP_PIN_STEP_VALUE=10
-DBUTTON_DOWN_PIN_LONG_PRESS_ACTION=kChangeBrightness
-DBUTTON_DOWN_PIN_STEP_VALUE=-10
If you attach a single button define the PIN to use (SINGLE_BUTTON_PIN)
This button toggles the power (actually when released).
The brightness can be decreased using a long press. Another long press within 10 seconds switches the direction and the brightness will be increased.
A double-click will switch to the next scene
Note: The GPIO Pins are used in the INPUT_PULLUP configuration the avoid the hassle of external pullups. Make sure you use a GPIO that supports pullup-mode. (All GPIOS except 35,36 and 39)
To use 3 buttons with a single GPIO the buttons need encoding with resistors
The buttons create a voltage divider where each button results in a different voltage level at the defined GPIO PIN. The voltage of each button is defined by the resistor connected to the button. However, the exact values are not important if they a different enough to be clearly distinguishable. With the values chosen in this example you get 3.3V at the GPIO pin if no button is pressed.
To map the buttons the ADC reading values are required. The easiest way to get these values is connecting the to your ESP32 and run at simple sketch to read out the values
const int adc_pin= 35;
int adc_value = 0;
void setup()
{
Serial.begin(115200);
}
void loop()
{
adc_value = analogRead(adc_pin);
Serial.print("ADC VALUE = ");
Serial.println(adc_value);
delay(500);
}
Example configuration for with the above resistor values using GPIO 35 to read the button values:
-DRESISTOR_BUTTON_PIN=35
-DRESISTOR_BUTTON_UP=1000
-DRESISTOR_BUTTON_DOWN=1700
-DRESISTOR_BUTTON_SWITCH=3000
Every ADC reading above 1000 and below 1700 will map to the first button. Readings between 1700 and 3000 to the second button and everything above 3400 and below 4000 to the third button) (It would probably make sense to use a 22k instead of a 47k resistor for the third button, but I took what I found first 😊)
Because this mode doesn’t need an internal pull-up pins 34,35,36 or 39 can be used (see https://github.com/bxparks/AceButton/blob/develop/docs/resistor_ladder/README.md)
You can define up to 4 Resistor buttons. UP, DOWN, SWITCH and SWITCH. if you are using a four button switch there are actualy 2 more levels available. When 2 buttons are pressed together the resistence is lower than the 2 signle buttons
If UP and DOWN are pressed together the resistence is 3.3k. If SW and SW2 are pressed together the resistence is 15k. Therefore 2 "virtual" buttons can be defined.
These are the ADC Values
Resistor | Calculated | Measured |
---|---|---|
3.3k | 1016 | 970 |
5.1k | 1383 | 1450 |
10k | 2048 | 1840 |
15k | 2458 | 2250 |
22k | 2816 | 2620 |
47k | 3377 | 3200 |
-DRESISTOR_BUTTON_PIN=35
## Defines 6 buttons connected one GPIO using voltage dividers
## A virtual pin triggered if RESISTOR_BUTTON_UP and DRESISTOR_BUTTON_DOWN are pressed together
-DRESISTOR_BUTTON_D1=970 ## 3.3k (both buttons pressed 5.1 and 10k parallel)
-DRESISTOR_BUTTON_UP=1450 ## 5.1k
-DRESISTOR_BUTTON_DOWN=1840 ## 10k
## A virtual pin triggered if RESISTOR_BUTTON_SWITCH and DRESISTOR_BUTTON_SWITCH2 are pressed together
-DRESISTOR_BUTTON_D2=2250 ## 15k (both buttons pressed 22k and 47k parallel)
-DRESISTOR_BUTTON_SWITCH=2620 ## 22k
-DRESISTOR_BUTTON_SWITCH2=3220 ## 47k
In this example UP and DOWN use low resistor values and SW/SW2 higher values. Because using 22k and 47k in parallel gives 15k we have a value for D2 that is still higher than DOWN
Note you have to assign the levels ascending in the order given here. (Lowest value for RESISTOR_BUTTON_D1 and highest for RESISTOR_BUTTON_SWITCH2). The range of a button is defined by it's level and the difference to the next higher level. A button is considered a match if the measured ADC value is between (level - difference next level/2)and (level + /difference next level/2 ). Example for BUTTON_SWITCH: Difference to next level is 600. Every measured level between 2321 and 2919 is a match.
If you want a buttons not to trigger don't define it or with level 0
-DRESISTOR_BUTTON_D1=0
The timings for long press and double click can be modified
For each button 3 actions can be defined:
Every action can be mapped to differents functions (enum button_function_codes)
Example: Set the action for a simple click on the rotary button to increase the color temperature by 15 steps (instead of the default action kPowerToggle). Change double-click to toggle the power instead and finally set long press to change the brightess (the same step value is used for all button actions )
-DROTARY_BUTTON_PIN=GPIO_NUM_25
-DROTARY_BUTTON_PIN_CLICK_ACTION=kChangeColorTemperature
-DROTARY_BUTTON_PIN_STEP_VALUE=15
-DROTARY_BUTTON_PIN_DOUBLE_CLICK_ACTION=kPowerToggle
-DROTARY_BUTTON_PIN_LONG_PRESS_ACTION=4 ; either the numeric value or the constant can be used
A on/off toggle switch can be used to toggle the power state of the lamp. Each switch change toggles power. Enable it by defining SWITCH_PIN Additional config settings LONG_PRESS_DELAY defines how long the button must be pressed for the first event. Default value is 1500 ms LONG_PRESS_INTERVAL defines how long it takes to trigger another long press event. Default is 1500 ms DOUBLE_KLICK_INTERVAL maximum time between 2 clicks to define them as a double-click. Default is 500 ms
By default, the lamp is turned off by setting the scene to 0. If you prefer a “real” power off a relay can be connnected to a GPIO PIN. The power command will then control the relay.
Example:
-DRELAY_PIN=5
All settings can be combined if different GPIO’s are used.
# Rotary
-DROTARY_PIN_A=GPIO_NUM_26
-DROTARY_PIN_B=GPIO_NUM_27
-DROTARY_BUTTON_PIN=GPIO_NUM_25
# Up / down button
-DBUTTON_DOWN_PIN=GPIO_NUM_16
-DBUTTON_UP_PIN=GPIO_NUM_18
# Switch
-DSWITCH_PIN=GPIO_NUM_23
# Single mode button
-DSINGLE_BUTTON_PIN=GPIO_NUM_19
# Voltage divider button
-DRESISTOR_BUTTON_PIN=35
-DRESISTOR_BUTTON_UP=970
-DRESISTOR_BUTTON_DOWN=1840
-DRESISTOR_BUTTON_SWITCH=3220
# Relay
-DRELAY_PIN=GPIO_NUM_32
# timings
-DLONG_PRESS_DELAY=1000
-DLONG_PRESS_INTERVAL=1000
-DDOUBLE_KLICK_INTERVAL=500
If you are getting compiler errors due to command line argument too you the buttons can be defined in my_buttondef.h instead. See my_buttondef.h.sample for an example
The wireless rotary sends the events to a smart home server that will then control the lamp using MQTT or HTTP In my case I use a Homematic IP Rotary Button.
It creates 6 different events: turn left, turn right, fast turn left, fast turn right, button press, long button press.
The advantage is that the esp device can be anywhere in the room and even use ethernet instead wifi if you have an esp32 with a ethernetport connected (Wifi and BLE can cause issues). At least for homematic ip the disadvantage is a higher duty cycle because turning the dimmer sends many commands. (and I don't like the design of the Homematic IP Rotary Button)
This project requires PlatformIO to build Before compiling and deploying a few settings must be configured in platformio_usersettings.ini. A sample is provided : platformio_usersettings.ini.sample
You can hardcode the device address of your lamp in platformio_usersettings.ini
-DLR_BLEADDRESS='"c4:b9:71:da:19:c7"'
If device address is not provided the gateway will scan for a device with the BLE Service ID "44092840-0567-11E6-B862-0002A5D5C51B" during startup and use the first address found. The address is published to the MQTT topic tele/yourdevicename/BLEADDRESS
From commandline
python -m pip install --upgrade pip
pip install --upgrade platformio
git clone https://github.com/martgras/LukeRobertsControl.git
# or
wget https://github.com/martgras/LukeRobertsControl/archive/master.zip
unzip master.zip
cd LukeRobertsControl-master/
mv platformio_usersettings.ini.sample platformio_usersettings.ini
# Edit the settings to for your environment
# nano platformio_usersettings.ini
pio run
The format is similar to tasmota
all commands use the topic cmnd/devicename>/command
topic: cmnd/\<devicename>/POWER payload ON/OFF 1/0 or TOGGLE
example: turn on the light on my device lrdimmer:
mosquitto_pub -h localhost -t "cmnd/lrdimmer/power" -m "off"
topic: cmnd/\<devicename>/DIMMER payload: