DIY Moai Soap Dispenser šŸ—æ

by Electro Retro in Circuits > Electronics

6301 Views, 52 Favorites, 0 Comments

DIY Moai Soap Dispenser šŸ—æ

Gemini_Generated_Image_fg3acqfg3acqfg3a.png
VID_20260401_030144-ezgif.com-video-to-gif-converter.gif

I’ve been wanting to experiment with Marble PLA for a while now, and I finally got my hands on a spool. While I was hunting for the perfect project to show off that stone-like texture, I stumbled across this awesome Moai model by ET-Huang.

It’s hard to miss how popular these statues have become lately everyone seems to be remixing them into something new(i.e. link, link), and let’s be honest, we all use the Moai emoji šŸ—æ way too much. I figured it was time to put my own spin on the trend!

I want to be clear that this project is all in good fun. I have nothing but respect for the Rapa Nui people and the incredible history of the original Moai statues, This is simply a creative way to bring a bit of that iconic look into a functional home project. Huge thanks to ET-Huang for the original 3D model. With that being said, let’s get into the build!

Supplies

IMG_20260330_012421.jpg
IMG_20260331_151742.jpg
IMG_20260331_183256.jpg
IMG_20260331_204331.jpg
IMG_20260331_204658.jpg
IMG_20260331_231318.jpg
IMG_20260401_010434.jpg
IMG_20260401_014139.jpg
  1. Marble PLA
  2. Black PLA
  3. Foaming pump
  4. 18650 battery
  5. silicone tube 6.5mm OD 3.5mm ID
  6. M2x3mm Threaded inserts - 4
  7. M3x3mm Threaded inserts - 2
  8. M2x5mm screw
  9. M3x10mm screw
  10. 3mm IR LED and PHOTODIODE
  11. JST connector 2.54mm female 2 pin - 2
  12. JST connector 2.54mm male 2 pin - 2
  13. JST connector 2mm male 2 pin - 1
  14. 18650 battery holder
  15. Female header right angle 2.54mm
  16. USB C port
  17. SMD On/Off toggle switch
  18. Attiny402
  19. TP4056
  20. MOT3400 Mosfet
  21. FR107 Diode
  22. Dual color LED
  23. 100uf 16v Tantalum capacitor
  24. 0805 100nf - 2
  25. 0805 10uf - 2
  26. 0805 5.1k - 2
  27. 0805 220R - 2
  28. 0805 47k
  29. 0805 4.7k
  30. 0805 100R
  31. 0805 1K
  32. 0805 1.6K
  33. 0805 10k

Check out BOM here Easyeda link

Remixing the Model

Screenshot 2026-03-31 142236.png
Screenshot 2026-03-31 142303.png

Since I’m building a soap dispenser rather than just a decorative statue, the first step was to remix the original 3D model. I needed to transform it from a solid object into a hollow container capable of holding soap and housing the dispensing hardware.

After downloading the STL files, I imported them into Tinkercad to begin the design work. I chose Tinkercad because it is incredibly beginner friendly and handles basic STL modifications really well.

Base part is for holding liquid, Top part is for pump and electronics, Back cover/Lid, and a Cover for IR LED and IR receiver.

Now, let’s be honest the design process here was a little bit wonky. It involved a lot of "eye-balling" and hoping the everything would actually fit.

You can view and edit model here:- Link

Slicing & Print Settings

Screenshot 2026-03-31 033853.png
Screenshot 2026-03-31 171836.png

Before printing, I prepared all the parts in Cura. Since different parts needed different levels of detail, I used two nozzle sizes.

For the base and top section, I used a 0.6 mm nozzle to speed things up. For smaller parts like the back lid and LED cover, I switched to a 0.4 mm nozzle for better detail.

Here are the main settings I used:

  1. Base & Top: 0.32 mm layer height
  2. Back Lid: 0.25 mm layer height
  3. LED Cover: 0.1 mm layer height
  4. Wall line count: 4

The base printed without any supports. For the top part and back cover, I used minimal supports to save material and reduce cleanup. I also added a brim to the back cover since it prints at a slight angle and needs extra bed adhesion.

Printing

ezgif.com-video-to-gif-converter.gif
IMG_20260330_012443.jpg
IMG_20260330_012558.jpg

There’s nothing quite like the feeling of cracking open a brand new spool of filament. The one I'm using is oreo marble pla from sunlu, I unboxed my fresh pack of Marble PLA, and started printing. Just keep an eye on the first layer and make sure everything sticks well, especially the back cover. Also note that the LED cover is printed using black PLA.

Clean Up the Print

IMG_20260331_103829.jpg
IMG_20260331_104934.jpg
IMG_20260331_103405.jpg
IMG_20260331_171302.jpg
IMG_20260331_175302.jpg

Once the prints were done, the next step was cleaning them up. I removed all the support material and trimmed off any extra bits left from printing.

I also did a bit of light sanding so some parts would fit together properly.

Electronics

Screenshot 2026-04-01 123013.png
Screenshot 2026-04-01 123037.png
Screenshot 2026-04-01 123109.png

The electronics for this project are pretty simple. To detect your hand, I used an IR LED and a photodiode (basically an IR transmitter and receiver).

Here’s how it works: the IR LED emits infrared light, and when your hand comes close, the light reflects back and is picked up by the photodiode. This creates a small electrical signal, which we use to trigger the pump with the help of a microcontroller and a simple motor driver circuit.

I also checked out some teardown videos by bigclivedotcom his videos on soap dispensers and pumps really helped me understand how commercial ones work.

For the microcontroller, I chose the ATtiny402. It’s tiny, cheap, and most importantly, has very low power consumption and sleep current, perfect for a battery-powered project like this.

For power, I went with an 18650 battery along with a TP4056 based charging. The pump motor is controlled using an N-channel MOSFET. Other components include a USB-C port for charging, a bi-color LED for charging status, an on/off switch, and few connectors.

I designed the PCB in EasyEDA. Now, I’ll be the first to admit: it’s not a masterpiece. If you look, you’ll see the USB-C port and the power switch are offset šŸ˜‚. It’s definitely not perfect, but it gets the job done!

I’m providing the link to my EasyEDA project below, so feel free copy it or improve the design.

Easyeda link

Ordering the PCB

Screenshot 2026-04-01 123423.png
IMG_20260331_111335.jpg
IMG_20260331_111355.jpg

With the design finished, it was time to get the boards manufactured. I want to give a big shout out and thank you to NextPCB for supporting this project! Their help made it possible to turn my design into a professional, physical circuit board.

If you want to build this exact version, You can simply download the Gerber file from my easyeda project link and upload it directly to NextPCB (or your preferred PCB manufacturer) to get your own boards made.

Once the boards arrive, we can move on to the most satisfying part: soldering everything together!

Soldering

IMG_20260331_151742.jpg
IMG_20260331_161426.jpg
Sequence01-ezgif.com-speed.gif
IMG_20260331_162907.jpg
VID_20260331_163114-ezgif.com-video-to-gif-converter.gif
Screenshot 2026-04-01 125043.png

I started by soldering the SMD components first, since they’re easier to handle before the larger parts get in the way. After that, I moved on to the through hole components. And finally cleaned flux residue using IPA.

If you’re assembling the board yourself, you can export an interactive BOM from EasyEDA (under the Export section). It makes identifying and placing components much easier.

Wiring the IR LED

IMG_20260331_204331.jpg
IMG_20260331_204658.jpg
IMG_20260331_210929.jpg
IMG_20260401_010127.jpg

Now it’s time to connect the IR components. I soldered wires with a JST connector to both the IR LED (transmitter) and the photodiode (receiver). The JST connector already comes with wires, which makes things easier.

A few things to keep in mind:

  1. The clear LED is the IR transmitter (TX), and the black one is the photodiode (RX).
  2. The longer leg is positive (+). I connected it to the brown wire, and the shorter leg (negative) to the black wire.

After soldering, I added some heat shrink tubing to protect the connections. I also labeled the connectors as TX and RX so there’s no confusion later on.

Programming

IMG_20260331_211522.jpg
Screenshot 2026-04-01 154004.png

The ATtiny402 uses something called UPDI (Unified Program and Debug Interface) for programming. It’s a simple single-wire interface, which makes things pretty convenient.

To program it, you can use an FTDI programmer. The trick here is that the TX and RX lines are tied together, with TX going through a resistor (which is already included on the PCB). So you can just plug in the FTDI module directly without needing any extra wiring tricks.


I’m using the Arduino IDE to program the ATtiny402, so first we need to install megaTinyCore. This is the board support package that allows the ATtiny402 to work with Arduino.

Step 1: Open Arduino IDE → go to File → Preferences

Step 2: In ā€œAdditional Boards Manager URLsā€, paste:

http://drazzy.com/package_drazzy.com_index.json

Step 3: Go to Tools → Board → Boards Manager, search for megaTinyCore, and install it

Step 4: After installation, go to Tools → Board → megaTinyCore → ATtiny402

Then configure the board settings in the Tools menu to match the settings shown in the image I’ve attached.

Next copy my code below and upload it.


FIRMWARE


The firmware needs to be as efficient as possible. If the microcontroller stayed awake 24/7 just waiting for a hand, the battery would be dead in a few days.

To solve this, I used Deep Sleep. Instead of constantly polling the IR sensor, the system spends most of its life asleep, consuming very little power. Every fraction of a second, it wakes up just long enough to check if there’s an object in front of the sensor. If it doesn't see anything, it goes right back to sleep.

If it does detect a hand, it triggers the pump for exactly 1 second just enough for a perfect dollop of soap and then dives back into sleep mode.

Another nice feature of the code is that it works well in different lighting conditions. It does this by subtracting the ambient light from the reflected IR signal, making the detection more reliable even in varying environments.


/*
ATtiny402 — IR + Deep Sleep + Ambient Rejection + Pump Timer
Pump turns off when:
1. Hand removed (checked every 0.5s PIT tick)
2. Max run time reached (PUMP_TICKS Ɨ 0.5s) — whichever comes first
*/
#include <avr/sleep.h>
#include <avr/interrupt.h>

#define PUMP_PIN PIN_PA6
#define IR_ADC_PIN PIN_PA1
#define IR_LED_PIN PIN_PA3

#define DETECT_THRESHOLD 80
#define IR_WARMUP_US 200
#define NUM_SAMPLES 8

// 0.5s per tick
#define PUMP_MAX_TICKS 2 // Max pump run: 2 Ɨ 0.5s = 1 second
#define SLEEP_TICKS 4 // Cooldown after pump: 4 Ɨ 0.5s = 2 seconds

volatile bool pitFired = false;

ISR(RTC_PIT_vect) {
RTC.PITINTFLAGS = RTC_PI_bm;
pitFired = true;
}

void setupPIT() {
RTC.CLKSEL = RTC_CLKSEL_INT1K_gc;
while (RTC.PITSTATUS & RTC_CTRLBUSY_bm);
RTC.PITINTCTRL = RTC_PI_bm;
while (RTC.PITSTATUS & RTC_CTRLBUSY_bm);
RTC.PITCTRLA = RTC_PERIOD_CYC512_gc | RTC_PITEN_bm; // 0.5s per tick
}

void sleepOneTick() {
pitFired = false;
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sei();
sleep_cpu();
sleep_disable();
}

void sleepTicks(uint8_t ticks) {
for (uint8_t i = 0; i < ticks; i++) {
sleepOneTick();
}
}

int readIRDifferential() {
long sumOff = 0, sumOn = 0;
for (int i = 0; i < NUM_SAMPLES; i++) {
digitalWrite(IR_LED_PIN, LOW);
delayMicroseconds(100);
sumOff += analogRead(IR_ADC_PIN);
digitalWrite(IR_LED_PIN, HIGH);
delayMicroseconds(IR_WARMUP_US);
sumOn += analogRead(IR_ADC_PIN);
digitalWrite(IR_LED_PIN, LOW);
delayMicroseconds(100);
}
return (sumOn / NUM_SAMPLES) - (sumOff / NUM_SAMPLES);
}

bool isHandDetected() {
return (readIRDifferential() > DETECT_THRESHOLD);
}

void runPump() {
digitalWrite(PUMP_PIN, HIGH); // Pump ON
for (uint8_t tick = 0; tick < PUMP_MAX_TICKS; tick++) {
sleepOneTick(); // Wait 0.5s
if (!isHandDetected()) { // Check hand every 0.5s
break; // Hand removed
}
}
digitalWrite(PUMP_PIN, LOW); // Pump OFF
}

void setup() {
pinMode(PUMP_PIN, OUTPUT);
pinMode(IR_LED_PIN, OUTPUT);
pinMode(IR_ADC_PIN, INPUT);
digitalWrite(PUMP_PIN, LOW);
digitalWrite(IR_LED_PIN, LOW);
setupPIT();
sei();
}

void loop() {
if (pitFired) {
pitFired = false;
if (isHandDetected()) {
runPump();
sleepTicks(SLEEP_TICKS); // Cooldown before next detection
}
}
sleepOneTick();
}

Looking Back at My Old Project

IMG_20190319_192608.jpg

Before starting the assembly, I want to take a quick look back at one of my old projects.

About 9 years ago, I built my very first automatic soap dispenser. At the time, I thought I had nailed it, But once I actually used it in the washroom, it turned out to be a complete disaster.

It didn’t have any sleep mode, so the battery would die every two days. I spent more time charging it than actually using it. I also used a submersible pump, which kept getting jammed with thick soap. Cleaning it was a messy, sticky nightmare.

The worst part was the sensor. I didn’t know how to handle ambient light back then, so sunlight or even a warm colored light would trigger it randomly. I’d walk into the washroom and find soap all over the place.

At that time, I didn’t have the skills to fix those issues. But I guess every failure teaches you something. Even though this Moai project isn’t perfect, it’s definitely a much better version of that old attempt.

Adding Threaded Inserts

IMG_20260331_231326.jpg
IMG_20260331_231906.jpg
IMG_20260401_005520.jpg

Next, I added the threaded inserts. I used 4Ɨ M2 inserts for the back cover and 2Ɨ M3 inserts for the top part.

In hindsight, I probably should have used shorter inserts something like 3 mm would have been better. I messed up a bit here, and the plastic slightly protruded through the insert. I had to tap it to fix the threads and make everything usable.

Putting Sensor Cover

IMG_20260331_175302.jpg
IMG_20260331_230903.jpg
IMG_20260331_230910.jpg
IMG_20260401_005841.jpg
IMG_20260401_010102.jpg

Remember that small part printed in black PLA? That’s the sensor cover. It has a small divider inside that separates the IR transmitter (TX) and receiver (RX). This helps improve sensing accuracy and prevents false triggers. This little shield ensures the light has to bounce off your hand before it reaches the receiver.

I put a tiny dab of superglue on the edges of the sensor cover to hold the LEDs in place. Just be careful not to get glue on the actual lenses of the LEDs

Next, I mounted the cover onto the main top part. I fed the wires through the hole and then press-fit the cover into the main body.

Inserting the Tubes

IMG_20260401_010434.jpg
IMG_20260401_011007.jpg
IMG_20260401_011032.jpg

For the inlet and outlet, I used silicone tubing with an outer diameter of 6.5 mm and an inner diameter of 3.5 mm.

Installing the Foaming Pump

IMG_20260331_183319.jpg
IMG_20260401_011239.jpg
IMG_20260401_012059.jpg
IMG_20260401_012718.jpg

If I haven't mentioned it yet, I’m actually making a foaming soap dispenser. It does require a specific type of pump to get that foam.

The pump I’m using has three distinct ports.

The Bottom Hole (Inlet): This faces downward and pulls the liquid soap up from the reservoir.

The Top Hole (Outlet): This is where the foam comes out.

The Small Hole (Air Intake): It’s left open to pull in air, which mixes with the liquid soap to create that foam.

I placed the pump inside the main body, then cut the outlet tube to the right length and connected it to the pump. For the inlet tube, I cut it long enough to reach the bottom of the base. I also trimmed the end into a small V-shape this helps prevent the tube from getting blocked when it touches the bottom surface, ensuring a steady flow of soap.

Mounting the PCB

IMG_20260401_013703.jpg
IMG_20260401_014036.jpg

Next, I mounted the PCB onto the back cover using M2 Ɨ 5 mm screws.

The Final Connections

IMG_20260401_014139.jpg
IMG_20260401_014230.jpg
IMG_20260401_014451.jpg

Now, insert the 18650 battery into the holder. Make sure to double check the polarity.

Next, connect all the connectors for the sensors and the pump. For the IR sensors, ensure you plug in the correct ones, This is where the earlier TX/RX labeling really helps avoid confusion.

Final Assembly

IMG_20260401_014704.jpg
IMG_20260401_015051.jpg
IMG_20260401_015425.jpg

Finally, it’s time to put everything together.

Secure the back cover using M3 Ɨ 10 mm screws. Then, attach the base to the top part using the twist-to-lock mechanism.

And just like that our project is complete! šŸŽ‰

Charging

IMG_20260401_020407.jpg

Before powering it on, I charged the battery first.

The indicator LED will show red while charging and turn green when the battery is fully charged.

Fill It With Liquid Soap

IMG_20260401_021201.jpg

Oh, one important thing I almost forgot! You need to use foaming liquid soap for this.

Regular liquid soap is usually too thick and won’t work properly with a foaming pump. If you don’t have foaming soap, you can easily make your own by diluting regular soap with water.

There are plenty of tutorials on YouTube showing how to make DIY foaming soap, so feel free to check those out.


One thing to keep in mind 3D prints aren’t always fully water-tight.

In my case, I tested it with water and didn’t notice any leaks, so it worked fine as-is. But this can vary depending on your print settings and material.

To be on the safer side, it’s a good idea to apply a clear coat or a thin layer of epoxy. This can help seal any tiny gaps and improve durability over time.

One Last Bit

IMG_20260401_025701.jpg

After testing, I noticed a small issue. Sometimes, the foam would build up and stick to the body while dispensing. This happened because the nozzle was a bit too close to the surface, causing the foam to blob up and make a mess.

To fix this, I pulled the outlet tube out slightly and cut the tip at an angle. This helped direct the foam away from the body and made the dispensing much cleaner.

Final Note

VID_20260401_030144-ezgif.com-video-to-gif-converter.gif
VID_20260401_025948-ezgif.com-video-to-gif-converter (1).gif

And that’s it! This project started as a simple idea to try out marble PLA and turned into a fun Moai-shaped foaming soap dispenser.

It’s not perfect, but I learned a lot along the way and that’s what really matters.

If you decide to build one, feel free to tweak the design, improve the PCB, or experiment with different materials. That’s the beauty of DIY projects, There’s always room to make it better.

Hope you enjoyed this build, and maybe even learned something from it. Happy making! šŸ› ļø