Peter Smith
Published © GPL3+

Spooky Messages

Show spooky messages (inspired by the Stranger Things Ouija board) using a pan and tilt servo controlled by a BASIC program.

IntermediateShowcase (no instructions)4 hours3,583
Spooky Messages

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
Texas Instruments CC2540T
×1
adafruit mini pan-tilt kit
×1
meped v1.4
×1

Software apps and online services

best calculator, iot edition

Story

Read more

Code

Scary Message package for Best Calculator BASIC

Markdown
## Scary Alphabet Ouija Board

Controls an Arduino-based pan-and-tilt on which is a TI Bluetooth light. 
The Training program lets you point at an area and give it a name (like "A").  Then save the resulting training file
The Display program will read in a training file and, given a message, point to each spot in turn
The Demo program controls the device manually

### Display

Displays messages using a Pan&Tilt controlled light

```BASIC
REM Display a message using the Pan & Tilt
REM The pan and tilt is controlled by an Arduino running
REM the enhanced Ardudroid program with Servo support.

IMPORT FUNCTIONS FROM "TableFunctions"

CLS GREEN
PRINT "Display messages using the Pan and Tilt"


REM Get the pan and tilt device
list = Bluetooth.DevicesRfcommName("HC-06")
PRINT list.Count
IF (list.Count = 0)
    PAPER RED
    PRINT "ERROR: unable to find HC-06 Ardudroid"
    STOP 
END IF
ptraw = list.Get (1)
PRINT ptraw
pt = ptraw.As ("Ardudroid")
pt.ServoAttach (0, 2)
pt.ServoAttach (1, 3)
CONSOLE pt.Name

list = Bluetooth.DevicesName ("beLight*")
lightraw = list.Get(1)
light = lightraw.As ("beLight")
light.SetColor (255, 0, 0, 0)

PRINT "READY!"

tbl = ReadTable()
10 REM Loop Top
message = INPUT DEFAULT "Hello" PROMPT "Enter a message"
message  = String.ToUpper (message)
FOR i  = 1 TO LEN(message)
    ch = MID(message, i, 1)
    pan = GetPan (tbl, ch, -1)
    tilt = GetTilt (tbl, ch, 1)
    IF (pan = -1 OR tilt = -1)
        PRINT "No pan or tilt for " + ch
    ELSE
        light.SetColor (0, 0, 30, 30)
        pt.ServoMove (0, pan)
        pt.ServoMove (1, tilt)
        PAUSE 10
        light.SetColor (255, 0, 0, 0)
        PAUSE 50
        PRINT ch, pan, tilt
    END IF
NEXT i
GOTO 10
```

### Pan Demo

Shows how to use the Slider and Button to manually point the pan and tilt device

```BASIC
CLS BLUE
PRINT "Pan and Tilt"
list = Bluetooth.DevicesRfcommName("HC-06")
PRINT list.Count
IF (list.Count = 0)
    PAPER RED
    PRINT "ERROR: unable to find HC-06 Ardudroid"
    STOP 
END IF
ptraw = list.Get (1)
PRINT ptraw
pt = ptraw.As ("Ardudroid")
pt.ServoAttach (0, 2)
pt.ServoAttach (1, 3)
CONSOLE pt.Name

list = Bluetooth.DevicesName ("beLight*")
lightraw = list.Get(1)
light = lightraw.As ("beLight")

W=400
g = Screen.Graphics (250, 50, 200, W)
g.Background = YELLOW

pan = g.Slider(0, 50, W, 110, "Pan", "pan")
pan.Min = 0
pan.Max = 180
tilt = g.Slider(0, 120, W, 180, "Tilt",  "tilt")
tilt.Min = 0
tilt.Max = 90

BW = 80
stopb = g.Button (0, 0, 70, 30, "STOP", "button")
redb = g.Button (1*BW, 20, 2*BW-5, 60, "RED", "red")
greenb = g.Button (2*BW, 20, 3*BW-5, 60, "GREEN", "green")
blueb = g.Button (3*BW, 20, 4*BW-5, 60, "BLUE", "blue")
offb = g.Button (4*BW, 20, 5*BW-5, 60, "OFF", "off")

FOREVER

FUNCTION button(b)
    FOREVER STOP
END

FUNCTION red(b)
    GLOBAL light
    light.SetColor (255, 0, 0, 0)
END

FUNCTION green(b)
    GLOBAL light
    light.SetColor (0, 255, 0, 0)
END

FUNCTION blue(b)
    GLOBAL light
    light.SetColor (0, 0, 255, 0)
END

FUNCTION off(b)
    GLOBAL light
    light.SetColor (0, 0, 255, 0)
END

FUNCTION pan(s, value)
    Screen.ClearLine (2)
    PRINT "Pan", value
    GLOBAL pt
    pt.ServoMove (0, value)
END
FUNCTION tilt(s, value)
    Screen.ClearLine (2)
    PRINT "Tilt", value
    GLOBAL pt
    pt.ServoMove (1, value)
END
```

### TableFunctions

A set of table functions to create, save, restore and get data from tables.
ReadTable () and SaveTable (tbl)

SetData (tbl, name, pan, tilt)  GetPan (tbl, name, default)  GetTilt (tbl, name, default)


```BASIC
REM Functions to make, use, save, restore tables
REM MakeEmptyTable
REM ...SetData (tbl, name, pan, tilt)
REM ...GetPan (tbl, name, default)
REM ...GetTilt (tbl, name, default)
REM
REM ReadTable ()
REM SaveTable (tbl)
REM
REM ...FindRow --> Index or -1
REM ...
CLS BLUE
PRINT "Test TableFunctions"
TEST()


FUNCTION MakeEmptyTable ()
    DIM tbl()
    tbl.AddRow ("Name", "Pan", "Tilt")
    RETURN tbl
END

FUNCTION FindRow (tbl, name)
    maxrow = tbl.Count
    retval = -1
    FOR r = 2 TO maxrow
        row = tbl[r]
        rowname = row[1]
        IF (rowname = name) THEN RETURN r
    NEXT r
    RETURN retval
END

FUNCTION GetPan (tbl, name, default)
    r = FindRow (tbl, name)
    retval = default
    IF (r <> -1)
        row = tbl[r]
        retval = row[2]
    END IF
    RETURN  retval
END

FUNCTION GetTilt (tbl, name, default)
    r = FindRow (tbl, name)
    IF (r = -1)
        RETURN default
    ELSE
        row = tbl[r]
        RETURN row[3]
    END IF
RETURN


FUNCTION SetData (tbl, name, pan, tilt)
    r = FindRow (tbl, name)
    IF (r = -1)
        tbl.AddRow (name, pan, tilt)
    ELSE
        row = tbl[r]
        row[2] = pan
        row[3] = tilt
    END IF
RETURN

FUNCTION ReadTable ()
    str = String.Escape ("csv", tbl)
    file = File.ReadPicker(.csv)
    IF (file.IsError)
        REM file will have a error message
        PRINT "Cannot open filee", file
        RETURN
    END IF
    str = file.ReadAll()
    tbl = String.Parse ("csv", str)
RETURN tbl

FUNCTION SaveTable (tbl)
    str = String.Escape ("csv", tbl)
    file = File.WritePicker("CSV file", .csv, "test.csv")
    IF (file.IsError)
        REM file will have a error message
        PRINT "Cannot open filee", file
        RETURN
    END IF
    file.WriteText (str)
RETURN


FUNCTION Test_Add_Row_Twice()
    nerror = 0
    tbl = MakeEmptyTable()

    pan = GetPan (tbl, "A", -1)
    nerror = nerror + ASSERT(pan, -1, "no rows result is default")

    SetData (tbl, "A", 10, 200)
    pan = GetPan (tbl, "A", -1)
    nerror = nerror + ASSERT(pan, 10, "first row pan is 10")

    SetData (tbl, "A", 11, 201)
    pan = GetPan (tbl, "A", -1)
    nerror = nerror + ASSERT(pan, 11, "pan should be 11")

    SetData (tbl, "B", 20, 120)
    SetData (tbl, "C", 30, 130)
    SaveTable (tbl)
    tbl2 = ReadTable ()
    pan = GetPan (tbl2, "B", -1)
    nerror = nerror + ASSERT(pan, 20, "tbl2 pan B")
    pan = GetPan (tbl2, "C", -1)
    nerror = nerror + ASSERT(pan, 30, "tbl2 pan C")

    RETURN nerror
END

FUNCTION TEST()
    nerror = 0
    nerror = nerror + Test_Add_Row_Twice()
    RETURN nerror
END

FUNCTION ASSERT (actual, expected, str)
    IF (actual = expected) 
        CONSOLE "OK: " + str
        RETURN 0
    END IF
    CONSOLE "ERROR: "+str
    CONSOLE "Variable is " + actual + " but should be " + expected
    RETURN 1
END
```

### Training

Trains the Pan and Tilt device, giving names (like "A") to different positions

```BASIC
REM Train the Pan & Tilt
REM The pan and tilt is controlled by an Arduino running
REM the enhanced Ardudroid program with Servo support.

IMPORT FUNCTIONS FROM "TableFunctions"

CLS GREEN
PRINT "Train the Pan and Tilt"
tbl = MakeEmptyTable()
Pan= 0
Tilt = 0

H = 300
W = 400
g = Screen.Graphics(50, 50, 300, 400)
g.Background = YELLOW
pan = g.Slider (0, 220, W, 280, "pan", "OnPan")
pan.Max  = 180
tilt = g.Slider(0, 150, W, 210, "tilt", "OnTilt")
tilt.Max = 90
readb = g.Button(0, 0, 70, 40, "Read", "OnRead")
trainb = g.Button(80, 0, 150, 40, "Train", "OnTrain")
saveb = g.Button(160, 0, 230, 40, "Save", "OnSave")
debugb = g.Button(240, 0, 310, 40, "debug", "OnDebug")


REM Get the pan and tilt device
list = Bluetooth.DevicesRfcommName("HC-06")
PRINT list.Count
IF (list.Count = 0)
    PAPER RED
    PRINT "ERROR: unable to find HC-06 Ardudroid"
    STOP 
END IF
ptraw = list.Get (1)
PRINT ptraw
pt = ptraw.As ("Ardudroid")
pt.ServoAttach (0, 2)
pt.ServoAttach (1, 3)
CONSOLE pt.Name

list = Bluetooth.DevicesName ("beLight*")
lightraw = list.Get(1)
light = lightraw.As ("beLight")
light.SetColor (255, 0, 0, 0)

PRINT "READY!"

FOREVER

FUNCTION OnRead(b)
    GLOBAL tbl
    tbl = ReadTable()
    CONSOLE "Read table " + tbl
RETURN

FUNCTION OnDebug(b)
    GLOBAL tbl
    CONSOLE "Current table " + tbl
RETURN

FUNCTION OnSave(b)
    GLOBAL tbl
    SaveTable (tbl)
RETURN

FUNCTION OnTrain(b)
    GLOBAL tbl
    GLOBAL Pan
    GLOBAL Tilt
    label = INPUT DEFAULT "A" PROMPT "What letter?"
    SetData (tbl, label, Pan, Tilt)
    CONSOLE "Current table " + tbl
RETURN


FUNCTION OnPan(s, value)
    GLOBAL Pan
    Pan = value
    Screen.ClearLine (2)
    PRINT "Pan", Pan
    GLOBAL pt
    pt.ServoMove (0, Pan)
END

FUNCTION OnTilt(s, value)
    GLOBAL Tilt
    Tilt = value
    Screen.ClearLine (2)
    PRINT "Tilt", Tilt
    GLOBAL pt
    pt.ServoMove (1, Tilt)
END
```

Ardudroid system with support for Servo motors

Arduino
/*
 PROJECT: ArduDroid 
 PROGRAMMER: Hazim Bitar (techbitar at gmail dot com)
 DATE: Oct 31, 2013
 FILE: ardudroid.ino
 LICENSE: Public domain
*/
#include <Servo.h>

#define START_CMD_CHAR '*'
#define END_CMD_CHAR '#'
#define DIV_CMD_CHAR '|'
#define CMD_DIGITALWRITE 10
#define CMD_ANALOGWRITE 11
#define CMD_TEXT 12
#define CMD_READ_ARDUDROID 13
#define MAX_COMMAND 20  // max command number code. used for error checking.
#define MIN_COMMAND 10  // minimum command number code. used for error checking. 
#define IN_STRING_LENGHT 40
#define MAX_ANALOGWRITE 255
#define PIN_HIGH 3
#define PIN_LOW 2

// Additions to support Servo
Servo Servos[10];
#define CMD_SERVO_ATTACH 14
#define CMD_SERVO_WRITE 15

// Additions to support Ultrasonic!
//+++++++++++++++ULTRASONIC VARIABLES++++++++++++++++++++++++++++

#define echoPin A2 // Echo Pin
#define trigPin A3 // Trigger Pin
#define buzzerPin A0 // Pin for the buzzer
int maximumRange = 200; // Maximum range needed
int minimumRange = 0; // Minimum range needed
long readDistance; // the output distance from the sensor
int ultraSensor(int theEchoPin, int theTrigPin);
#define CMD_ULTRA 16

String inText;

void setup() {
  Serial.begin(9600);
  Serial.println("ArduDroid 0.12 Alpha by TechBitar (2013)");
  Serial.flush();
}

void loop()
{
  Serial.flush();
  int ard_command = 0;
  int pin_num = 0;
  int pin_value = 0;

  char get_char = ' ';  //read serial

  // wait for incoming data
  if (Serial.available() < 1) return; // if serial empty, return to loop().

  // parse incoming command start flag 
  get_char = Serial.read();
  if (get_char != START_CMD_CHAR) return; // if no command start flag, return to loop().

  // parse incoming command type
  ard_command = Serial.parseInt(); // read the command
  
  // parse incoming pin# and value  
  pin_num = Serial.parseInt(); // read the pin
  pin_value = Serial.parseInt();  // read the value

  // 1) GET TEXT COMMAND FROM ARDUDROID
  if (ard_command == CMD_TEXT){   
    inText =""; //clears variable for new input   
    while (Serial.available())  {
      char c = Serial.read();  //gets one byte from serial buffer
      delay(5);
      if (c == END_CMD_CHAR) { // if we the complete string has been read
        // add your code here
        break;
      }              
      else {
        if (c !=  DIV_CMD_CHAR) {
          inText += c; 
          delay(5);
        }
      }
    }
  }

  // 2) GET digitalWrite DATA FROM ARDUDROID
  if (ard_command == CMD_DIGITALWRITE){  
    if (pin_value == PIN_LOW) pin_value = LOW;
    else if (pin_value == PIN_HIGH) pin_value = HIGH;
    else return; // error in pin value. return. 
    set_digitalwrite( pin_num,  pin_value);  // Uncomment this function if you wish to use 
    return;  // return from start of loop()
  }

  // 3) GET analogWrite DATA FROM ARDUDROID
  if (ard_command == CMD_ANALOGWRITE) {  
    analogWrite(  pin_num, pin_value ); 
    // add your code here
    return;  // Done. return to loop();
  }

  // 4) SEND DATA TO ARDUDROID
  if (ard_command == CMD_READ_ARDUDROID) { 
    // char send_to_android[] = "Place your text here." ;
    // Serial.println(send_to_android);   // Example: Sending text
    Serial.print(" Analog 0 = "); 
    Serial.println(analogRead(A0));  // Example: Read and send Analog pin value to Arduino
    return;  // Done. return to loop();
  }

  if (ard_command == CMD_SERVO_ATTACH){
    Servos[pin_num].attach(pin_value); // the 'pin' is the servo number
  }

  // 'pin' is the servo number and 'value' is the value.
  // often values are 0..180
  if (ard_command == CMD_SERVO_WRITE) {
    Servos[pin_num].write (pin_value);
  }
  
  if (ard_command == CMD_ULTRA) {
   int singleRead = 0;
   int allReads = 0;
   for (int i = 0; i< 25; i++){
      singleRead = ultraSensor(echoPin, trigPin); //read the distance
      allReads += singleRead;
    }
    
    // final average
    readDistance = allReads/25;
    Serial.print("Distance="); 
    Serial.println(readDistance); 
  }
  
}

// 2a) select the requested pin# for DigitalWrite action
void set_digitalwrite(int pin_num, int pin_value)
{
  switch (pin_num) {
  case 13:
    pinMode(13, OUTPUT);
    digitalWrite(13, pin_value);  
    // add your code here      
    break;
  case 12:
    pinMode(12, OUTPUT);
    digitalWrite(12, pin_value);   
    // add your code here       
    break;
  case 11:
    pinMode(11, OUTPUT);
    digitalWrite(11, pin_value);         
    // add your code here 
    break;
  case 10:
    pinMode(10, OUTPUT);
    digitalWrite(10, pin_value);         
    // add your code here 
    break;
  case 9:
    pinMode(9, OUTPUT);
    digitalWrite(9, pin_value);         
    // add your code here 
    break;
  case 8:
    pinMode(8, OUTPUT);
    digitalWrite(8, pin_value);         
    // add your code here 
    break;
  case 7:
    pinMode(7, OUTPUT);
    digitalWrite(7, pin_value);         
    // add your code here 
    break;
  case 6:
    pinMode(6, OUTPUT);
    digitalWrite(6, pin_value);         
    // add your code here 
    break;
  case 5:
    pinMode(5, OUTPUT);
    digitalWrite(5, pin_value); 
    // add your code here       
    break;
  case 4:
    pinMode(4, OUTPUT);
    digitalWrite(4, pin_value);         
    // add your code here 
    break;
  case 3:
    pinMode(3, OUTPUT);
    digitalWrite(3, pin_value);         
    // add your code here 
    break;
  case 2:
    pinMode(2, OUTPUT);
    digitalWrite(2, pin_value); 
    // add your code here       
    break;      
    // default: 
    // if nothing else matches, do the default
    // default is optional
  } 
}


// From walteros_05_2.ino from their Slant Robot
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

int ultraSensor(int theEchoPin, int theTrigPin){
   //this fucntion caluclates and returns the distance in cm
    
   long duration, distance; // Duration used to calculate distance
   /* The following trigPin/echoPin cycle is used to determine the
   distance of the nearest object by bouncing soundwaves off of it. */ 
   digitalWrite(theTrigPin, LOW); 
   delayMicroseconds(2); 
  
   digitalWrite(theTrigPin, HIGH);
   delayMicroseconds(10); 
   
   digitalWrite(theTrigPin, LOW);
   duration = pulseIn(theEchoPin, HIGH);
   
   //Calculate the distance (in cm) based on the speed of sound.
   distance = duration/58.2;  
   return distance;
  
}

Credits

Peter Smith

Peter Smith

2 projects • 3 followers
C# Programmer for many years, and a lover of all things IOT and Bluetooth.

Comments