Tasmota on ESP8266 can speak

I’m a huge fan of synthetic voices. I love devices around me chattering away letting me know things so I don’t have to look at another screen and interpret what’s being displayed.

But the thing is, these voices don’t have to be great. In fact I prefer if they’re a little clunky and jagged as they realise the dream of the future I had as a kid growing up in the 80s. I thought that when the year 2000 rolled around, we’d have talking robots wandering our houses, our kitchen appliances would announce when they’re being turned on and off, and our houses would announce “night mode” as dusk rolled around. Unfortunately the world has turned out to be far more conservative in weirdness than I had hoped, so I realised I had to make this happen for myself.

I already have home automation happening in my home, built with Home Assistant at its core, and with a focus on locally-processed everything rather than relying on cloud based services like Google and Amazon offer. This has allowed me the freedom to get as weird as I want, and to make the look and feel exactly what I want.

Part of this process has been to reflash every smart bulb and smart switch I use with the amazing open source Tasmota. It allows for truly locally processed and linked devices, that don’t need an external service, just your local Home Assistant controller. In my case I have Home Assistant running on a tiny Raspberry Pi 4 upstairs on the wall.

Tasmota is breathtaking in complexity and ability. It can adapt to almost every smart device and is constantly being expanded, and yet still fits on the super tiny and super cheap ESP8266 and ESP32 chips that are found in almost every smart iot device on the market (and of course you can buy them standalone for your own builds).

Recently I was forced to compile Tasmota from sources to enable some built in functions that aren’t enabled in the default binary builds (for a kitchen control interface requiring a multiplexor chip). While I was doing this I stumbled across some very promising libraries that were in the source code for audio and “SAM” text to speech. My heart skipped a beat.

For those not in the know, “SAM” (or Software Automated Mouth) was a program for the ancient Commodore 64 computer, that allowed for some of the earliest domestic speech synthesis. It’s very recognisable as it was in so many things from movies, to music, to TV, as well as being every 80s kid’s dream. A computer that can talk!

Turns out, this software was ported to a C library by Sebastian Macke and put up on GitHub some time ago, and then adapted to run on microcontrollers by Earle F. Philhower, III. (especially the ESP8266). This meant you could already make this happen if you wrote your own code from scratch and use the library on ESP8266, but somewhere along the way it was added to Tasmota. I couldn’t find documentation for it, but there it was, hiding away, along with commands to make your Tasmota speak.

I quickly realised, though, that in order to perform this trick, I’d need to also buy an I2S IC/board and amplifier as the audio output library relied on I2S which is a simple audio interfacing specification. Being that I wanted to use this voice inside my doorbell button, I didn’t want to spend the money, or make the doorbell button that large to fit all of this.

That’s where I did some digging and found that the ESP8266audio library had a mode where it could roughly bit-bang audio out of the RX pin of the ESP board. From this output, you could make a very simple amplifier with 2 basic transistors to drive a speaker at an audible volume.

Unfortunately, Tasmota source code didn’t have this ability yet, so I set about forking the source code, modifying it, and merging it back (pull request) to Tasmota’s team to add this ability.

The nimble team have already merged this into the Tasmota Development branch so it’s ready to use, but you’ll need to compile it yourself. I won’t go into setting up an IDE for Tasmota compilation from source as that’s been covered quite well by other people including in the readme for Tasmota itself (I recommend the Atom + PlatformIO method):

https://github.com/arendst/Tasmota

Make sure you clone the Development branch (as at 10th Feb 2021) – it’ll move into the main releases at some point.

In order to enable audio output for Tasmota without I2S hardare, you’ll need to add to your “tasmota/user_config_override.h” file the following:

#ifndef USE_I2S_AUDIO
#define USE_I2S_AUDIO
#endif

#ifdef USE_I2S_EXTERNAL_DAC
#undef USE_I2S_EXTERNAL_DAC
#endif

#ifndef USE_I2S_NO_DAC
#define USE_I2S_NO_DAC
#endif

This allows you to enable audio, override the default (to use an external I2S DAC board), and enable the use of direct output.

But before we do anything more, we definitely need to hook up at least one transistor to the output from the ESP chip as you definitely cannot drive a speaker directly (it’ll also probably burn out the chip, or the pin on the chip trying to do so). For the following I assume that you’re running your ESP board from 5V to its 5v/USB input so that it regulates its required 3.3v onboard. We’ll use some of this 5V to feed the transistor and in turn the speaker.

You’ll need:
1 x 2N3904 transistor (NPN type, driven by positive voltage, but switching the negative)
1 x 1k resistor
1 x 3w or so speaker (nothing under 4 ohms)

When driving the audio output with this method, it will always come out of the RX pin of the ESP board. So when I say audio output, I mean the RX pin.

  1. Connect the resistor between the RX pin and the base of the transistor (middle leg).
  2. Connect the collector of your transistor (right pin of transistor with flat face side facing you) to the negative side of your speaker
  3. Connect the positive side of your speaker to 5 volts
  4. Connect the emmitter of your transistor (left pin of transistor with flat face side facing you) to the Ground or negative from the 5V supply, or the ground of your ESP board.

This is a very basic single transistor amplifier. This is what’s outlined on the ESP8266audio library page here:

https://github.com/earlephilhower/ESP8266Audio

Yes the output can be a little rough, and yes if you went to use some of the other capabilities like playback of files, playing of web radio stations (Which is actually pretty cool), they would sound pretty rough which a whistle over the top, but the SAM voice sounds perfectly the same as it originally did.

So we’ve uploaded our custom-compiled Tasmota binary to the board, how can we make it speak? Well documentation is thin (I’ll contribute some to the Tasmota project to help out of course), but you only need to issue the following at the console of Tasmota:


I2SSay(text goes here)

If you’ve played with old speech synthesizers before, you’ll know that they don’t always pronounce words correctly, so you’ll need to craft words at times to sound the way they’re supposed to. For example the word “house” can sound a little strange, so I use the word “howse”. Sometimes adding an h after vowels in words can help too. It’s all up to experimentation.

So it can speak when we issue commands at the console of Tasmota now, but that’s not super useful yet. We want automation!

I use Home Assistant, combined with the MQTT integration for my Tasmota linked automation, so it’s quite easy to issue anything that can be done at the console in Tasmota as an MQTT message.

In whatever script or automation you’re building in Home Assistant, all you need to do is add action type “Call Service” with the service being “mqtt.publish”, and the service data as:

payload: (hello I am home assistant. I am pleased to meet you!)
topic: cmnd/speakboy/I2SSay

You’ll see in the topic above that my Tasmota device has “device name” in config -> other config set to “speakboy”. The payload is simply what you want to say, surrounded by brackets. You can of course put substitution into play to drop in current weather conditions, or variables or whatever you want using Home Assistant methods, as long as it comes out as something that SAM can say.

You may find in your case, like mine, that audio output wasn’t high enough in volume for your purposes. I’m using mine as a doorbell announcer (at the button end, to speak to visitors while they wait for me to run down the stairs for the door) so there is road noise to compete with.

The first step is to try the gain control. It is set at 10 by default, but I found a balance between loudness and distortion to be at 20. Simply issue the command in the console in Tasmota:

I2SGain 20

If we also want to improve the speaker, mount it in a hole in a hollow box or cavity, or even a short length of pvc pipe glued to the back. The back pressure will give the speaker more ooph, as well as allowing some more resonance.

If it still isn’t loud enough we can go further with another transistor. It’s quite easy to use a suitable PNP transistor in combination with the already explained NPN transistor to amplify that current even higher for the speaker.

I’m using a BC559 PNP transistor for the purpose. By modifying how we above ran our simple amplifier, we can get more current to the speaker:

  1. Disconnect the speaker, connect the collector on the 2N3904 to 5V
  2. Disconnect the emitter of the 2N3904 from ground and connect it instead to the base of the BC559 (middle pin)
  3. Connect the Collector of the BC559 (left pin when facing the flat front) to ground/negative.
  4. Connect the Emitter of the BC559 (right pin when facing the flat front) to the negative of the speaker.
  5. Connect the positive of the speaker to 5V

You should now be much MUCH louder, but just make sure you’re not overdoing it by feeling the transistors. They shouldn’t be getting hot.

A quick note here: Never connect this to an actual amplifier. It’s switched DC voltage, not variable AC which is what audio is. It’s also WAY too high for line level audio, at around 5 times the gain. Bad things will happen to the amplifier, and if they don’t, it’ll also sound terrible!

So there you have it. Tasmota speaking everywhere all the time! Get in touch if you have problems or comments – always happy to help!

Keeping a 2006 Roomba Discovery running in 2021: adventures in patience

It was 2008, I was very excited and I had just brought home my first commercial domestic robot: the Roomba Discovery 4220.

It was second-hand from an Ebay seller who claimed it just needed a new battery, but after getting one, cleaning it up and having it scuttle around my house and workshop cleaning I tried to put it onto its home-base to charge. This of course didn’t go well, and when I attempted to wake it up the next day, it was dead flat despite charging for over 12 hours.

It was the dreaded burned-out U2/U4 MOSFET transistors that were very underrated for the current and heat they would handle to charge the battery. For some time I charged the battery with an external charger and popped it back in to make him clean, but eventually I had to tackle the problem.

At the time, there wasn’t a huge amount of info about this problem, so the advice from many at the time was to just replace these two tiny transistors with an equivalent match. It was difficult, and I wasn’t super across surface-mount components but I managed to change both out with the same replacement. The advice was to just make sure it never ran completely flat, or pre-charge the battery for a bit before putting it in the robot to charge, and the transistors shouldn’t burn out again.

Of course Roombas would sometimes get stuck somewhere for long periods of time, and it only took a couple of years before it burned them out again with a flat battery after it was wedged under a couch all night.

For the next 5 years I charged the battery externally, and this dance went on until I’d had enough, and put it away until a few months ago, when taking it apart I put the vacuum and side brush plugs the wrong way around on reassembly which burned out their transistors. I’d had enough.

The Onboard Charging Fix:

I was determined this year, and with the advice of those who had solved the problem on the Robot Reviews website forum years earlier, I set about putting huge MOSFET transistors that should never overheat or blow no matter the state of the battery. Instead of using tiny surface-mount components, I sourced much larger TO-220 form factor transistors: FQP27P06 (rated for 60v 27amps, way WAY higher than will ever be experienced by the bot). I found space above the battery compartment where they would fit inside the plastic top shell and set about gluing them first to small pieces of flat aluminium (to act as small heatsinks) and then gluing these two assemblies to the plastic case.

I carefully removed the mainboard, taking photos to ensure I get the leads in the right sockets again after (some have the same sockets, they’ll burn your transistors that drive things like the vacuum and side brush out). I carefully de-soldered U2 and U4 transistors from the board (on opposite sides). I like to snip the legs off them using super sharp tiny side-cutters, and then heat the head of them to remove them, to avoid pulling the pads off the board.

Using appropriate thickness wire I then ran the 3 pads that were connected to each of the legs out to the transistors I had glued in place (making sure to match the specs sheets for gate, drain, and source). I made sure to use enough to move the mainboard around, but not so much that it’s hard to coil it back inside the robot (maybe 5cm or so?).

Without the case on, I plugged the Roomba directly into the power supply and bingo – the transistors got warm and it was charging. Or so I thought, as they quickly cooled back down and I wasn’t so sure. So how can we know what the robot is actually doing?

Learning to speak robot:

Turns out the Roombas all have a great serial interface called SCI that has been around since the first models. It’s pretty well documented, but the most useful thing I’ve found is simply the feedback you can get from it charging with highly detailed info about battery voltage and charge.

But to do so, we need a method of plugging this interface into our computer. Computers use RS232 for their serial (or USB serial) which is -12 to +12 volts. The Roomba however runs at TTL levels with is 0v to 5v signalling. It would probably do damage to simply try and directly plug this in. So we need to make a cable and method to plug in.

First up the cable is a mini-din8 (technically the roomba has mini-din7 but din8 will plug into it, and din7 is hard to find). I found in a box of old cables for Apple macs there was a mini din8 that was for Appletalk between machines in the 80s/90s. I was lucky, but if you can’t find this you can look for these through suppliers, or even whole cables on ebay.

We’ll cut one end off and strip the wires carefully apart, and strip the insulation from their tips. Using a multimeter we need to find the TX, RX and Ground pins. When looking at the male connector on the other end of the cable, turn the connector so that the single notch is upright, and the flat part of the connector is at the top. The pins are numbered starting at the bottom left, and from left to right. So the bottom row is 1,2, next row up is 3,4,5, top row is 6,7,8. Use your multimeter on continuity mode (where it will beep to show connection between the two leads) and carefully pick your way through the pins and match these with the coloured leads coming out of the freshly stripped area. On my lead yellow was TX, red was RX, and blue and purple were ground.

Now we need to interface with the computer. I always have on hand for other projects the handy little FTDI Basic boards from Sparkfun. These are great because you can plug in TTL level devices to interface via USB.

So with my FTDI Basic board at the read I simply soldered little pins onto the ends of the leads we identified earlier, plugged the TX line into the Rx of the FTDI, then the RX into the TX of the FTDI, and the two ground leads linked together into the GND of the FTDI.

You’ll then need to use a serial program to show the output from the robot. I use Ubuntu Linux on my computer so I used GTKTerm with the serial port settings of:
port: /dev/ttyUSB0 (this could change depending on what you have plugged in)
baud rate: 57600
parity: none
bits: 8
stop bits: 1
flow control: none

Bingo – you should now be receiving data from the Roomba when it’s plugged into the charger. It’ll report every 1 second what’s happening. The important piece of info here is the charge rate. It should be something around 1500ma when fast charging, something like 280ma when slowing down, and maybe 100ma when trickle charging. If you see negative numbers, you haven’t fixed your transistor problem properly and the robot is discharging.

A first charge can sometimes be something like 16 hours as it attempts to recondition the battery, but as mine was externally charged, I simply unplugged and ran the roomba for a short bit before plugging in again to snap it out of this mode and charge fast. I wouldn’t recommend leaving the roomba alone to charge overnight until you’re sure it’s safe and happy as you could cause a fire if something is wrong and the battery overcharges.

It should just charge on the home base right?

So I excitedly unplugged the charger from the Roomba, and plugged in the home base, then put the Roomba back on the home base, and….. not much. Exercising patience…

So what was up with the home base? Plugging in my serial interface from above showed me that when on the home base the charge rate was in negative numbers. It was actually discharging while on it.

It turns out the home base also has a switching MOSFET transistor inside that turns on the power to the pads only when the Roomba has made contact – it was the exact same type that had failed in the bot, so I replaced it in the same way also, squeezing the bigger transistor to the bottom of the case with glue. This time, the home base worked (be careful on assembly and disassembly, there are screws in 8 places underneath pads and foam).

So it can charge now for the first time since 2010 or so.

But the vacuum fan and side brush is running all the time?

From previous adventures in assembly and disassembly, I’d mixed some of the connectors up and burned some regular bipolar junction transistors (BJT) out, meaning they were shorted (switched on) all the time. Obviously not ideal.

I had to locate the transistors in question: Q35, Q36, Q17

Then replace them with something similar: BC337

BUT: make sure you follow the data sheets, the legs of the new transistors were reversed to the original (originally SS8050), so these needed to be flipped.

Soldered together, reassembled, with the dance complete, finally the vacuum and side brush have stopped, and they start when you start the robot up. Excellent.

Put it back together: pull it back apart

I assembled the casing following all of this testing, and…. the vacuum motor won’t run. Why? A multimeter doesn’t show any power coming from the prongs on the side.

Let’s take it apart again.

On closer inspection we find that all of this plugging and unplugging has made the poor little connectors quite loose, and tracing back the vacuum lead to the mainboard shows that when in test mode for the vacuum (that’s a whole other story to get there) wiggling the lead starts the vacuum.

This is the same kind of connector you’ll find on all of these kinds of things. I’ve found them in my other Neato XV-21 robots, when their wheels start to misbehave and drive erratically.

It’s a simple fix. There isn’t anything as drastic as corrosion, it’s simply the internal prongs in the connector have bent apart and no longer squeeze the pins when plugged in.

All we need to do here is gently use a sewing pin to lift the super micr0-tiny plastic tab on the side of the connector for each pin, and gently slide the pin out of the connector by pulling the cable slowly. DO THIS ONE AT A TIME SO YOU DON’T MIX THEM UP. Even take photos so you don’t accidentally reverse the polarity. This takes some practice and skill so take your time. When you have the connector out, use tiny pliers to squeeze the tiny prongs back together, but don’t be rough. The last thing you want to do is try and make a new connector.

Slide them back together and plug it back in. You should notice straight away that it’s now quite tight.

Test the other connectors to feel if they feel tight. If they feel loose it’s better to do this maintenance now than later.

So, does it work now?

Yes. Yes it does. OH MY GOD IT WORKS. And it works very well.

Of course normal maintenance now applies, and for this model that’s the usual Roomba brush deck clearing and cleaning, wheel and cliff sensor blowing out with air, and troubleshooting when you see odd things happen (like circle dances etc). They can be a little rougher than newer models, and sometimes docking with the home base can take a couple of goes, but they still clean very well and do it reliably.

The biggest thing though with this model of Roomba is that the front wheel is a non-swivel castor, which can be be rough for the little wheel, so I thoroughly recommend cleaning the wheel, making sure it spins easily, and tightly pulling electrical tape around it. Winding it around a few times means that it has a protective layer that you can replace time to time so that it doesn’t grind off. If you’re cleaning concrete like mine does my workshop, definitely paint it with glossy concrete paint because otherwise you’re going to just sand that wheel off.

Let’s keep these things working for as long as we can. It’s something that can reduce so much waste, but also can be another cleaning buddy to keep your lungs healthy indoors. If you don’t want yours, don’t throw it away, offer it for very cheap in online trading websites, or give it to someone who will put it to use again.