Explanation of project
A raspberry pi pico that powers a couple of linear actuators attached to a table hinge using an MQTT server to send and receive messages.
I will go ahead and tell you right now: This is, without a doubt, a totally
unnecessary project.
This project is built using a coffee table that has a hinged that works just
fine when opened manually, as the manufacturer intended. When I wired
everything up, put the final pieces together, and ran it successfully for the
first time to show my girlfriend, the first words out of my mouth upon it
completing the opening and closing movements were: "Pretty pointless, huh?"
That being said, this was fun to build and it does actually make for a cool
party trick when people are over. I used this in conjunction with Home Assistant
running a Mosquitto MQTT server, but you could still make this work if you have
an alternative MQTT set up.
EDIT (Jan 18, 2025): I have added a switch to my setup to ensure that I can still use this table motor when the internet is down, meaning it won't get locked shut. The updated code is located in the project on github. I have opted for a switch but you could use a simple stateless "click" button with a resister, leveraging the code to manipulate state and drive the motor.
Supply list
The list of supplies you will need are: One of these coffee tables with a similar hinge type: Amazon has many of them
Linear actuators: I used the 4 inch version, which I think will fit most of these tables
A couple of diodes: Also available on Amazon
Adafruit DRV8871 DC Motor Driver Breakout Board: Amazon link
Raspberry pi pico: Amazon link
Limit switches: Amazon link You'll also need some small bolts to attach these to the table. I went to my local hardware store for these.
Power supply: I used a 12v 5a supply.
You will also need something to attach the linear actuators to the hinge. This was the hardest part to figure out for me. I ended up getting a few supplies to drill a hole in the arm of the table hinge in order to run a bolt through the hole of the actuator. To reduce the wiggle that you get when you do it this way, I used some plastic spacers. Hopefully this is clearer in the pictures when you see them. There may be better ways to do this, but it was the best idea that I came up with.
Plastic spacers: Amazon link
Countersink bolts (the countersunk nut fit the linear actuator through hole perfectly; also used these to attach the bottom brackets of the actuator to the table): Amazon link
1/4 to 1-3/8 Inches HSS Step Drill Bit for drilling holes in metal, among other materials: Amazon link
Methods of the Madness
Attaching the actuator
I unfortunately did not remember to take many photos during the building process, but the finished product photos will hopefully suffice. The other thing that I wish I had done is to build this at the same time as I was putting the table together. It is not the end of the world if you build the table and then later come back and put the motors on, but it sure would have been easier if I could have done it all at once. The hardest part of this project was figuring out just how to attach the linear actuators. For quite some time, I tested the actuators without actually drilling any holes or attaching the brackets that come with them to any part of the table; I wanted to see if the stroke length would match up well enough to the arms on the hinge to just attach them to one spot and not have to do anything extra. There are little hydraulic pistons that attach to posts on the hinge that I thought I could co-opt, but unfortunately that didn't work.
However, I knew that if I could attach the end of the actuator to the right spot and then have some way of shutting off power to the actuator once the table reached the right position, this was a doable project!
It is a good idea to power the linear actuator all the way out to the end of its length and use that to figure out where to attach the bottom bracket table's bottom floor in the inner compartment, along with where on the hinge arm you need to attach the end of the actuator
In the picture above, the actuator is a little less than fully extended (just enough to give myself a couple of centimeters of extra room, but the distance doesn't matter all that much). At this point, the other thing that you will want to do is to be absolutely sure that once you retract the actuator, the hinge will be fully closed. The bottom bracket (which comes with the actuator usually) was easy to mark the outline of using chalk. You will want to be sure that you don't put it too close to the compartment wall. I also marked off where the top of the actuator should attach via the through hole with a white paint marker on the actual hinge. Another thing I did (because I did all of this after the table was built) was to take the top table part off of the hinges to make things easier to measure and work with. Otherwise you cannot measure where the retracted actuator should be positioned with the hinge all the way closed. I cannot stress enough that you should measure, test, remeasure, and test again as many times as possible until you are absolutely certain you have both the bottom bracket and the top through hole measured to the right spots on the bottom of the table compartment and the hinge arm respectively; it'll make your life way easier if you aren't doing this on the fly later.
Once you figure out where the actuator needs to be positioned, it's time to start drilling the holes. There isn't too much that is special about the bottom bracket. The countersink screws worked pretty well for attaching this as well. For the hinge arm, I took the hole thing off the table and drilled through it, being extra careful to make the hole as centered as possible. I went slow and constantly checked to be sure the hole didn't get too big for the screw. These tables aren't crazy expensive, but I wanted to not have to start over if I ruined it.
Since the end of the linear actuator needs to physically be a little apart from the hinge arm, you will have to get the correctly-sized spacer. I had to cut mine to the right size.
You will want to pick out the right spacer or cut it to size as needed.
After everything is attached, we can move on to getting the limit switches, drivers, power, etc all wired up and the code uploaded to the pico!
Wiring
All in all, this isn't that complicated of a wiring job. You need to connect the correct pins on the pico to the motor drivers, split the power from the power supply and run it to the power inputs of the motor drivers, then run the motor outputs from the drivers to the limit switches and subsequently to the actuators. The diodes should be wired to the correct poles of the limit switches as well (here is another graphic that illustrates this better). For the experts who read this, you will already know this, but for others: I recommend NOT soldering anything until you are sure you have it all in working order. Start with one actuator, wiring everything up (using electrical tape to maintain connections before permanently soldering later) and get it working before moving onto the second actuator. Be sure you can disconnect power to the motor quickly in case the limit switches aren't working properly at first. You can also take the pin out of the bottom brackets that hold the actuator to it, if needed, but be aware that it can be a little tricky to get back on when the actuator is attached to the hinge arm.
The limit switches need to be attached in place so the hinge triggers them when the table is fully open. I took some long skinny screws that I got from the hardware store and a plastic spacer cut to size that would hold the switch far enough awa from the sidewall of the table to engage when the hinge was all the way open. For the size, you will probably have to tailor this to the table that you buy. I bought a size that fit snugly through the limit switch bolt holes (just bring the switch to the store with you) and I bought a series of lengths of them starting from something like 25mm on up to 40mm (I think, it was about 6 months ago) and took used the first one that fit well, which was 35mm I believe.
I used electrical tape to wrap the exposed metal parts of the diode and the poles of the limit switches. As you are wiring / soldering, TEST AND TEST AGAIN to be sure that everything is still working as expected. I left the motor drivers until last so I could use the power outputs to be sure that the limit switches and actuators were working.
Coding
If you haven't already flashed micropython to a raspberry pi pico before, google it. There are plenty of tutorials on that and on getting Thonny running to do some coding. After you have done that grab the code from this repo and upload it via Thonny. You'll need to have an MQTT server and a way of sending messages to the pico, or you can set up Home Assistant to do all of this. I have also included my yaml set up if anyone wants to go that route.
The code is pretty straightforward. You will need to put in your network id and
password and your MQTT server. The quick and dirty explanation of the code is:
After the pico is connected to the MQTT server, it will then be ready to receive
messages on the cmd_topic
which will match to the string of "OPEN"
or
"CLOSED"
. Depending on which one is received, it will change the output pins
which switches the polarity to the motors. The state_topic
sends the updated
state of "OPEN"
or "CLOSED"
to the MQTT server. The server also gets pinged
periodically to maintain the connection, although occasionally (at least for my
set up) I have to unplug and replug the pico back in because the connection gets
dropped.
import time
from machine import Pin
import network
from umqtt.simple import MQTTClient
led = machine.Pin("LED", machine.Pin.OUT)
in1 = machine.Pin(16, machine.Pin.OUT)
in2 = machine.Pin(17, machine.Pin.OUT)
in3 = machine.Pin(14, machine.Pin.OUT)
in4 = machine.Pin(15, machine.Pin.OUT)
# connect to wifi
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect("{wifi_network}", "{wifi_password}")
while not wlan.isconnected():
print("Connecting...")
time.sleep(1)
print("Connected!")
# mqtt vars
mqtt_server = '{mqtt_server}'
mqtt_user = '{mqtt_user}'
mqtt_password = '{mqtt_password}'
client_id = 'table_motor_switch'
sub_topic = 'cmd/table_motor_switch'
pub_topic = 'state/table_motor_switch'
mqtt_status_topic = 'state/table_motor_switch/status'
PING_INTERVAL = 60
mqtt_con_flag = False
pingresp_rcv_flag = True
lock = True
next_ping_time = 0
table_state = ""
# connect to mqtt
mqtt_client = MQTTClient(
client_id=client_id,
server=mqtt_server,
user=mqtt_user,
password=mqtt_password,
keepalive=3600
)
def drive_motor(state):
if state == 'OPEN':
in1.high()
in2.low()
in3.high()
in4.low()
if state == 'CLOSED':
in1.low()
in2.high()
in3.low()
in4.high()
for i in range(6):
led.toggle()
time.sleep(0.1)
def mqtt_subscription_callback(topic, message):
global table_state
print (f'Topic {topic} received message {message}')
msg = message.decode('utf-8')
table_state = msg
if table_state == 'OPEN':
print("opening table")
table_state = msg
drive_motor(table_state)
if table_state == 'CLOSED':
print("closing table")
table_state = msg
drive_motor(table_state)
mqtt_client.publish(pub_topic, table_state, retain=True)
mqtt_client.set_last_will(mqtt_status_topic, "disconnected", retain=True)
mqtt_client.set_callback(mqtt_subscription_callback)
def mqtt_connect():
global next_ping_time
global pingresp_rcv_flag
global mqtt_con_flag
global lock
while not mqtt_con_flag:
print("trying to connect to mqtt server.")
led.off()
try:
mqtt_client.connect()
mqtt_client.subscribe(sub_topic)
mqtt_con_flag = True
pingresp_rcv_flag = True
next_ping_time = time.time() + PING_INTERVAL
lock = False
mqtt_client.publish(mqtt_status_topic, "connected", retain=True)
except Exception as e:
print("Error in mqtt connect: [Exception] %s: %s" % (type(e).__name__, e))
time.sleep(0.5)
print("Connected and subscribed to mqtt")
led.on()
def ping_reset():
global next_ping_time
next_ping_time = time.time() + PING_INTERVAL
print("Next MQTT ping at", next_ping_time)
def ping():
mqtt_client.ping()
ping_reset()
def check():
global next_ping_time
global mqtt_con_flag
global pingresp_rcv_flag
if (time.time() >= next_ping_time):
ping()
mqtt_client.check_msg()
while True:
mqtt_connect()
try:
check()
except Exception as e:
print("Error: [Exception] %s: %s" % (type(e).__name__, e))
lock = True
mqtt_con_flag = False
raise e
time.sleep(.1)
If you would like to use this in conjunction with Home Assistant like I have, I have provided the yaml to do so below:
mqtt:
switch:
- name: Table motor switch
unique_id: b327b0e5-cc0a-488e-93fc-22ec3831296f
state_topic: 'state/table_motor_switch'
value_template: "{{ value == 'OPEN' }}"
command_topic: 'cmd/table_motor_switch'
payload_on: 'OPEN'
payload_off: 'CLOSED'
state_on: true
state_off: false
retain: true
qos: 1
sensor:
- name: 'Table Motor Status'
state_topic: 'state/table_motor_switch/status'
You can then add the following yaml to your dashboard to add a card:
- type: entities
entities:
- entity: switch.table_motor_switch
- entity: sensor.table_motor_status
Final thoughts
I listed as many things to watch out for as I could so you don't have as much trouble as I did, but I may have missed some things. Feel free to comment with whatever thoughts or questions you may have. I am sure someone somewhere has some ideas on improving it.