Hi there, if you have been following my recent tweets, you know how much I am enjoying my new standing desk: The E2B by FlexiSpot! So, on one late evening, I had an excess of creativity and I came up with a fun idea: What if I could use the Google Assistant to control my desk settings?
So I decided to build a proof of concept and 5 hours later...
Liquid error: internal
Let me walk you through the process...
Before we start, a couple of things to note:
- All the information provided here is provided on an "as is" and "as available" basis and you agree that you use such information entirely at your own risk. Under no circumstances will I be held responsible or liable in any way for any damages, losses, expenses, costs or liabilities whatsoever resulting from your use of the information and material presented here.
- I am not a professional Electronic engineer, but I do have some knowledge - from university - which is enough to put together this proof of concept. However, if you know a better alternative to the circuit I built, please let me know. I'll be more than happy to upgrade my setup!
Our plan for this project is as follow:
- Reverse engineer the desk controller to understand how the system works.
- Figure out a way to manually trigger the control signals without using the desk controller.
- Figure out a way to programmatically trigger the control signals.
- Hook up the Google Assistant to the system.
Well, obviously if you need to understand how a device works, the first thing to do is to open it (THIS WILL VOID THE WARRANTY). Also, make sure you have a spare device (or parts) in case things go wrong (which it did for me, read the next part for more details!). So, thankfully I had already anticipated this scenario and bought a spare controller. I'd like to thank the folks over FlexiSpot who nicely gave me a discount code!
By the way, if you want to buy the E2 standing desk, you can use the code "rffr1" to get a 20€ discount (only valid for the on the Fexispot website untill end of February 2019)!
Let's have a look at the outside of the desk controller which looks like this:
Once opened, the internal PCB board shows the following design:
Here is a basic explanation of each component illustrated in this PCB:
- Up and Down buttons allow you to raise and lower the desk from 70cm to 120cm (42.1″ to 63″);
- The Setting Modes button allows to memorize a certain height and store it into the M1, M2 or M3 slots (three programmable presets);
- The Auto-tracking button is used to set the sit/stand timer system and helps alert you when it's time to stand;
- The TM1650 is a LED microcontroller which takes care of the 8-segments display on the left (see full specs);
- The STM8S (STM8S103F2) is the main microcontroller which is the brain of the whole system (see datasheet here).
Now we need to figure out the commands to use to manually control the desk without really using (can we?) the desk controller!
Let the fun begins!
My first attempt was to try and reverse engineer the STM8S microcontroller so I could get the required signals that would instruct the motors to change position, and then I would model and send out those exact signals to the motors as if they were coming from the STM8S itself. Well, that was my plan!
However, while I was trying this process, I managed to damage the PIN 14 of the microcontroller which role was to lower the desk down (Down Button). Admittedly, even if I managed to get all the signals from the microcontroller, I would not have been able to lower the desk anymore - other than replacing the STM8S microcontroller unit which I would have done if I had a spare one!
So, I had to figure out another way. And actually, there was a much simpler way of doing exactly what I was looking for.
It turns out there was a simpler method that would allow me to send the signals to the motors without touching (or burning!) the STM8S microcontroller. I simply had to hack into the M1, M2, and M3 buttons! These modes work as presets that store a specific height of the desk and when triggered, they instruct the desk to go to that exact height, from any position.
This hacky method turns out to be more generic and can literally be applied to any electrical device which has button inputs.
Now, I was ready for the next step: figuring out a way to simulate a push button so the microcontroller thinks that one of the M1, M2 or M3 buttons was pushed.
For that, I had to build an electronic switch circuit.
Before we automate all the things, let's first try and test manually our approach. For that, I built a simple circuit which is an "NPN Transistor Switching Circuit". This transistor-based switch circuit is ideal for low voltage DC devices which is exactly what we are trying to do: remember that we are trying to simulate a push button which requires a few milli-amps to be triggered!
To make it simple, the areas of operation for a transistor switch are known as the Saturation Region and the Cut-off Region. This means that we will use the transistor as a switch by driving it back and forth between its "fully-OFF" (cut-off) and "fully-ON" (saturation) regions. Here is a very simplified illustration of the operation regions of a typical bipolar transistor:
Having said that, let's now have a look at a typical "NPN Transistor As A Switch" circuit:
In this circuit, we are using an LED as a load to demonstrate the switch process. And here is an "implementation" of such a circuit:
Very simple, right? Our circuit seems to work as expected.
Now let's move on and hook up the desk controller into this circuit:
In the diagram above, J1, J2 and 3 are the jumpers that will be connected to the wires that we soldered into the desk controller, respectively, Mode 1, Mode 2 and Mode 3. J0 is the ground which will also be connected to the desk controller.
And here is the actual circuit on the breadboard:
Note that the V1, V2 and V3 (and GND which is not in this picture) wires are intended to remain loose for the moment.
In order to automatically trigger the "push button" signals that will fool the STM8S microcontroller, we will need a programmable microcontroller. For this, you can use any board you have (Arduino, Nanode, Teensy, etc…) or even a Raspberry PI; anything with GPIOs.
I initially used an Arduino MEGA 2560 board in order to experiment with the code but then I realized that I didn't have a Wifi shield (which is required for the next part of this project). So as a reference, here is the Arduino code:
Here is the final circuit. Please note that the V1, V2, and V3 wires are now connected to PIN 1, 2 and 3 of the Onion Omega2+ chip:
And here is the breadboard implementation:
Now that everything is in place, all we need to do is to write a simple program that will toggle the GPIO PIN 1, 2 and 3: this will send a HIGH voltage up to 3.3V and a LOW voltage down to 0.4V.
So basically, when we set the output on PIN 1 to HIGH, this will deliver a 3.3V through the transistor-based switch circuit which in turn will saturate the transistor allowing it to close the switch and this will act as a push button. The STM8S will then receive the push signal from the M1 button and then instruct the desk motor accordingly. That's it!
Here is the actual Node.js code illustrating this:
I should mention that the Onion Omega2+ has a built-in shell command that allows controlling the GPIO pins. For instance, if we need to set the PIN 1 to OUTPUT mode with a HIGH voltage, we can run the following command: gpioctl dirout-high 1 But I was happy to find an NPM module that abstracts these operations.
Now that our code is working as intended, we are going to expose a simple REST API that we will use to trigger the same commands but this time, over HTTP (this is required for the Google Assistant integration):
We can now easily send a GET request to
http://192.168.80.84:1337/mode/1 and this will set the PIN 1 to HIGH for 800ms then set it back to LOW. The same thing applies to
One last thing to do is schedule this script to run automatically during the boot sequence. We do this by editing the
/etc/rc.local script file (as described in the documentation):
node /root/desk-controller-assistant-server.js & exit 0
Please note that this command runs continuously since we are executing a node server. In order for the boot sequence to finish successfully (and reaches the exit 0), we need to fork the process by adding an ampersand (&) to the end of the command.
Before moving to the next part, I highly recommend you allocate a static IP address to your device, this will help with the Google Assistant integration!
Here is the final hardware setup:
Make sure your Arduino or Onion Omega2+ is accessible from the Internet. For the purpose of this proof of concept, I simply used ngrok which allows me to expose a Webhook - from a local device in my local network, to the outside world - It's really the perfect tool for this experiment!
One caveat though: The Onion Omega2+ I used has only a 32MB of internal storage so I was unable to install ngrok on the device itself, instead, I installed ngrok on my Pixelbook.
The Omega2+ has an SD card slot to extend the internal storage, I just didn't want to bother with that!
Using ngrok, I opened up an HTTP tunnel on
http://220.127.116.11:1337 (the IP of the Omega2+):
ngrok http -subdomain=wassimchegham 192.168.86.84:1337
Once ngrok is up and running, I finally got a public URL:
And now I can send HTTP requests to my device to set the desired mode:
⚠️ WARNING: ngrok will expose your local machine to the Internet. Make sure your turn it off if you are not using it.
This is where the infamous IFTTT comes in place. Here is the recipe I used:
I highly recommend reading the next part about the recommended way to integration IoT device with the Google Assistant.
If you are building a serious device integration with the Google Assistant, you will have to build a Smart Home Action:
Smart Home Actions rely on Home Graph, a database that stores and provides contextual data about the home and its devices. For example, Home Graph can store the concept of a living room that contains multiple types of devices (a light, television, and speaker) from different manufacturers. This information is passed to the Google Assistant in order to execute user requests based on the appropriate context. Read more.
Creating a Smart Home Action requires you to implement the following command:
- action.devices.SYNC: Requests the list of devices that the user has connected and are available for use.
- action.devices.QUERY: Queries for the current states of devices.
- action.devices.EXECUTE: Requests a command to execute on smart home devices. The new state should be provided in response if available. One EXECUTE intent can target multiple devices, with multiple commands.
- action.devices.DISCONNECT: Informs your app when a user has unlinked the app account from the Google Assistant. After receiving a DISCONNECT intent, you should not report state for this user's devices.
I am not going to explain all the implementation details of each method. But, I should mention though that most of the code was adapted from the Smart Washer codelab. So I highly recommend you make sure to follow that codelab for more details on how to deploy and sync your smart device with your Google Assistant.
Shout out to Nick Felker and his team for putting together this codelab which saved me a lot of time 👏🏼
Even though if every Smart Home Action logic is different, 99% of them will share the same boilerplate code (assuming they use of Node.js and Firebase):
To know more about the role of each method, please head over to the official documentation.
One thing to note about the Device Traits API: When registering a new device (ie. a Smart home Action) with the Google Assistant (ie. the Home Graph), your device must be one of the supported devices (see full list) and must have one of the supported traits (see full list). In addition, every trait attribute such as Modes attributes (see full list) and Toggles attributes (see full list) must also be one of the supported ones. So, obviously, a standing desk does not fit in any of those categories. I had then to make the Home Graph thinks that the standing desk is a… Switch (type:
action.devices.types.SWITCH) with an ON/OFF feature (trait:
actions.devices.traits.OnOff). This means that my desk can have only two states:
- Turned On: will raise the desk (Mode 3).
- Turned Off: will lower the desk (Mode 1).
Well! That should be fine for this proof of concept. But if you are building your own device integration, you have plenty of other Traits that you can combine together that would perfectly match your device functionalities.
When it comes to our specific code implementation, here is the complete implementation:
Here is the full project source if you'd like to improve the setup
Basically, what the code does is: when we send commands to our Smart Home Action, we store each state in the Firebase Realtime database. Then, for every state change, we simply send the HTTP request to our local device through ngrok. Here is the high-level - self-explanatory - picture of the architecture:
For a more detailed explanation about how the Smart Home API works, please refer to the official documentation.
Now, let's connect and use our [test] Standing Desk device (ie. the Switch) to our Google Assistant:
Remember to deploy a test version of your Smart Home Action from the Actions Console, in order to find in under the available devices list.
For a more natural interaction with the actions, I created two routines that would allow me to raise and lower my desk - instead of turning on and off my desk:
That's it! I hope you enjoyed reading this post as much as I enjoyed writing it and building this fun project.
Until next time, take care ❤️
Follow me @manekinekko for more fun projects.