#!/usr/bin/python
from sense_hat import SenseHat
from datetime import datetime
import math,os,random,subprocess,time

#
# Background:
#
# I have seen various electronic devices on paranormal TV shows (most popular
# being the Ovilus by Digital Dousing) that claim to convert "ghost energy" into
# actual English words.  From the digital dousing website, they claim that their
# ghost box simply "converts environmental readings into words."
#  - https://www.digitaldowsing.com/shop/ovilus-v/
# The algorithm for how this is done is not open source and could be as simple
# as this example, which uses a raspberry pi and sensehat to pick up
# "environmental readings" and using changes in that data to seed a random
# number generator later randomly choosing a word from the provided dictionary.

# To get this to work, you will need:
# * A raspberry pi
# * A sensehat
# * The python sensehat library installed
#     sudo apt-get install sense-hat
# * A word list - set the "filename" variable (Results vary depending on the
#                                              quality of your word list)
# * (optional) The espeak library installed (if you wish to HEAR the ghosts)
#                sudo apt-get install espeak
#              Comment out the calls to espeak() if you don't need this.

# You can have this program start up when the raspberry pi is powered by adding
# something akin to the following into /etc/rc.local before the exit 0
# 
# # Ghost Box
# cd /home/pi
# su pi -c "python ghostBox.py" &

# This code is very sensitive to physical movement, it works best when set in a
# stationary position and read (or listened to) from about 3 or so meters away.


# VARIABLES

sense = SenseHat()
sense.low_light = True # It's more fun at night :-)
red = (255, 0, 0)
white = (255, 255, 255)
black = (0, 0, 0)

# Put the location of your word list file here.
# The one that Digital Dousing uses is available on their website.
# https://www.digitaldowsing.com/product-guides/ovilus-v/word-list/
filename="/home/pi/Documents/ovilus.txt"

# The minimum percent change required to trigger a word.
#  A higher number will cause the ghost box to produce fewer words.
#  A lower number will cause the ghost box to produce more words.
#  2.5 percent seems to be the magic number based on experimentation.
percent_change_min=2.5

# The number of seconds between readings.
#  A higher number will cause the ghost box to produce fewer words.
#  A lower number will cause the ghost box to produce more words.
#  5 seconds feels like a good cadence.
rest_period_between_readings=5


# FUNCTIONS

# Displays the message given, in the color given,
#  right side up based on the detected acceleration of gravity.
def show_right_side_up_message(sense, message, color):
    acc = sense.get_accelerometer_raw()
    x = acc["x"]
    y = acc["y"]
    orientation = 90
    if y > 0.75 :
        orientation = 0
    elif y < -0.75 :
        orientation = 180
    elif x > 0.75 :
        orientation = 270
    elif x < -0.75 :
        orientation = 90
    sense.set_rotation(orientation)
    sense.show_message(message, text_colour=color)

# Get a random line from the file with the name provided
#  and seed the rng with the given seed.
def get_random_line(seed, filename):
    my_file = open(filename,'r')
    line = next(my_file)
    random.seed(seed)
    for num, aline in enumerate(my_file):
        if random.randrange(num + 2): continue
        line = aline
    return line
    
# Get a heuristic aggregating all sensor data allowing general change detection.
def aggregate_sense_data():
    hum = sense.get_humidity()
    temp = sense.get_temperature()
    temp_from_hum = sense.get_temperature_from_humidity()
    temp_from_press = sense.get_temperature_from_pressure()
    press = sense.get_pressure()
    
    o = sense.get_orientation()
    yaw = o["yaw"]
    pitch = o["pitch"]
    roll = o["roll"]

    mag = sense.get_compass_raw()
    mag_x = mag["x"]
    mag_y = mag["y"]
    mag_z = mag["z"]

    acc = sense.get_accelerometer_raw()
    x = acc["x"]
    y = acc["y"]
    z = acc["z"]

    gyro = sense.get_gyroscope_raw()
    gyro_x = gyro["x"]
    gyro_y = gyro["y"]
    gyro_z = gyro["z"]
    
    return hum + temp + temp_from_hum + temp_from_press + press + pitch + yaw \
        + roll + mag_x + mag_y + mag_z + x + y + z + gyro_x + gyro_y + gyro_z

# Speak the words verbally through the headphone jack.
# You must have espeak installed for this to work.
def espeak(message):
   espeak = 'espeak -s100 "%s" 2>>/dev/null' % message
   subprocess.Popen(espeak, shell=True)

# Display a basic animation to get us to look
#  at the box before the message is displayed.
# Keeps you from missing words if audio is disabled.
def alert(sense):
    spiral_sequence = [
        [4, 4],
        [3, 4],
        [3, 3],
        [4, 3],
        [3, 3],
        [5, 3],
        [5, 4],
        [5, 5],
        [4, 5],
        [3, 5],
        [2, 5],
        [2, 4],
        [2, 3],
        [2, 2],
        [3, 2],
        [4, 2],
        [5, 2],
        [6, 2],
        [6, 3],
        [6, 4],
        [6, 5],
        [6, 6],
        [5, 6],
        [4, 6],
        [3, 6],
        [2, 6],
        [1, 6],
        [1, 5],
        [1, 4],
        [1, 3],
        [1, 2],
        [1, 1],
        [2, 1],
        [3, 1],
        [4, 1],
        [5, 1],
        [6, 1],
        [7, 1],
        [7, 2],
        [7, 3],
        [7, 4],
        [7, 5],
        [7, 6],
        [7, 7],
        [6, 7],
        [5, 7],
        [4, 7],
        [3, 7],
        [2, 7],
        [1, 7],
        [0, 7],
        [0, 6],
        [0, 5],
        [0, 4],
        [0, 3],
        [0, 2],
        [0, 1],
        [0, 0],
        [1, 0],
        [2, 0],
        [3, 0],
        [4, 0],
        [5, 0],
        [6, 0],
        [7, 0]
    ]
    # set them all white
    for x, y in spiral_sequence:
       sense.set_pixel(x, y, white)
       time.sleep(0.02)
    # set them all black
    for x, y in spiral_sequence:
       sense.set_pixel(x, y, black)
       time.sleep(0.02)

       
# MAIN LOOP

previous=aggregate_sense_data()
alert(sense)
espeak("Ghost Box Starting up!")
show_right_side_up_message(sense, "Ghost Box!", red)

while True:
    current=aggregate_sense_data()
    change=abs( (current / previous) - 1.0)
    if ( (percent_change_min / 100.0) < change ) :
        ghost_message = get_random_line(change, filename)

        # Logging output
        
        print (ghost_message)
        
        alert(sense)
        espeak(ghost_message)
        show_right_side_up_message(sense, ghost_message, red)
        # it takes time to display the message so no sleep is required
    else:
        time.sleep(rest_period_between_readings)
    previous=current
    