Ship in a Bottle

An internet connected, Morse code blinking ship in a bottle

4 minute read

A few years ago my grandpa showed me a book that he had handwritten called How to Bottle It. In the book he describes the process of making a ship in a bottle.

I followed his instructions and added a technological twist of my own by augmenting it with IoT connectivity!

The process

There’s a WiFi connected Particle Photon microcontroller inside the cork, wires buried under the water, a pulsing LED in the lighthouse, and another LED on the boat which blinks Morse code messages sent from a website. All of that carefully pushed through the mouth of a Woodinville Whiskey Co. bottle.

Ship taking shape

The first step, naturally, was to carve the ship.

There’s a gap here in the process because I didn’t have enough hands to take pictures and put the ship in the bottle! The LED on the ship is connected to two wires that travel along the side of the deck. The wires act as hinges for the mast before going under the ship and being buried beneath the water as they travel to the neck. Similarly, the lighthouse’s wires go through the building and travel under the water toward the neck.

Dupont cork connector

Next, I brought all of the wires to the mouth of the bottle and soldered them to a male dupont connector. This made it so the cork plugs into the neck, connecting the microcontroller to the two LEDs.

Message sending client

A website to send messages to the bottle.
A website to send messages to the bottle.

I made a little website that makes an Ajax call to the Particle API.

When you press “SEND” it takes whatever value is in the “Message…” input field and converts it to Morse code by mapping every letter to its proper Morse code equivalent.

"a": ".-", "b": "-...", "c": "-.-.", "d": "-..", "e": ".", "f": "..-.", "g": "--.", "h": "....", etc

I arbitrarily decided to separate letters with a space and to separate words with a slash. This probably isn’t standard but it won’t matter as long as the microcontroller knows how to interpret it! I could have converted it into raw on/off signal:

         1         2         3         4         5         6         7         8
12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
 
M------   O----------   R------   S----   E       C----------   O----------   D------   E

===.===...===.===.===...=.===.=...=.=.=...=.......===.=.===.=...===.===.===...===.=.=...=
   ^               ^    ^       ^             ^
   |              dah  dit      |             |
symbol space                letter space    word space

Morse Code… Code

The message was already converted to Morse code before being sent to the microcontroller so all we need to do is blink the symbols with the correct timing. According to the International Telecommunication Union:

  • A dash is equal to three dots.
  • The space between the signals forming the same letter is equal to one dot.
  • The space between two letters is equal to three dots.
  • The space between two words is equal to seven dots.

I chose 150 milliseconds as my dot unit and just multiplied it out!

Here’s the code flashed to the Particle Photon:

#include <math.h>

// Pin variables
int ship = A4;
int lighthouse = A5;

// Message variables
String message = "... --- ...";
int messLen = message.length();
int messIndex = 0;
char dot = '.';
char dash = '-';
char letter = ' ';
char word = '/';
int unit = 150;

// Fade variables
const int pwmIntervals = 100;
const int lowest = 35;
const int speed = 1;
const float R = (pwmIntervals * log10(2))/(log10(255));
int fadeVal = lowest;

void setup() {
    pinMode(ship, OUTPUT);
    pinMode(lighthouse, OUTPUT);

    Particle.function("submit", submit);
}

// The function called from the cloud to pass a message
int submit(String args) {
    message = args;
    messLen = message.length();
    messageIndex = 0;
}

void loop() {
    // If we've recieved a message start blinking through it
    if(messageIndex < messLen) {
        if(message.charAt(messageIndex) == dot) {
            digitalWrite(ship, HIGH);
            delay(unit);
            digitalWrite(ship, LOW);
        } else if(message.charAt(messageIndex) == dash) {
            digitalWrite(ship, HIGH);
            delay(unit * 3);
            digitalWrite(ship, LOW);
        } else if(message.charAt(messageIndex) == letter) {
            delay(unit);
        } else if(message.charAt(messageIndex) == word) {
            delay(unit * 7);
        }

        messageIndex++;
        
        delay(150); 
    } else {
        if(fadeVal < lowest || fadeVal >= pwmIntervals) {
            speed = -speed;
        }
        fadeVal += speed;
        
        // Set the LED output to the calculated brightness
        float brightness = pow (2, (fadeVal / R)) - 1;
        analogWrite(lighthouse, brightness);
        
        delay(30);
    }
}

Finished

Morse code demo