loading

Psychic Fortune Teller - An automaton that reads the mind of Twitter

Featured
Picture of Psychic Fortune Teller - An automaton that reads the mind of Twitter

Creepy fairground attraction and Twitter App!


The Psychic Hive-Mind Fortune Teller is a fairground arcade-type attraction which can tell your fortune. Even better than that though - it's also a web-connected Twitter application that can harvest tweets and regurgitate them as fortune readings!

The Fortune Teller reads the mind of a twitter discussion. It takes people's thoughts from their tweets, then deconstructs them into base content. It then reconstructs that content with randomised connecting text fragments to create the fortune reading. By doing this, it creates readings which are not just unique, but also context-specific and completely up-to-date as they are generated from current discussion happening live in real time.

As well as speak your fortune and tweet it back to you, it also has a window into its brain, through which you can see the inner thoughts of the twitter discussions it is scanning.

What can you get from this Instructable?

This Instructable shows you
  • Hacking social media data to power a physical device
  • Twitter - using OAUTH connections to access Twitter data
  • Google - using spreadsheets and their feeds as quick-and-dirty data servers
  • Arduino - how to make a computer programme event be triggered by a physical interaction (button push)
  • Box-building - really basic with glued-on paper printouts as typographic quick fix
  • Computer keyboard hacking - remodelling a standard computer keyboard to use as a restricted interface
  • Consumer car video hardware as display - how to connect up a primitive car video screen as a monitor 
  • Processing - a sketch that can connect to Twitter using OAUTH to harvest tweet content
  • Processing - use of text-to-speech library for speaking auto-generated text
  • Processing  - Randomised sentence construction for pseudo artificial intelligence
  • Processing - visualisation of data
  • Processing - using simple remote-access admin settings
  • Hacking old audio kit - hacking apart a simple computer audio amp to power the sound
Parts list:
  • Face mask (optional)  - Clay, silicon mould rubber, latex, roll-on deodorant balls
  • Head and Shoulders - A broken shop dummy
  • Visualiser/interaction screen - Car reversing monitor plus video driver box
  • Box - 6mm exterior plywood, hot glue (or wood glue), wood off cuts, paint, varnish, lettering printed on standard printer paper, bolts, etc.
  • Internet connected computer (small laptop used here, but probably a Raspberry Pi would work?)
  • Active Twitter account
  • Old computer speakers with built in amplifier
  • Processing with various libraries installed
  • Doorbell button push
  • Arduino board
Tools needed:
  • Computer - Wireless-enabled PC (or Mac, whatever you use) with Processing 1.5 environment with various libraries, arduino environment
  • Box - basic stuff - handsaw, jigsaw, glue gun, paintbrushes, spray paint, drill/screwdriver etc.
  • Head - mainly just modelling tools for the face mask in clay, glue gun to stick it on. Fibre tip pens for colour highlights
 
Remove these adsRemove these ads by Signing Up

Step 1: Psychic Hive Mind Fortune Teller in action and summary of what it is doing

Picture of Psychic Hive Mind Fortune Teller in action and summary of what it is doing
The Psychic Hive Mind Fortune Teller is designed to be used at events where people are gathered and discussing online via Twitter. When people interact with the Fortune Teller, it gives them insights into the live Twitter conversations happening around them.

It is really a type of automoton, but one that can be tuned into the specific conversation of the event at which it is present. It just needs to be set up to target the event, then it will serve up real-time reflections automatically, based on the digital conversation happening around it. In effect, it reads the collective mind of the event, hence why it's called the Psychic Hive-Mind Fortune Teller. It can easily be programmed using simple admin settings that determine what it listens to on Twitter. These can be set remotely using a Google spreadsheet as a web database.

Here it is in action at museum-technology conference "MuseumNext", in May 2013, in Amsterdam.

This 47 second clip shows what an event-goer experiences when they come across the Psychic Fortune Teller - generally bafflement!

.

To have their fortune told, a person interacting with it just has to enter their twitter @username and press a physical button. The Fortune Teller's Twitter-app brain then reads tweets harvested in real time and dissects them into their component parts of usernames, hashtags, urls and other general words. It then rebuilds them with randomised sentence construction to generate rather leftfield recombinations. Finally it sends a tweet containing a summary of the fortune reading to the person's twitter account.

The Fortune Teller uses the irrestible appeal of freaky fairground things and plugs in the social web, piggybacking on some of the many data connections that we trace in our digital lives.

In using digital social media, aspects of our communal connections are translated into and back from data. The Fortune Teller uses this to create a new physical re-manifestation of our digital interactions. It ponders what it means to be part of a large-scale collective 'hive' conversation, how digital social media creates random and unexpected insights and connections with people clustered around interest, and reflects on the meaning of sharing ideas on the web.

But mainly it's just a little bit random :)

How it works

The automaton is tuned to an event discussion by being logged into Twitter. It is programmed to harvest tweets by conducting one of three searches. Usually this is a #hashtag search, but it can be set up to do a @username search or just a straight keyword search. It then extracts the basic content in four types
  1. The usernames of people who have sent the tweets being harvested
  2. Hashtags used in the discussion
  3. URLs sent round
  4. Finally a big list of all the words people have used to express themselves. 
It then constructs random fortune readings using these terms, that it speaks to the visitor, and then tweets them a summary.
Here are some other baffled looking people!

Psychic Hive Mind FortunePsychic Hive Mind Fortune























Psychic Hive Mind Fortune Psychic Hive Mind Fortune
 

Step 2: Design - techniques for working up a concept - from vague idea to fully developed creepy thing

Picture of Design - techniques for working up a concept - from vague idea to fully developed creepy thing
Here's how I worked up the design for the physical thing.

I started by pondering what a creepy fairground head that reads tweets, should look like!

There were a few constraints and some wish-list desires. I knew roughly what I was after and here's how I developed the concept to create a real thing that was suitably weird, but also practically could be built. 
  • The main thing with a fairground attraction is that it must be a bit creepy.
  • I knew I wanted it to be recognisable a person at about normal size, but that a slightly freaky or mutated one!
  • The text-to-speech library I was using was a male voice, so it had to be male
  • It had to have a means by which the user could see into its mind, to see the data in its thoughts
  • It needed a data entry method to allow users to type in their twitter @usernames
  • It needed a plinth or some other mount
  • The whole thing needed a fairground vibe, so it needed to have gawdy colours
  • Did I mention it needed to be creepy? 
I essentially needed to get a clear picture of something in my mind that was a slightly creepy head with a big monitor. Once I had decided on that, I just had to play about with possible variations until I had the design worked up nicely.

The technique I use for this sort of design development is to just draw lots of versions of what I am roughly after. This technique is not trying to  produce an exact design of the final object (in this case the head and it's plinth). The idea is just to explore variations until the right one emerges. The process of doing los of drawings quickly is deliberate to avoid getting too attached to any one of them. Each individual drawing is not important, it's the strength of the mental "feel" in the mind that matters. This is established by the combination of all the images.

This method may not suit everyone, but it's worth a try. It certainly works for me. Once the final look is established, I also find to useful to redraw it a few more times. This gives me enough of a feel for how the finished thing, to be able to sculpt and build a variation on it later from my mind.

It's not a build plan or blueprint though. It remains a mental concept. This design technique is to fix something in the brain that just has a general visual shape and an overall emotional feel. Once fixed in the mind, it helpd guide the hands later when sculpting and building it in 3-d.

Below are some of the design concept sketches.  

headHiveMind Fortune Reader sketch

Pencil is good as it is so fast to work in for mood drawings.






 



 



Ink pen is also fast and clean. The one on the left below was a more advanced version of the design. With ink, you can also colour it separately too. The right hand version used Promarker fibre-tipped pens, which are like a colour wash

.
Fortune teller monitor headFortune teller monitor head

 



















 

Non-representational drawings

I also made some non-representational drawings, like this one to help build up how I wanted it to feel, more than how it might look.It's done as a graphic novel page, mainly to put a sense of narrative/time in. As a design for an interactive, I felt it needed some feel for the fact that it is not fixed but does things in time.

While it's a bit rough to say the least, I find it very helpful to instill a sense of the interaction as well as the physical object.

Hive-mind Fortune-reader as a graphic novel


 

Step 3: Build - Making the Fortune Teller's head

Picture of Build - Making the Fortune Teller's head
This is what the head ended up as. I think it achieved the creepy intention quite nicely.

The head for this automaton was built upon a damaged exhibition mannequin from the exhibition Ballgowns at the V&A. The face mask is a latex test-casting from a previous monstrosity (Twitr Janus). It was spliced into pieces and then Frankensteined back to create a new and even creepier version by wrapping round the plain mannequin.

HiveMind Fortune Reader Mannequin

Here's the rather beautifully-made mannequin torso, looking like classical greek marble in the sun on this azure table tennis table.

DSCN4072 DSCN4074























Beautifully made though this mannequin was, only the top part was needed. The head and shoulders needed to be sawn off, to leave a flat base. This is easier said than done. The main thing to get right was the marking. (Check twice, saw once!) The simplest way to get a sense of the line around the body was to use sticky tape, in this case our old friend insulation tape. Sticky, but not so sticky that it strips the paint layer off!

DSCN4081DSCN4078


















DSCN4086
Another handy use for the wonder tape!

Below is the head. The body is GRP (fibreglass), so the sawdust is evil, ground glass lung-killing stuff.


The next thing to do was get some eyeholes. The sockets were to be made from deodorants as per usual.
The eyeballs were put in place to get a rough place to draw the stencils.

HiveMind Fortune Reader MannequinHiveMind Fortune Reader Mannequin















The orange thing is the ball mount from a manly deororant (it suggests so on the label at least)

HiveMind Fortune Reader MannequinHiveMind Fortune Reader Mannequin















So more crude surgery for the mannequin. Drill to the head, followed by jigsaw to cut out the sockets, ouch...

HiveMind Fortune Reader MannequinHiveMind Fortune Reader Mannequin















And don't they look fetching. Vaguely reminds me of the film nine http://www.imdb.com/title/tt0472033/
and possibly even Fantasic Planet
https://www.google.co.uk/imghp?hl=en&tab=wi&authuser=0&q=fantastic_planet

HiveMind Fortune Reader Mannequin

Tremendous


Next, the rather beautiful skull needed a face, which is a cast off moulding from Twitr_janus, but cut into pieces and reapplied to fit the shape of the mannequin. As per usual this was attached with the marvellous hot glue gun. This one has a fantastic slim-bore nozzle and can be used to inject under the skin.

DSCN4120HiveMind Fortune Reader Mannequin
























The rod below is glued to a bolt being fed through a restrictive passage through the throat, where a hand don't fit. Once in place it was fixed with hot glue and the rod removed

HiveMind Fortune Reader Mannequin
HiveMind Fortune Reader Mannequin
























Below the shape of the face is being drawn to act as a guide for fitting the skin face on. The rest of the head was to be left blank. The face is both a real mask and a metaphorical one (if that isn't a tiny tad meta!)
On the right the monitor mount is shown attached to the mounting bolt fitted previously.

HiveMind Fortune Reader Mannequin HiveMind Fortune Reader Mannequin


























The pencil line was then covered with clear gaffer tape (clear so it could still be seen!), then the line traced with a scalpel


HiveMind Fortune Reader Mannequin HiveMind Fortune Reader Mannequin


























The tape inside the line was removed. The tape is there to protect the surrounding skull area's painwork from grubby fingers, chip, scratches and hot glue whilst working on it.

The face mask was glued on from features of the latex face mask, and offcuts streched over gaps and trimmed to fit.


HiveMind Fortune Reader MannequinHiveMind Fortune Reader Mannequin



























And there you have it. The basic head, now with face...

Step 4: Build - Hacking a keyboard to allow restricted data entry

Picture of Build - Hacking a keyboard to allow restricted data entry
The Fortune Teller requires the user to enter their Twitter username. To build in this feature meant it would need a keyboard, but one that didn't allow the user to interrupt its operation, either accidentally or deliberately.  The crude, but rather satisfying solution to doing this was to hack apart the innards of a standard keyboard, then remove all the control-type keys that allow deeper access to the computer (WINDOWS key, CTRL, ALT, etc) , just leaving the letters and numbers for data entry.

This is what the keyboard looked like once modified. It's neat, although I would have preferred it to be a little bit more "fairgroundy". It could quite happily have benefitted from being steam-punked or fairground-punked or something. If time allowed, I might have carved wooden keys or maybe bakelite or brass...
 
Anyway, here's how the keyboard hacking was done.

NB- hats off to Randolfo for this one. I used his great Instructable Hacking a USB Keyboard as the starting point for this modification.

Hacking a keyboard

On the other hand, the good thing about testing these possibilities is that you get to hack through plastic in a generally therapeutic way...

Before and after...
Hacking a keyboardHacking a keyboard














To start with, first unscrew everything, to reveal the innards...
Hacking a keyboard
Hacking a keyboard













The key mechanism is great...
Hacking a keyboard Hacking a keyboard














Hacking off the end of the board eighteenth-century-ship style - with a saw, then planimg it down - so enjoyable!
Hacking a keyboard Hacking a keyboard














This looked pretty neat in the spring...
Hacking a keyboard

But underneath all that, there's a rather sensitive set of plastic circuit matrices. These capture the physical presses of keys and make the corresponding electronic connections
Hacking a keyboard Hacking a keyboard

Sadly, I managed to kill the first board by some rather unsubtle trimming (subtltey is not really my thing), so take two was rather more careful. Trim the plastic and just fold the silicone and printed acetate key-press sheets...
Hacking a keyboard Hacking a keyboard














There are two sheets of contacts, separated by a masking sheet. The top sheet has a matrix of electrodes which can connect to corresponding contacts on the bottom sheet. In the middle, the masking sheet has holes that control which contacts can touch. Before folding the sheets, the contacts for any keys that need to be neutralised (like CTRL or WINDOWS or ALT) need to be prevented from touching, and therefore activating anything undesirable!

What you can't really see here is the sellotape used to do this. This was applied accross the sheets to mask the contacts. This needed some filigree-level scalpel work to cut round the screw fittings...

Hacking a keyboard

This is the orignal gaffer tape attempt which was too crude...
Hacking a keyboard Hacking a keyboard














Eventually though, the second attempt worked and the cut down keyboard finally worked..
Hacking a keyboard Hacking a keyboard















This works, but I still want to individually re-create each key...
Hacking a keyboard

Step 5: Build - Making a fairground-style plinth case from plywood

Picture of Build - Making a fairground-style plinth case from plywood
Once the head was built it needed a display plinth. Here you can see the finished plinth, and below is how it was constructed. This  effectively needed to be a box large enough to house the tech that drives it. Mainly an arduino, amplifier, a video driver box and a mini laptop.

The first thing to do was to check the size of the parts that had to be stood on the box, and/or fitted into it. This meant sizing up the width based on the width of the shoulders of the automaton, and the width of the sawn-off keyboard.

The image below shows a sheet of rather tasty 5.5mm exterior plywood being marked up in rough, simply by placing the parts onto it, positioning by eye and then drawing lines as guides.

The black square was used to keep it accurate and true on the right angles.


Measuring Fortune Teller keyboard mount

From this, a top board was first cut to width and approximate length (below)...

Measuring Fortune Teller keyboard mount Measuring Fortune Teller keyboard mount

The keyboard was positioned to get the placement right...

Measuring Fortune Teller keyboard mount

The top board was then cut into two parts (shown below). This is because the keyboard needed to be presented to users with a forward sloping angle...

Measuring Fortune Teller keyboard mount

The section that would be used to mount the keyboard is shown below with the paper template attached with gaffer tape.

Marking the Fortune Teller keyboard mount

After marking, a jigsaw was used to cut out the keyboard hole, to allow mounting to the underside.

Fortune Teller keyboard mount

Here is the keyboard, sitting fairly snugly in the board...

Fortune Teller keyboard mount

The laptop shown below is a notebook (i.e. the small ones), so you can see the scale more or less...

Fortune Teller keyboard mount

Jumping ahead a bit, in the shot below you can see the other part of the topboard has been fitted to the forward-sloped keyboard mounting board, with two side boards to make the basic box. It is missing the front, back and base boards. The height was governed by the need to have the laptop open inside the box when running the automaton later.

Also shown below, the head is being positioned to mark out some guide holes to locate it into the base box.

Basic unpainted box for Fortune Teller

From these markings, holes in the top of the box were cut as positioning holes for the shoulders of the automaton to sit in.
Fitting automaton head to box

The shoulders being lowered to test the fit (below)...

DSCN4203

And the shoulders sitting in place...

Fitting automaton head to box

Sitting in place the head looked like this...

Head on box

It also needed a loudspeaker. A hole was cut in the center of the vertical front board to fit this. The speaker and its driver amplifier were hacked from a computer audio amplifier. You can see the speaker showing through in the picture below.

The grooves are there to locate the speaker grill. These were cut with hacksaw blade running against a piece of wood to keep them straight along the guide markings.

speaker mount

The shot below shows the speaker from inside the box. The amplifier circuit is in the lower right, mounted onto a wooden housing and secured with screws.

Inside box

Here's the head again, with the speaker showing. (below)

Head on box

Here's a back view showing the laptop open and running inside the box.

Hea on box (rear)

To give it a fairground attraction feel, the box was painted a garish red. The box on the left shows the bare wood with the keyboard and top holes masked off with gaffer tape to stop paint getting in. The right hand shot shows the box after several coats of red paint. This was normal car spray paint. (Acetone based)

Basic unpainted box for Fortune Teller Spray painting box for Fortune Teller

To continue the fairground vibe, the label wording was printed out in large letters (about 90pt I think), cut into strips, then dyed yellow with felt tip pens (Promarkers). Some pseudo-fading was applied with layers of brown and green faint transparent pigment Promarkers. You can't really see this in this shot.

Basic unpainted box for Fortune Teller

The lettering was glued in place with Bostick (clear fumey general purpose adhesive) and was varnished with 3 separate layers of quick-drying gloss varnish to seal it all.

In the shot below you can see the pseudo-aging more clearly. This was intended to mimic varnish yellowed with age.

Painting box

And here (below) is the finished box. The metal foil speaker grid was separately sprayed with yellow paint to match the lettering, then fixed with wire ties holding it in place within the housing grooves that had previously been cut into the box to locate it.

Psychic HIve Mind Fortune Teller

Below is what it looks like from the back with the back board removed. The laptop does fit inside.

Psychic Hive Mind Fortune Teller

Behind the laptop are the arduino board (manages the putton push actions), the audio amplifier and a video signal convertor. This makes the standard VGA monitor output from the laptop work on the TV-type monitor that sits on the automaton's head. The monitor is a closed circuit TV monitor normally used as a  reversing monitor in a car.

Psychic Hive Mind Fortune Teller

Below is a shot of the various things needed to be crammed inside the box. An important thing to note is that the head is not glued onto the base. It can be taken off and is only secured with a bolt. This retaining bolt is visible below on the far left (left of the arduino board). It's a simple threaded bolt with a wooden block threaded onto it, held loosely in place with nuts.

Psychic Hive Mind Fortune Teller

The reason for not gluing the head onto the box is so it can be taken apart for transport. Here it is shown inside a suitcase, just about to be taken to Amsterdam to demonstrate at the MuseumNext conference. The head is inside the box.

Psychic Hive Mind Fortune Teller

and that is it.

Step 6: Techy - The Psychic brain, its Twitter app and its Google spreadsheet admin interface

Picture of Techy - The Psychic brain, its Twitter app and its Google spreadsheet admin interface
The Psychic Fortune Teller works by reconstructing contextual content harvested from a pre-specific discussion in Twitter. It does this more or less in real time. Usually it checks every 10 seconds for any new content, but this can be changed in the admin settings.

What it looks for in Twitter is defined as a set of admin parameters. This is set in its current admin settings, which are loaded remotely (see later for more details). The settings are stored in a Google spreadsheet elsewhere. This schematic shows how it works

Psychic Hive-Mind Fortune Teller flowchart
It accesses the Twitter API to harvest tweets, then processes and sorts the content of the tweets into lists of words, #hashtags, @usernamesand URLs.

Making the connection to Twitter from Processing

Obviously to get tweets, it needs to connect to Twitter, and to do this, it uses a dedicated Twitter account (@rbeetlelabs). This account is used to register the Fortune Teller as an official Twitter app. This allows it to connect to Twitter using open authentication keys and to make the API search queries needed to extract the tweets for processing.

I won't attempt to re-write instructions on how to set up a new Twitter account. This is actually quite easy, but how it's done can change over time. It is simplest to refer to Twitter's developer pages: https://dev.twitter.com/docs

Essentially once you've registered an app with Twitter, you get 4 huge strings which are used as security keys to authorise and API connections from that app. (This is open authentication OAUTH)

oauth

The twitter app is connected to the Psychic brain. This is a Processing sketch that is running constantly.

The processing sketch uses a Java library called twitter4j (Twitter for Java, geddit?).

http://twitter4j.org/

Twitter4j is open source and although a bit fiddly to import, works well.  It handles the connection between the Processing sketch and Twitter.  You need to store the OAUTH keys in your processing sketch, and the Twitter4j library has a number of classes and method that you can call on to make connections. 

The Psychic Fortune Teller uses 2 Twitter4j methods search(query) which makes search calls to the twitter search API and updateStatus(status), which will update the Twitter account by sending a tweet.

Full documentation on using the Twitter4j library is available here:

http://twitter4j.org/en/api-support.html


Google spreadsheet as admin interface

Once the Processing sketch has made the Twitter handshake connection, it makes an API search. What it searches for is controlled by admin settings that are stored in a Google spreadsheet. This allows them to be remotely set over the web.  
googleadmin

The real beauty of Google spreadsheets is that you can make them accessible as data really easily without any API keys or authentication. To do this you just publish them to the web. Obviously this is not something you can do for sensitive data, so you should NOT use the spreadsheet to store your OAUTH twitter keys (that would be well dodgy!)

To publish to the web, use the file menu
googleadmin-publish

In the publish to web dialog box, check the box "Automatically republish when changes are made" option. This just means that when you update settings, they go out onto the web automatically.
googleadmin-publish-settings

This dialog box is where you get the data URL that you can use in Processing.
googleadmin-publish-text

This is how it is used in the Processing code. When you access the URL, an array is created in Processing that is conveniently filled up with the admin settings, so you can use them as you please - sweet!

void loadRemoteAdminSettings () { try { String checkRandomSpeech = adminSettings[8];
adminSettings = loadStrings("https://docs.google.com/spreadsheet/pub?key=0AgTXh43j7oFVdFNOcGtMaXZnS3IwdTJacllUT1hLQUE&output=txt");
if ((checkRandomSpeech.equals(adminSettings[8]))!=true) { tts.speak(adminSettings[8]);
}

This example is used to send random messages to the Psychic Fortune Teller.


For the full Procesing sketch and what each part does see step 7


Step 7: Techy - Brain logic: extracting sense by deconstructing live conversations

Picture of Techy - Brain logic: extracting sense by deconstructing live conversations
Making an automaton speak random slices of people's tweets is one thing, making them approximate to a reasonably grammatic or intelligible phrase is quite another. It struck me the key to this is looking at the extent of structured and classification that may (or not!) exist within tweets.

Although there are not many of them, there are simple grammar rules used on Twitter that are generally used in a consistent way.  There is also some sense of community etiquette that monitors and maintains these standards. The most important are listed below, with some indication of how this meaning might be used

  • @username - any word with an @ in front of it is very specifically a twitter username. E.g. @rosemarybeetle.  This is extremely useful. If a @username is featured, the person is either being discussed or directly alerted/invited to contribute. Separating these out will give a list people involved in the discussion - very handy!
  • #hashtag - this is twitter grammar for a topic of conversation. Once adopted a hashtag is intended to be used for only one specific subject. Therefore hashtags are very useful and if separated will give a list of subjects being discussed or referenced.
    • There is a special case to be handled when extracting hashtags, which is how to handle the main discussion hashtag. This is the glue of a twitter discussion, but will crowd out any other hashtags in terms of frequency of occurrence. However, as this hashtag id usually what is used a search query term, handling it is easy as the query term can be excluded if required.
    • There are some other variations on hashtags. For example some hashtags for a recurring event may have a root and a date modifier - e.g #MW2012 , #MW2013, etc. Hashtags can often be acronyms and there are also ocassionally some acronymous (is that even a word?) hashtags that can lead to confusion. For instance the hastag #rdg has been used for some time by a local newspaper to donate the town of Reading, UK, but the popularity of japanese anime Red Data Girl led to the #rdg hashtag being widely adopted to refer to that.
      http://www.getreading.co.uk/news/s/2132218_red_data_girl_fans_twitter_storm_over_rdg_hashtag
  • URLs - this is the twitter equivalent of a reference. These are usually not the actual url, but a shortened referral url. Again these were considered to be worth separating out as they are resources associated with the discussion and lead to more in depth ideas that are too long for representation on twitter
  • RT or RT:  - (ReTweet) this is the standard etiquette for acknowledging a tweet being sent is not original is someone else's tweet being sent on. It is equivalent to a traditional credit. If followed by a @username, there is an implicaton that this is the person who sent the original tweet, but this is not a strict rule. While RT might be useful, it was decided not to bother to distinguish usernames that might be being credited rather than mentioned or included
  • VIA  - similar to RT, this is usually an acknowledgement that this is a secondary retweet, naming the person who sent it on. It is almost always the case that a following @username is the person who retweeted it initially.
  • "words in quotes" - Quotation marks are used as a shorthand for a direct quotation from another tweet. 

Flowchart

Here's the flowchart from step 6 again. 

Psychic Hive-Mind Fortune Teller flowchart

The Processing sketch, like any other, has a load of initialisation code and a load of functions being declared. It also has the standard looping function draw(). This runs continuously when the sketch is running and executes various code, makes checks and calls functions as required.

The algorithm for the draw() function, simplistically stated, flows something like this.

  1. Check the remote admin settings
  2. Load remote sentence fragments
  3. Update local admin settings with new versions if needed
  4. Harvest tweets according to admin settings
  5. Strip out stop words (like "the", "and", etc.)
  6. Split tweets into component types of words, hashtags, usernames and urls
  7. Update local storage buckets for each type with latest values
  8. Listen for button presses
  9. If button pressed:
    1. create fortune from tweet content randomly combined with sentence fragments according to fortune construction expression
    2. speak tweet using text-to speech
    3. tweet summary to user

Fortune construction

The sentence fragments are intended to be neutral, so they can be used with twitter discussion content from any Twitter search.
These are randomly chosen from four lookup tables. The content of each of these tables is pulled into the Processing sketch from a separate Google spreadsheet.

Below are the contents of these tables showing the current values at time of writing this Instructable.  These are not fixed and can be amended at any time. In fact they can be amended live while the Psychic Fortune Teller is in action. This is because it checks the contents of these tables at the same rate it checks the admin settings and polls Twitter.

Spoken Fortune

Here is the diagram, for each white box here, a corresponding table of actual values are included below...
 
spoken-reading
The function that creates the fortune within the Psychic brain is:
void readFortune (String tweetText)
{
int picW1 = int(random (words.size()));
String fortuneWord1= words.get(picW1);
int picW2 = int(random (words.size()));
String fortuneWord2= words.get(picW2);
int hash = int(random (hashtags.size()));
String fortuneHash= hashtags.get(hash);
int urler = int(random (urls.size()));
String fortuneUrl= urls.get(urler);
int userer = int(random (usernames.size()));
String fortuneUser = usernames.get(userer);
int frag1Int =int (random (fortFrags1.size()));
String fraglet1 = fortFrags1.get(frag1Int);
int frag2Int =int (random (fortFrags2.size()));
String fraglet2 = fortFrags2.get(frag2Int);
int frag3Int =int (random (fortFrags3.size()));
String fraglet3 = fortFrags3.get(frag3Int);
int frag4Int =int (random (fortFrags4.size()));
String fraglet4 = fortFrags4.get(frag4Int);
fortune = "Psychic summary for @"+tfUserCurrent + ". for: #"+queryString+". "+ fortuneWord1+", "+ fortuneWord2+", #"+fortuneHash+ ", @"+fortuneUser+", "+fortuneUrl+". Enjoy/RT";
println ("just before fortune spoken");
fortuneSpoken = "Hello. "+tfUserCurrent+". "+adminSettings[7]+ ". "+fortuneGreeting +". Here. you are. Your Psychic Hive Mind. Fortune. based on reading .the collective mind of. "+queryString+". is. "+fraglet1+". "+ fortuneWord1+". "+ fraglet2+". "+fortuneWord2+". "+fraglet3+". hashtag."+fortuneHash+ ". "+fraglet4+". Twitter user."+fortuneUser+". Thank you. I have tweeted a psychic summary of this reading to your twitter account. Moove along now. " ;
println ("fortuneSpoken= "+fortuneSpoken);
}


The script is looking at the size of the array storing the fragments, then obtaining a random value from the array by requesting the element that is an random integer between 0 and the max number of elements.

Here are examples of the phrases used (current values at time of writing)

Sentence fragment 1 - (INTRO)

The first Google lookup table contains sentence openers to get the fortune started. 
Current values are...

Perhaps you would be advised to try
It could be that
Don't assume always that
Have you pondered
Try something different like
Maybe its
You might like
Grasp the
Believe in a beautiful
How you feel about
Something unexpected like
An unexpected issue like

The intro is followed by...


+ Twitter word +

A random word from the harvested list of Twitter terms currently in use is inserted. This is followed by...


Sentence fragment 2 (CONNECTOR)

This fragment is a short connector. It will connect the first word with a second, also pulled from the harvested list of Twitter terms currently in use. E.g.


back to
also
and
both
but what if
or maybe
not only but
and
always
hardens the
possibly
more clearly
and the
important
goes towards

It is followed by...


Full stop

The second Twitter term ends the first part of the fortune reading, so it gets a full stop. 



Sentence fragment 3 (#HASHTAG)

This fragment invites the user to consider a hasttag that has been tweeted within the current collective discussion.
It is intended to expose the user to one of the subjects under discussion.E.g.

Also think, you might like to seek out
But perhaps a conversation would help with
Some new ideas may come from talking to
You may be surprised to know what you can learn from
And don't rule out sharing ideas with someone like
You may have something in common with
Try finding things in common with
Find out more about the work done by

It is followed by...

Twitter #hashtag

As you might imagine.

Sentence fragment 4

The final sentence fragment introduces a Twitter user. This user is specifically someone who has sent one of the tweets being harvested, NOT someone mentioned in them.

In other words this fragment is being used to deliberately highlight a current contributor to the discussion. E.g.

Also think, you might like to seek out
But perhaps a conversation would help with
Some new ideas may come from talking to
You may be surprised to know what you can learn from
And don't rule out sharing ideas with someone like
You may have something in common with
Try finding things in common with
Find out more about the work done by

And there you have it.

Tweeted fortune summary

The spoken fortune is a central part of the live intreraction. However, given the Fortune Teller is designed to be used in a hectic environment, a second summary is also generated which is intended as a reminder for the user, of the reading. This is tweeted to the user.

This is similar to the above spoken fortune, but is much simpler in construction. Mainly because as it has to be tweeted, it has to be 140 characters or less.

Here is the diagram representing this

tweet-summary
and the code:
fortune = "Psychic summary for @"+tfUserCurrent + ". for: #"+queryString+". "+ fortuneWord1+", "+ fortuneWord2+", #"+fortuneHash+ ", @"+fortuneUser+", "+fortuneUrl+". Enjoy/RT"; println ("just before fortune spoken");

One thing the summary includes that the spoken fortune does not, is a random URL. Once again, this is taken from the live discussion and is specifically a URL that had been shared and therefore a resource presumably of added interest to the discussion.

The reason the spoken fortune does not contain URLs is simply because they are frequently unintelligible when spoken via text-to-speech.



Limitations of auto-generated fortune readings

The method shown above, of creating fortune readings from auto-harvested tweet content, is crude to say the least. Not surprisingly, the random (or semi-random) connection of terms used to create fortunes has some rather unpredictable results. These are quite often a little strange, but ocassionally you get a really quite surprisingly good juxtaposition. A bit like a leftfield haiku :)

Step 8: Techy - Using Arduino to make a physical press of a button send a tweet

Picture of Techy - Using Arduino to make a physical press of a button send a tweet
One thing that I wanted for the Psychic Fortune Teller was that it required a physical interaction. It has a keyboard needed for data entry, so I could have just used the return key on that. However, this felt not quite enough for a fairground/arcade type experience. I wanted the user to have to make a much more obviously deliberate action to get their fortune told.

Clearly this was where a handy Arduino was always going to be the solution!

I fairly quickly knew this would be a button. Eventually I decided on a good old doorbell...
doorbell
The doorbell circuit is extremely simple
circuit

The full Arduino skethc is available on GitHub here:
https://github.com/rosemarybeetle/psychic-fortune-teller

The same Arduino sketch with some annotations is shown below. You can see it's pretty simple. It looks for a button press (as in the circuit above)



/*
Fortune Teller Hive Arduino board code
Needed to control physical interaction...
and relay physical-detection data back to Processing mothership!
*/


The next line is defining a variable (switchPin) to store an interger, that will be used to define which analogue input will be used.
int switchPin = A0; // Analogue in = A0, called switchPin.
The next line defines a variable to show which PIN an LED is attached to
int led = 13; // LED pin...
The next line is the initialisation for the analogue input variable 
int analogValue = 0; // this is used to determine whether to make a call to Twitter (if high)
Initialise timer period...
float timerPeriod=5000; //don't send data more often than this
Initialise a timer variable...
float timerSend=millis();
Initialise a second comparison timer variable
float timerCheck=0;

The setup function...
void setup() {
Make a serial connection at baud rate 114200...
Serial.begin(115200);
Send confirmation message across serial USB port. This is used in testing
Serial.write("Serial connection initiated");

// initialize the digital pin as an output.
pinMode(led, OUTPUT);
}

// the loop routine runs over and over again forever:

The main loop function...
void loop() {
The next line is making the Arduino check for any analogue signal to the analogue pin 0...
analogValue = analogRead(switchPin);
If the doorbell is pressed, then the input to analogue pin 0 will be 5V. This is equivalent to 1024. The IF statement has a threshold of 900 (about 4.5V)
if (analogValue >=900) {
reset first Timer variable (timerSend)
timerSend=millis();
Check if period between now and last reset (timerSend-timerCheck) is greater than default period (timerPeriod)
NOTE initially this will be true. After the first press it will be false until a period has elapsed. This is preventing accidental multiple sends of serial data which might cause the Fortune Teller to try to speak the same thing several times
if ((timerSend-timerCheck)>timerPeriod) {
Send the coded message "fireTweet" to the Processing script over the serial (USB) port
Serial.write("fireTweet");
Once sent reset the check timer variable. This will be usd in the IF check to prevent refiring too early
timerCheck=millis();
analogValue = 0; // reset - this is used to ensure the value is reset after a successful release of the switch
// ADD ANY OTHER TRIGGERS HERE THAT COME FROM THIS INTERACTION
}
}
}

.

Step 9: Techy - A micro car-monitor display for visualising the inner brain

Picture of Techy - A micro car-monitor display for visualising the inner brain
A key feature of the design of the Psychic Fortune Teller was that it should have some means of seeing into its brain. I wanted people using it to be able to see where the content it speaks comes from, by displaying it a live graphic visualisation. This mean it needed a monitor that would display this visualisation.  

I considered several possible sources for a screen. It needed to be small enough to use with a normal sized head, but the display needed to be big enough to actually read.

I initially had wanted to hack the screen from a DVD player, as these are small and have a fairly good screen resolution, needed to for playing DVDs back.  There were some useful examples, such as this one on Instructables from computerwiz_222

http://www.instructables.com/id/Use-your-Portable-DVD-Player-as-a-Monitor/

This was a great Instructable, but required the DVD player to be a model with a video-in connection, which many do not have. I didn't want to pay the full price for a new one to achieve this, so this would have meant getting lucky with a second-hand one.  I couldn't easily find one on e-bay, but in the process discovered an alternative consumer product that was just what I was after as a source of a small display - in-car monitors!

It turns out that cars use a range of monitors mainly within closed circuit TV reversing warning systems which need to be small enough to fit on car dashboards, or for in-car entertainment (i.e. using as as display on the back of seats, to shut your kids up on a journey!). They are an established product with a massive market, so there was a good range of sizes available including 2.5", 3.5", 4.5" and so on. They also crucially are available as a separate part for making up your own reversing camera system and come with a video inpit as standard - perfect!

There were also loads available and they were cheap enough to buy new off ebay. The only compromise in using them is that the display quality is not intended for watcjing DVDs. It is just good enough to see if your about to mash up your car whilst parking.

In the end, I bought a 7" screen, which was slightly larger than I had originally intended, but anything smaller would not have been legible due to the fairly low screen resolution.

One thing I hadn't realised in advance is that because they are not made for connection as a computer screen, they only work with video line in. They almost all come with standard VGA connectors. Check this!
screenleads
However, although these RCA connectors are standard and allow easy connection, the screens are really being connected like a very small television. They don't come with computer drivers.  This means that you can only connect them directly to a computer that has a specific line out. This usually only means computeres designed to allow you to connect them to big consumer TV screens, e.g. for gaming.

Luckily, there is a handy and fairly cheap fix, which is to use a video convertor box. This takes an input from a standard VGA monitor output (this is the standard blue rhomboid connector for connecting projectors etc)  and converts it into the video line signal that the mini monitor needs. Here's what they look like. They are powered by USB

convertor

Anyway, these screens are very handy, dead easy to get hold of and reasonably cheap. I recommend considering them as a cheap and cheery solution. 

Step 10: Techy - Building the visual interface

Picture of Techy - Building the visual interface

Stare deep into the collective brain...

One of the key features I wanted for the Psychic Fortune Reader was that it be possible to see into its mind in real time as it is scanning the collective thoughts of the Twitter conversation. The screen on its head is a window into its brain.

It also needed to have a simple form box area to allow users to interact by entering their Twitter username.

Eyes up visualisation


Visualising the discussion

I wanted the visualisation is to be a randomised animated something, but I wasn't sure what would work.  While wandering about the web trying to get ideas, I came across a rather good visualisation example by Jer Thorp. Respect to him for putting out such a clear example. His visualisation is essentially using Twitter to get a word list, then presenting this by displaying these word at random positions on the screen. Here's his very admirable and handy example.

http://blog.blprnt.com/blog/blprnt/updated-quick-tutorial-processing-twitter

Jer's example has a simple white text on black and works by calculating the screen size, then randomly placing the text within those boundaries. I played with this code. I had not one, but four subsets of data types: words, hashtags, usernames and URLs.

I gave each of these a distinguishing colour and size range. For each type of data, the words are displayed with a distinct colour and the size ranges between a min and max value. This worked quite nicely.

Here is a visualisation of a discussion at MuseumNext in Amsterdam May 2013

vis100

Interaction elements of the interface

The other need was for a user input area.  This was handled by a convenient Processing library called ControlP5, written by Andreas Schlegel, another person deserving of respect for their free sharing of this very handy library.

www.sojamo.de/libraries/controlP5/

The form is extremely simple and is a backing banner band, a text input and a text label...

form



Iterations

To get to the final visual interface design above, it was developed in stages.
The first stage was monochrome, based on Jer Thorp's code. These four screenshots show the rather cumulative white-out effect that this original script produced.  This was good, but not intelligible enough...

monoSG-1 monoSG-2
monoSG-3 monoSG-4
This was developed to the eventual four-colour display, with greater control over variable sizes and a much faster fade to black, which exposes the words much more distinctly and clearly overall.

This example below is a test reading at Museums and the Web 2013. It appears much more clear because it is a screen shot of a reasonably high screen resolution on a desktop PC display. 
 
HiveMind FortuneReader screenshot
 
When scaled down the words were unreadable, so the font size had to be dramatically increased to be legible on the smaller, lower-resolution car monitor.

You can also see that the form design is not yet finished at this stage.

Step 11: Techy - The full Processing code (and GitHub link)

Picture of Techy - The full Processing code (and GitHub link)
By far the most important part of the Psychic Fortune Teller is its psychic brain, written in Processing.

The full code for the psychic brain is on GitHub for free reuse under GNU GPL.  You should refer to this for forking or copying the code as it will get any updates I bother to make.

https://github.com/rosemarybeetle/psychic-fortune-teller

The code below is the full Processing sketch at time of writing, with basic notes that show where some of the features discussed in this Instructable are featured in the code.  It has quite a lot of information in the code comments, so here I am merely signposting the broad sections of code and what they do.

The idea is that if you only want to reuse some features, you can find the code block you need, hack it up as you like and ignore the rest. Note that there are a number of imported libraries, so you need to include these.

Terms of use = use it freely. I hope it helps you build stuff :)


More or less you can do what you like, except try to copyright it or restrict reuse by others of this code, or your new code based on it. 

It is also based in part on other people's efforts before me. Respect to them. If you manage to make any use of it, I'll be pleased and suggest you put yours back out there.

Here are some credits...

Acknowledgments

Twitter4j

Yusuke Yamamoto (and others I believe?)
Twitter4j is a java library that does the connection and transaction handling between Processing and Twitter. This is what enables simple connection to harvest the tweet content, and to send tweets, both from withing the Processign brain. AWESOME.
github.com/yusuke/twitter4j/network

GURU text-to-speech

Nikolaus Gradwohl
The GURU text to speech library for Processing, is a key bit of code. It is what allows the Psychic Fortune Teller to talk to you.
www.local-guru.net/blog/pages/ttslib

ControlP5

Andreas Schlegel
ControlP5 is a Processing library used to handle creation and control of user interfaces in the Processing draw() window.
It's used for the twitter username entry box
www.sojamo.de/libraries/controlP5/

Visualisation

JER THORP
The visualisation of words on screen is based on an example sketch by Jer.
blog.blprnt.com/blog/blprnt/updated-quick-tutorial-processing-twitter 




Full sketch with some interpretation

Version 9. This is the Psychic brain Processing sketch at the time of release of this Instructable.
Any amendments will be on the Git repo
https://github.com/rosemarybeetle/psychic-fortune-teller


credits

// -----------------------
// ----
// PSYCHIC FORTUNE TELLER
// @rosemarybeetle 2013
// http://makingweirdstuff.blogspot.com
// version 9
/* -----------------------
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see .
----------------------
*/

//
// This sketch is the mind control of Psychic Fortune Teller, an automaton that can read the collective mind of twitter
// It has a Processing brain connected to a Twitter app, connecting via OAUTH
// It harvests tweets from predefined searchs
// Deconstructs the weet content into words, hashtags, usernames and urls
// Then uses these to create fortune readings, which it speaks using text-to-speeach
// it also tweets a summary.

//
// RESPECT to...
//
// JER THORP - Visualisation is based on his code example
// see http://blog.blprnt.com/blog/blprnt/updated-quick-tutorial-processing-twitter
// Awesome!
//
// The people behind twitter4j
// see https://github.com/yusuke/twitter4j/network
// using here, version 3.03
// NOTE - you have to have the twitter4j library installed in the libraries folder for this to work!
// You need to register your app to get OAUTH keys for Twitter4j
// You can put them in a separate tab in your sketch
//
// Andreas Schlegel - controlP5 GUI Library
// see http://www.sojamo.de/libraries/controlP5/
// For positioning see (also @@@ Andreas Schlegel @@@) -
// https://code.google.com/p/controlp5/source/browse/trunk/examples/controlP5button/controlP5button.pde?r=6
// ----
// Nikolaus Gradwohl for the GURU text to speech library for Processing
// see http://www.local-guru.net/blog/pages/ttslib
// -----------------------

// -----

initialisation

// >>>>>>
boolean serialCheckInt=true;
boolean grabtweetCheckInt=true;
boolean loadSettingsFirstLoadFlag=true;
boolean loadstopWordsCheckInt=true;
// <<<<<< end load flags

// >>>>> fortune variables initialisations
int tweetTextOutro = int (random(99));
String tweetSendTrigger ="fireTweet";
String fortuneGreeting = "I have stared deep into the hive mind. ";
String fortune = "";
String fortuneSpoken = "";
int widthRandomiser = 120;
// <<<<<<

// >>>>>> gui variables init...
String tfUserCurrent =""; // used to check what is in the username text box
String tfTextCurrent =""; // used to check what is in the free-text text box
int valFocus = 0; // default
color focusBackgroundColor = color (255, 255, 00);
color focusOffBackgroundColor = color (0, 0, 0);
color focusOffColor = focusBackgroundColor ;
color focusColor = focusOffBackgroundColor;
color clPanel = color(70, 130, 180);
// <<<<<<

// >>>>>> ArrayLists to hold all of the words that we get from the imported tweets
ArrayList stopWords = new ArrayList();
ArrayList cleanTweets = new ArrayList();
ArrayList words = new ArrayList();
ArrayList hashtags = new ArrayList();
ArrayList usernames = new ArrayList();
ArrayList urls = new ArrayList();
ArrayList tweetster = new ArrayList();
String uberWords [] = new String[0]; //massive array to build up history of words harvested
String uberHashtags [] = new String[0]; //massive array to build up history of hashtags harvested
String uberUsers [] = new String[0]; //massive array to build up history of users harvested
String uberUrls [] = new String[0]; //massive array to build up history of urls harvested
String queryString = ""; //
String queryType = ""; //
ArrayList fortFrags1 = new ArrayList();
ArrayList fortFrags2 = new ArrayList();
ArrayList fortFrags3 = new ArrayList();
ArrayList fortFrags4 = new ArrayList();

// <<<<<< Variables for admin and tweettexts - e.g Array for containing imported admin settings from Google spreadsheet (init with default settings)
String adminSettings [] = {
"#hivemind", "@rosemarybeetle", "weird", "100", "50000", "h", "500", "Psychic Hive-Mind Fortune Reader", "Greetings Master. I am a-woken"
};

String tweetTextIntro="";
String readingSettingText="";
int panelHeight = 60;
int border = 40;
int boxY = 515;
int boxWidth = 270;
int boxHeight = 40;
int columnPos2_X = 310;

// >>>>>> grabTweets Timer settings >>>>>>>>>>>
float grabTime = millis();
float timeNow = millis();
String stamp = year()+"-"+month()+"-"+day()+"-"+hour()+"-"+minute();// <<<<<<

Set up GUI interface-building library (ControlP5)

// >>>>>> GUI library and settings
import controlP5.*; // import the GUI library
ControlP5 cp5; // creates a controller (I think!)
ControlFont font;
controlP5.Button b;
controlP5.Textfield tf;
controlP5.Textlabel lb;
// <<<<<<<

Set up text-to-speech library (GURU)

// >>>>>>> import GURU text-to-speech library
import guru.ttslib.*; // NB this also needs to be loaded (available from http://www.local-guru.net/projects/lib/ttslib-0.3.zip)
TTS tts; // create an instance called 'tts'

// <<<<<<<

// >>>>>>> import standard processing Serial library
import processing.serial.*;

Serial port; // create an instance called 'port'
// <<<<<<<

// >>>>>> needed to stop Twitter overpolling from within sendTweet
float tweetTimer = 5000; // wait period (in milliseconds) after sending a tweet, before you can send the next one
float timerT=millis(); // temporary timer for sendTweet
float delayCheck; //delayCheck; // THIS IS IMPORTANT. it i what stops overpollin g of the Twitter API
// <<<<<< End of main initialisation


Main SETUP() function...

void setup() {
tts = new TTS(); // create text to speech instance
tts.speak(adminSettings[8]);// preloaded, not web
println (" adminSettings 1 " + adminSettings); // @@ DEBUG STUFF
for (int i = 0 ; i < adminSettings.length; i++) {
println("adminSettings["+i+"]= "+adminSettings[i]); // @@ DEBUG STUFF
}
updateDisplayVariables();
try {
loadRemoteAdminSettings(); // loads Twitter search parameters from remote Google spreadsheet
println ("adminSettings 2 "+adminSettings);
tts.speak("I am connected to the web. Master.Your commands have been loaded into my brain"); // @@ DEBUG STUFF - SPOKEN OUT. ONLY WORKS IF CONNECTION WORKS
}
catch (Exception e) {
tts.speak("I am sorry. I am not able to connect to the web. Your commands have not been loaded into my brain master"); // @@ DEBUG STUFF
}
loadRemoteStopWords();// load list of stop words into an array, loaded from a remote spreadsheet

// >>>>>>> screen size and settings....
size(screen.width-border, screen.height-border);// USE THIS SETTING FOR EXPORTED APPLICATION IN FULLSCREEN (PRESENT) MODE
background(0); // SET BACKGROUND TO BLACK
// <<<<<<<

// >>>>> Make initial serial port connection handshake
println(Serial.list());// // @@ DEBUG STUFF - display communication ports (use this in test for available ports)
try {
port = new Serial(this, Serial.list()[0], 115200); // OPEN PORT TO ARDUINO
}
catch (ArrayIndexOutOfBoundsException ae) {
// if errors
println ("-------------------------");
println ("STOP - No PORT CONNECTION");
println ("Exception = "+ae); // print it
println ("-------------------------");
println ("-------------------------");
}
// <<<<<<<
buildAdminPanel();

smooth();

grabTweets(); // Now call tweeting action functions...

println ("finished grabbing tweets");
println ();
println ();
} // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< end of setup() <<<<<<<<<<<<<<<<<<<<<<<<<<

Main LOOP() function

void draw() {
int panelTop= height-panelHeight;
buttonCheck("HELLO"); // on screen check button every loop

timeNow=millis();

try {
println ();
if ((timeNow-grabTime)>float(adminSettings[4])) {
grabTweets();
}

// >>>>>> Draw a faint black rectangle over what is currently on the stage so it fades over time.
fill(0, 30); // change the latter number to make the fade deeper (from 1 to 20 is good)
rect(0, 0, width, height-panelHeight);
// <<<<<<

// >>>>>>> WORDS
// Draw a word from the list of words that we've built
int i = (int (random (words.size())));
String word = words.get(i);
println ("word = "+word+" #"+i);
// <<<<<<<

// >>>>>>> HASHTAGS
//Draw a hashtag from the list of words that we've built
int j = (int (random (hashtags.size())));
String hashtag = hashtags.get(j);
// <<<<<<<

// >>>>>> USERNAMES
//Draw a username from the list of words that we've built
int k = (int (random (usernames.size())));
String username = usernames.get(k);
// <<<<<<

// >>>>>> URLS
//Draw a url from the list of words that we've built
int l = (int (random (urls.size())));
String url = urls.get(l);
// <<<<<<

//-------------
// >>>>> Put url somewhere random on the stage, with a random size and colour
fill(255, 255, 0, 255);
textSize(random(30, 40));
text(url, random(width)-widthRandomiser, random(panelTop)); //
// <<< SEND URL TO THE SCREEN

// >>> SENDs HASHTAG TO THE SCREEN WITH DIFFERENT SIZE
fill(255, 0, 0, 255);
textSize(random(40, 45));
text("#"+hashtag, random(width)-widthRandomiser, random (panelTop));
// <<< END SEND HASHTAG#

// >>>SEND WORD TO SCREEN ALSO WITH DIFFERENT SETTINGS
textSize(random(45, 60));
fill(255, 255);
text(word, random(width)-widthRandomiser, random (panelTop));
// <<< END SEND WORD

// >>> SEND USERNAME TO SCREEN
fill(0, 255, 22, 255);
textSize(random(35, 45));
text("@"+username, random(width)-widthRandomiser, random (panelTop));
// <<< END SEND USERNAME

// --------------
// following is for text boxes background.
tfUserCurrent=tf.getText() ; //check the text box content every loop
println ("tfUserCurrent= "+tfUserCurrent); // @@ DEBUG STUFF
}
catch (Exception e) {
}
finally
{
println ("inside DRAW()");
}
checkSerial() ; // check serial port every loop
}


Function SENDTWEET() - sends tweets!

// >>>>>>>>>>>>>>>>>>>>>>>> SEND THAT TWEET >>>>>>>>>>>>>>>

void sendTweet (String tweetText) {

if ((tfUserCurrent.equals(""))!=true) { // THE BOX CAN'T BE EMPTY
updateDisplayVariables();
//@@@
timerT=millis(); // reset the timer each time


if (timerT-delayCheck>=tweetTimer)
// this is needed to prevent sending multiple times rapidly to Twitter
// which will be frowned upon!
{
delayCheck=millis(); // RESET A TIMER

println("tweet being sent"); // @@ DEBUG STUFF
println("tfUserCurrent = "+ tfUserCurrent); // @@ DEBUG STUFF
tweetTextIntro = readingSettingText; // INITIALISE THE INTRO TEXT VARIABLE...
readFortune(tweetText);
tts.speak(fortuneSpoken);
println("tweet Send actions complete over"); // @@ DEBUG STUFF
println();

//@@@
ConfigurationBuilder cb2 = new ConfigurationBuilder();
// ------- NB - the variables twitOAuthConsumerKey, are in a seperate tab
cb2.setOAuthConsumerKey(twitOAuthConsumerKey);
cb2.setOAuthConsumerSecret(twitOAuthConsumerSecret);
cb2.setOAuthAccessToken(twitOAuthAccessToken);
cb2.setOAuthAccessTokenSecret(twitOAuthAccessTokenSecret);

Twitter twitter2 = new TwitterFactory(cb2.build()).getInstance();

try {
Status status = twitter2.updateStatus(fortune);
println("Successfully tweeted the message: "+fortune + " to user: [@" + status.getText() + "]."); // @@ DEBUG STUFF
delayCheck=millis();
}
catch(TwitterException e) {
println("Send tweet: " + e + " Status code: " + e.getStatusCode());
} // end try
;
}
}
else {
tts.speak("You have not entered your Twitter user nayme. Sorry. I cannot reed your fortune. without this") ; // THE BOX WAS EMPTY
}
}
// <<<<<<<<<<<<<<<<<<<<<<<<< END SEND TWEETS <<<<<<<<<<<<<<<

Function GRABTWEETS() - this is the main harvesting function

// >>>>>>>>>>>>>>>>>>>>>>>>> GRAB THOSE TWEETS >>>>>>>>>>>>>

void grabTweets() {

color cl3 = color(70, 130, 180);
fill (cl3);
rect(0, (height/2)-120, width, 90);

fill(0, 25, 89, 255);
textSize(70);
text("Reading the collective mind...", (width/8)-120, (height/2)-50); // THE ALERT FOR UPDATE CHECKING PAUSE
loadRemoteAdminSettings(); // GET THE LATEST ADMIN FROM GOOGLE SPREADSHEET

//Credentials
ConfigurationBuilder cbTest = new ConfigurationBuilder();
// ------- NB - the variables twitOAuthConsumerKey, etc. ARE IN A SEPARATE SHEET
cbTest.setOAuthConsumerKey(twitOAuthConsumerKey);
cbTest.setOAuthConsumerSecret(twitOAuthConsumerSecret);
cbTest.setOAuthAccessToken(twitOAuthAccessToken);
cbTest.setOAuthAccessTokenSecret(twitOAuthAccessTokenSecret);

Twitter twitterTest = new TwitterFactory(cbTest.build()).getInstance();

try { // TRY ALLOWS ERROR HANDLING FOR EXCEPTIONS...
Query query = new Query(queryString); // this is default you check the first of 4 admin settings, but should be extended to include passing a selctor param
query.count(int(adminSettings[3])); // count is the number of tweets returned per page

QueryResult result = twitterTest.search(query); // gets the query

int ll=1; // @@ DEBUG STUFF
for (Status status : result.getTweets()) { // EXTRACT THE TWEETS
String user = status.getUser().getScreenName();// GET THE TWITTER USERNAME
usernames.add(user); // ADD TO THE ARRAYLIST FOR USERNAMES
String msg = status.getText(); // EXTRACT THE TWEET TEXT
println ("tweet #"+ll); // @@ DEBUG STUFF
println("@" + user); // @@ DEBUG STUFF
println("Text of tweet=" + status.getText()); // @@ DEBUG STUFF
println ("-----------");
ll++; // @@ DEBUG STUFF (INCREMENT)

//Break the tweet into words
String[] input = msg.split(" "); // BREAK DOWN THE TWEET USING SPACES AS A DELIMITER
for (int j = 0; j < input.length; j++) {


cleanTweets.add(input[j]); // CLEANTWEETS IS A STORE FOR TWEET WORDS WITH STOP WORDS REMOVED

for (int ii = 0 ; ii < stopWords.size(); ii++) {

if (stopWords.get(ii).equals(input[j])) {
cleanTweets.remove(input[j]); // THIS WORD IS A STOP WORD - REMOVE IT!
println("Word removed due to matched stopword: "+input[j]); // @@ DEBUG STUFF
} // end if
} //end for (ii++) //stopword c
}// end clean this msg
}// end of all tweet cleaning
println ("cleanTweets = "+cleanTweets);

for (int k = 0; k < cleanTweets.size(); k++) {
if ((cleanTweets.get(k).equals(queryString))!= true)
{
println ("(cleanTweets.get(k) <"+cleanTweets.get(k)+".equals(queryString))"+queryString+"!= true");
words.add(cleanTweets.get(k));
if (words.size() >int(adminSettings[6]))
{
words.remove(0);
} // keeps aray to a finite length by dropping off first element as new one is added


// >>>>>> make the list of hashtags
String hashtag= cleanTweets.get(k);

String hashtagArray[] = hashtag.split("#");
if (hashtagArray.length>1)
{
//println ("inside checker");
hashtags.add(hashtagArray[1]);
int v=words.size()-1;
words.remove(v);
if (queryType.equals("hashtag"))
{
if (hashtagArray[1].equals("#"+queryString)) {
hashtags.remove(hashtagArray[1]);
}
else if (hashtags.size() >int(adminSettings[6])/10)
{
hashtags.remove(0);
} // keeps aray to a finite length by dropping off first element as new one is added
}
println ("hashtagArray["+k+"]= "+hashtagArray[1]);
}
// <<<<<<<


// >>>>>>> set up list of usernames
String username= cleanTweets.get(k);
String usernameArray[] = username.split("@");
// println ("usernameArray = ");
//println (usernameArray);
if (usernameArray.length>1)
{

int vv=words.size()-1; // takes out the username by removing last entry in words()
words.remove(vv);//
// println ("usernameArray["+j+"]= "+usernameArray[1]);
}
if (usernames.size() >int(adminSettings[6])/6)
{
usernames.remove(0);
} // keeps aray to a finite length by dropping off first element as new one is added

// <<<<<<<<

// >>>>>>>> set up urls >>>>>>
String url = cleanTweets.get(k);
String urlArray[] = url.split("h");
if (urlArray.length>1)
{
String urlArray2[] = urlArray[1].split("t");
if (urlArray2.length>2)
{
urls.add(url);
int vvv=words.size()-1;
words.remove(vvv);
}
else if (urls.size() >int(adminSettings[6])/6)
{
urls.remove(0);
} // keeps aray to a finite length by dropping off first element as new one is added

// <<<<<<<<<< end

// >>>>>>>>>>
}
};
}

println ("WORDS.SIZE () = "+words.size());
println ("words = "+words);
println ("@@@@@@@@@@@@@@@@@@@@@@@");
// >>>>>>> create text log file of words from pyschic scanning >>>>>>>>>
for (int p =0;p
{
uberWords = append (uberWords, words.get(p).toString());
}
uberWords = append (uberWords, "WORDS UPDATE REFRESH COMPLETED");
uberWords = append (uberWords, " ");
saveStrings ("words-"+stamp+".txt", uberWords);
// <<<<<< end word text log file

// >>>>>> create log file of users
for (int jj =0;jj

{
uberUsers = append (uberUsers, "@"+usernames.get(jj).toString());

}
saveStrings ("users-"+stamp+".txt", uberUsers);
// <<<<<<<<< end user text log file

// >>>>>> create log file of hashtags
for (int jj =0;jj

{
uberHashtags = append (uberHashtags, "#"+hashtags.get(jj).toString());

}
saveStrings ("hashtags-"+stamp+".txt", uberHashtags);
// <<<<<<<<< end hashtag text log file

// >>>>>> create log file of urls
for (int jj =0;jj

{
uberUrls = append (uberUrls, urls.get(jj).toString());

}
saveStrings ("urls-"+stamp+".txt", uberUrls);
// <<<<<<<<< end url text log file

} //end try ??

catch(TwitterException e) {
println("TEST query tweet: " + e + " Status code: " + e.getStatusCode());
} // end try/catch

grabTime=millis(); // reset grabTime
if (loadSettingsFirstLoadFlag==true)
{
loadSettingsFirstLoadFlag =false; //
//this is the line that will cause subsequqnt updates to remove the first word(0)
}
cleanTweets.clear();
tweetster.clear();
} // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< end grabTweets() <<<<<<<<

Function BUTTONCHECK() - checking for interaction

void buttonCheck(String tweetTextIntro)
{
if (b.isPressed()) {
println("button being pressed");
sendTweet ("digital (onscreen) Button MOUSE");
b.setWidth(50);
// action for onscreen button press
}
}
// <<<<<<<<<<<<<<<<<<<<<<< end of BUTTONCHECK

// >>>>>>>>>>>>>>> check the open serial port >>>>>>>>>>


Function CHECKSERIAL() - checks for data from Arduino

void checkSerial() {
println ();
//println ("inside checkSerial()");
try {
// >>>>>> see if the port is sending you stuff
while (port.available () > 0) {
String inByte = port.readString();
println ("Safe from OUSIDE IF . inByte = "+inByte);
int w=int(random(150));
b.setWidth(w);
println ();
port.clear();
sendTweet ("physical Button");
}
} // end try
catch (Exception e) {
println ("Check serial exception = "+e);
}
} // <<<<<<<<<<<<<<<<<<<<< end checkSerial <<<<<<<<<<<<<<<<<<<<<


// >>>>>>>>>>>>>>>>>>> load remote admin settings >>>>>>>>>>>>>>

Function LOADREMOTESETTINGS() - pulls control data from Google spreadsheetsdata

void loadRemoteAdminSettings ()
{
try {
String checkRandomSpeech = adminSettings[8];
adminSettings = loadStrings("https://docs.google.com/spreadsheet/pub?key=0AgTXh43j7oFVdFNOcGtMaXZnS3IwdTJacllUT1hLQUE&output=txt");
if ((checkRandomSpeech.equals(adminSettings[8]))!=true) {
tts.speak(adminSettings[8]);
}
for (int i = 0 ; i < adminSettings.length; i++) {
println("adminSettings["+i+"]= "+adminSettings[i]);
} // end for

if (adminSettings[5].equals("h")) {
println ("use hashtag for search");
queryString = adminSettings[0];
queryType = "hashtag";
}
if (adminSettings[5].equals("u"))
{
println ("use username phrase for search");
queryString = adminSettings[1];
queryType = "username";
}
if (adminSettings[5].equals("s"))
{
println ("use search term for search");
queryString = adminSettings[2];
queryType = "search term";
}
updateDisplayVariables();
// now load load fortune fragments
String frag1 []= loadStrings ("https://docs.google.com/spreadsheet/pub?key=0AgTXh43j7oFVdDQ3cUZ5Y2RMTm9RSXNrdElZTjN5R1E&output=txt");
for (int ff1=0; ff1

{
fortFrags1.add(frag1[ff1]);
println ("Fortune Frag1 = "+fortFrags1.get(ff1));
}
String frag2 []= loadStrings ("https://docs.google.com/spreadsheet/pub?key=0AgTXh43j7oFVdGFQLTFhMUVqTTlkTjlRVUN4c3JtOGc&output=txt");
for (int ff2=0; ff2

{
fortFrags2.add(frag2[ff2]);
println ("Fortune Frag2 = "+frag2[ff2]);
println ("Fortune Frag1 = "+fortFrags2.get(ff2));
}
String frag3 []= loadStrings ("https://docs.google.com/spreadsheet/pub?key=0AgTXh43j7oFVdFE0Qm1yYmhyYWJETVJsSHJIOGFMQ3c&output=txt");
for (int ff3=0; ff3

{
fortFrags3.add(frag3[ff3]);
println ("Fortune Frag3 = "+frag3[ff3]);
}
String frag4 []= loadStrings ("https://docs.google.com/spreadsheet/pub?key=0AgTXh43j7oFVdG9KTnhLS2Zvbk5HNXp2RmRpeUZtTUE&output=txt");
for (int ff4=0; ff4

{
fortFrags4.add(frag4[ff4]);
println ("Fortune Frag4 = "+frag4[ff4]);
}
// end if
}
catch (Exception e) {
println ("no CONNECTION");
}
}

// >>>>

LOADREMOTESTOPWORDS() - imports the stopword filter list

void loadRemoteStopWords ()
{
try {
String stopWordsLoader [] = loadStrings("https://docs.google.com/spreadsheet/pub?key=0AgTXh43j7oFVdFByYk41am9jRnRkeU9LWnhjZFJTOEE&output=txt");

if (loadstopWordsCheckInt==true)
{
for (int i = 0 ; i < stopWordsLoader.length; i++) {
//stop
stopWords.add(stopWordsLoader[i]);
println("stopWords["+i+"]= "+stopWords.get(i)+". Length now: "+stopWords.size());
}
loadstopWordsCheckInt=false;
}
}
catch (Exception e)
{
println("jjjjjjjjjjjjj");
}
}



Function KEYRELEASED() - makes keyboard "enter/return" press fire tweet (testing only)

void keyReleased() {
if (key==TAB) {
println ("Tab key released");

//tfToggleFocus(valFocus);
}
else if ((key==ENTER )|(key == RETURN)) {

sendTweet("pressed return");
}
}

Function TFTOGGLEFOCUS - Ignore this. used for testing

void tfToggleFocus (int val)
{
/*if (val==0)
{
tf.setFocus(true);
tf.setColorBackground(focusBackgroundColor);
tf.setColor(focusColor);
valFocus=1;
}
else if (val==1) {
tf.setFocus(false);
tf.setColorBackground(focusOffBackgroundColor);
tf.setColor(focusOffColor);
valFocus=0;
}*/
tf.setFocus(true);
tf.setColorBackground(focusBackgroundColor);
tf.setColor(focusColor);
}



Function UPDATEDISPLAYVARIABLES() - updates variables with admin settings...

void updateDisplayVariables() {
// Reading the mind queryString
String currentHashtag = adminSettings [0];
String displayHashtag = "hashtag = "+adminSettings [0]+" ";
if (adminSettings[0]=="")
{
displayHashtag="";
}
String currentUserName = adminSettings [1];
String displayUserName = "@username = "+adminSettings [1]+" ";
if (adminSettings[1]=="")
{
displayUserName="";
}
String currentSearchTerms = adminSettings [2];
String displaySearchTerms = "search = "+adminSettings [2];
if (adminSettings[2]=="")
{
displayUserName="";
}
readingSettingText = "Reading the hive mind for "+queryType+"= "+ queryString;
color cl = color(70, 30, 180);// not in use
color cl2 = color(70, 230, 180);//not in use
fill (clPanel);
//rect(30, boxY+15, width, 105);
fill(0, 0, 0, 255);
textSize(40);
//text(readingSettingText, 10, boxY+40);
//rect(0, boxY+13, width, 1);
textSize(40);
text("@", 2, boxY+33);


fill (clPanel);
rect(columnPos2_X, boxY-10, width, 50);
fill(0, 0, 0, 255);
textSize(35);
//text(adminSettings[7], columnPos2_X+30, boxY-25);


text("


//displayHashtag+displayUserName+displaySearchTerms;
}


Function BUILDADMINPANEL() - create the form interface

void buildAdminPanel() {
int panelTop = height-panelHeight;

fill (clPanel);
rect(0, panelTop, width, panelHeight);
// >>>>>>> set up fonts
//PFont font = createFont("arial",20);
font = new ControlFont(createFont("arial", 100), 40);
// <<<<<<<

// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> set up GUI elements >>>>>>>>>>>>>>>>>>>>
noStroke();
cp5 = new ControlP5(this); // adds in a control instance to add buttons and text field to
noStroke();
tf = cp5.addTextfield("");
tf.setPosition(border, boxY);
tf.setSize(boxWidth, boxHeight);
tf.setColorBackground(focusBackgroundColor);
tf.setColor(focusColor);
tf.setFont(font);
tf.setFocus(true);
//tf.setAutoClear(true);
tf.captionLabel().setControlFont(font);
// @@@



// create a new button with name 'Tell my Fortune'
b = cp5.addButton("but", 20, 100, 50, 80, 20);
b.setId(2); // id to target this element
b.setWidth(250); // width
b.setHeight(25);
b.setPosition(border, boxY+100);

b.captionLabel().setControlFont(font);
b.captionLabel().style().marginLeft = 1;
b.captionLabel().style().marginTop = 1;
b.setVisible(true);
b.isOn();
b.setColorBackground(focusOffBackgroundColor);


// @@@



// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< end of GUI <<<<<<<<<<


// >>>>>>>>
}


Function READFORTUNE()  - The biggie - this concocts the fortunes...


void readFortune (String tweetText)
{
int picW1 = int(random (words.size()));
String fortuneWord1= words.get(picW1);
int picW2 = int(random (words.size()));
String fortuneWord2= words.get(picW2);
int hash = int(random (hashtags.size()));
String fortuneHash= hashtags.get(hash);
int urler = int(random (urls.size()));
String fortuneUrl= urls.get(urler);
int userer = int(random (usernames.size()));
String fortuneUser = usernames.get(userer);

int frag1Int =int (random (fortFrags1.size()));
String fraglet1 = fortFrags1.get(frag1Int);
int frag2Int =int (random (fortFrags2.size()));
String fraglet2 = fortFrags2.get(frag2Int);
int frag3Int =int (random (fortFrags3.size()));
String fraglet3 = fortFrags3.get(frag3Int);
int frag4Int =int (random (fortFrags4.size()));
String fraglet4 = fortFrags4.get(frag4Int);
fortune = "Psychic summary for @"+tfUserCurrent + ". for: #"+queryString+". "+ fortuneWord1+", "+ fortuneWord2+", #"+fortuneHash+ ", @"+fortuneUser+", "+fortuneUrl+". Enjoy/RT";
println ("just before fortune spoken");
fortuneSpoken = "Hello. "+tfUserCurrent+". "+adminSettings[7]+ ". "+fortuneGreeting +". Here. you are. Your Psychic Hive Mind. Fortune. based on reading .the collective mind of. "+queryString+". is. "+fraglet1+". "+ fortuneWord1+". "+ fraglet2+". "+fortuneWord2+". "+fraglet3+". hashtag."+fortuneHash+ ". "+fraglet4+". Twitter user."+fortuneUser+". Thank you. I have tweeted a psychic summary of this reading to your twitter account. Moove along now. " ;
println ("fortuneSpoken= "+fortuneSpoken);
}

Step 12: The Psychic Fortune Teller reading and its tweet summary - test example

Picture of The Psychic Fortune Teller reading and its tweet summary - test example
This is recording of a pre-conference test fortune reading.
It gives a pretty good idead of what the Psychic Fortune Teller readings sound like.



It a test of the randomising fortune teller logic of the Psychic Hive-Mind Fortune Teller automaton.

This was ecorded in early May 2013, just prior to taking to MuseumNext in Amsterdam, a European museum technology conference (museumnext.org)

The psychic mind-reading is being generated from a search of the live twitter data with an API call for search term "museumnext"

http://search.twitter.com/search.json?q=%23museumnext
The subtitles show what it is speaking. The words in red are being harvested from Tweets in real time. The rest of the words, shown in black, are pre-scripted, but randomised text from the logic engine.


The full text of the tweet is:

"
Hello rosemarybeetle. 

I hope you are very well and thank you for stopping by.
I have stared deep into the hive mind. 
Here you are. 
Your psychic hive mind fortune, based on reading the collective mind of museumnext is:

It could be that city and no’s
There perhaps are #amsterdam
Find out more about the work done by twitter user @javiercelaya

Thank you.
I have tweeted a psychic summary of this reading to you twitter account.

Move along now
"
This spoken fortune includes two random terms (city and Nos) a hashtag (#amsterdam) and a user name @javiercelaya

The psychic brain is actually a Twitter app. The account used to register the Twitter app (Rosemarybeetle Labs) also sends a tweet summary with the two random terms, hashtag and username, but also a random URL from the discussion. Although they tweet well, URLs are not included in the full spoken fortune reading, because generally URLs are unintelligible when spoken by text-to-speech engines!


Psychice Hive-Mind test tweet

The URL it picked at random was an Arts and Health South West website news item.

Rather superbly this was about research into participation in museums and the arts and the findings that

"visiting museums is positively associated with higher levels of happiness”




This is a lovely example the psychic Fortune Teller demonstrating the the power of social discovery, that randomised connections make (if in a rather weird way!!)

Step 13: Further information, references, credits, etc.

Picture of Further information, references, credits, etc.

Acknowledgments

As ever, this is built on the open shared skills of others - respect!

Twitter4j  - Processing library for connecting to Twitter

Yusuke Yamamoto (and others I believe?)
Twitter4j is a java library that does the connection and transaction handling between Processing and Twitter. This is what enables simple connection to harvest the tweet content, and to send tweets, both from withing the Processign brain. AWESOME.
github.com/yusuke/twitter4j/network

GURU - text-to-speech library for Processing

Nikolaus Gradwohl
The GURU text to speech library for Processing, is a key bit of code. It is what allows the Psychic Fortune Teller to talk to you.
www.local-guru.net/blog/pages/ttslib

ControlP5 - interface-building library for Processing

Andreas Schlegel
ControlP5 is a Processing library used to handle creation and control of user interfaces in the Processing draw() window.
It's used for the twitter username entry box
www.sojamo.de/libraries/controlP5/

Visualisation - Processing sketch for graphically display text list files

JER THORP
The visualisation of words on screen is based on an example sketch by Jer.
blog.blprnt.com/blog/blprnt/updated-quick-tutorial-processing-twitter 

Keyboard hacking

RANDY SARAFAN (Randolfo)
The keyboard hacking started by reading this explanation of how he hacked open a keyboard to use the keys as inputs - super clear Instructable.
instructables.com/id/Hacking-a-USB-Keyboard/

MuseumNext Conference appearance

Thanks to JIM RICHARDSON and JASPER VISSER for agreeing to see what could be hacked together as an exhibition piece for the MuseumNext conference. The Psychic Fortune Teller debuted there in Amsterdam in May 2013.
museumnext.org/



More information on my Making Weird Stuff blog

Much of this Instructable first appeared on my blog Making Weird Stuff as it was being developed.
There is some extra stuff not included here too...
  • Tag cloud of data terms used

This post shows a breakdown of terms used at the conference MuseumNext 2013.
makingweirdstuff.blogspot.co.uk/2013/05/what-psychic-fortune-teller-read-from.html
  • Original concept idea in a post in March 2013

This post is amusing to see how far the concept developed.

  • More detailed post on text-to-speech using guru library and Google spreadsheet

A more detailed post on this.


Previous Instructables

  • Twitr_janus

Twitr_janus is a previous project from which some of the Psychic Fortune Teller tech was developed. It is a web-controlled data-driven puppet.
instructables.com/id/How-to-make-a-remote-control-sentient-web-puppet-b/

Love it! :)

You could just cut the case only(inside circuits removed completely),fold the bottom trace to the back,add insulation on the bottom layer so keys are not being connected,and do the same thing on the second and third layers.
In that way,you could save the time and effort.
Weird and creepy, but cool!!
Hey thanks on both cases. It is intended to be slightly unsettling. That's always the mark of a good fairground thing. It is the slight sense of fear/unease that makes it exciting. :)
canida2 years ago
Wow. Excellent detailed write-up, and super-creepy effect!
rosemarybeetle (author)  canida2 years ago
thanks. took a few weeks to write up, so glad you liked it. appreciated

check out my blog for ongoing making stuff. mostly as weird. occassionally food-related :)
http://makingweirdstuff.blogspot.com
BrittLiv2 years ago
Wow, this is creepy, good job!
rosemarybeetle (author)  BrittLiv2 years ago
many thanks. it has a creepier cousin too!
http://www.instructables.com/id/How-to-make-a-remote-control-sentient-web-puppet-b/