Skill Level: Any Skill Level

Some Arduino experience useful

I will show you how to set up a small device that connects to IBM Watson IoT Platform and uses Watson Sentiment Analysis to visualize Twitter feeds.


Particle Internet Button:

IBM Bluemix account and IBM Watson IoT Platform instantiated

Review Kevin Hoyt's excellent tutorial on connecting the device up:


  1. Get Connected

    Follow Kevin Hoyt's tutorial to get connected. I will focus the next steps on the modifications I did to enable 2-way communication, support the Internet Button and to decode commands coming back to the device from the IBM Watson IoT Platform.

    Once that is done, search for the “Internet Button” library and add it to your project.



  2. Modify code to enable 2-way comms and respond to commands

    You can replace his entire example with this code, I started with his and then re-worked it to enable upstream (buttons) and to light up lights based on commands coming down.

    Section by section comments:


    The only change from Kevin's example is that I added a subscribe command and of course put in my credentials. I also added the setup for the Internet Button and to set the LED brightness to 50%. The LEDs are very bright and I wanted a more subtle look. I also plan to run this device from a battery so running at a lower brightness made sense. I also use Light 1 as a connection status.


    The little Particle device has 4 buttons on the back. I read the buttons during the main processing loop to see if any are pressed. Because the loop runs so fast, I put a small “debounce” delay in the loop to ignore inputs for 250 iterations. I determined that number by trial-and-error so you may want to adjust that up or down. Smaller numbers makes the buttons more responsive but also more prone to sending more than 1 message to the Watson IoT Platform, and larger numbers make it feel sluggish or could even miss a button press. LED 11 is used to show which button is pressed by changing colors, but it gets reset quickly on the next call to “callback()”. Just for debugging mostly.

    I did not use a “delay(nnn)” statement because when an Arduino encounters one of those, processing stops for that time duration (in milliseconds). I'm relying on my ancient history of writing high speed code ifor fighter jet cockpits, so here's my analysis on why I am avoiding it:

    • The device runs at 120Mhz, not bad but we have to be aware it's not super fast
    • My desire is to process incoming MQTT messages as fast as possible to make the display zippy. That means the call to client.loop() needs to be done as often as possible. This also keeps the incoming message queue short since we will be getting a lot of data.
    • Buttons need to be debounced so that we don't send a ton of button press messages up the line.
    • Responsiveness is subjective, but we need to handle button presses and incoming messages in a timely manner so the device seems fast to a human, and we of course want to display as much data as we can.

    Using a state machine to debounce the buttons then is the best solution – we process incoming messages at full speed and only ignore buttons for 250 iterations of the loop. I think it's a great compromise so that our little display can keep up with a high rate Twitter feed.

    The main loop also detects disconnects and attempts to reconnect – I ran into this every now and then on my network. It may not be all that necessary in the long run but it makes the device a lot more reliable. It then sets Light 1 to show the status.


    This is a small routine to respond to commands coming back from the Watson IoT Platform. The parameter is a 1 character string so we have to decode it to a number. I used a simple counter to index around the lights except for the two I am using for status (1 and 11). This gives a nice “history” effect, showing the last 4 to 5 seconds of Twitter sentiment data.

    // This #include statement was automatically added by the Particle IDE.
    #include "InternetButton/InternetButton.h"

    // This #include statement was automatically added by the Particle IDE.
    #include "MQTT/MQTT.h"

    InternetButton b = InternetButton();

    char *IOT_CLIENT = "d:<org>:<devType>:<devID>";
    char *IOT_HOST = "<org>";
    char *IOT_PASSWORD = "<pw>";
    char *IOT_PUBLISH = "iot-2/evt/count/fmt/json";
    char *IOT_USERNAME = "use-token-auth";
    char *IOT_SUBSCRIBE = "iot-2/cmd/lightUp/fmt/json";

    MQTT client( IOT_HOST, 1883, callback );

    void setup() {

    b.begin(); //Init the Internet Button hardware
    b.setBrightness(50); // lower the blazing brighness, also lower power for batteries

    // Connect to IBM Watsion IOT Platform

    // Subscribe to events if connected. Use LED for status.
    if( client.isConnected() ) {
    client.subscribe( IOT_SUBSCRIBE );

    //Main processing loop, just look for buttons and process the MQTT queue
    int debounce = 0;
    void loop() {
    // handle disconnects
    if( !client.isConnected() ) {
    client.subscribe( IOT_SUBSCRIBE );

    // Handle buttons with a debounce
    if ( debounce > 250 ) {
    // B1
    if (b.buttonOn(1)) {
    debounce = 0;
    "{\"button\": 1}"

    // B2
    if (b.buttonOn(2)) {
    debounce = 0;
    "{\"button\": 2}"

    // B3
    if (b.buttonOn(3)) {
    debounce = 0;
    "{\"button\": 3}"

    // B4
    if (b.buttonOn(4)) {
    debounce = 0;
    "{\"button\": 4}"

    } else {
    //Run MQTT processing

    // handle commands from the Watson IOT Platform
    // light up one LED, color based on the parameter
    // loop around from 1 to 11, so it shows a history of the values
    int s; // used to decode the incoming character, declare outside the function to keep it off the stack
    int num = 1; // tracks which LED is lit, valid values 1 to 11 (unless heartbeat is on - then change to 1 to 10)
    void callback( char* topic, byte* payload, unsigned int length ) {

    // Wrap around LED indexer. Use 1 and 12 unless heartbeat is on then use 1 to 11
    if (num > 11)
    num = 1;

    // convert incoming character to integer
    s = payload[0]-'0';

    // decode number to color
    switch (s) {
    case 1:
    b.ledOn(num, 0, 255, 0);
    case 2:
    b.ledOn(num, 255, 255, 255);
    case 3:
    b.ledOn(num, 255, 0, 0);
    b.ledOn(num, 0, 0, 255);


  3. Create NodeRED flow for sentiment

    Using NodeRED, I then connected to Twitter and searched for “interesting” tweets (for this example, any tweet with “Trump” or “Hillary” in it during the 2016 US election season will generate PLENTY of interesting data!). Because of the volume, you will see a rate limiter in there because the device was just getting overloaded with data. I have run it with the rate limiter out and it works great if you have a good network connection! The lights spin quickly due to the volume.  Once the tweet has been analyzed by Watson, I convert the sentiment to a value of 1, 2 or 3. If you look at the code above, the function named “callback” does all the heavy lifting. it cycles through lights 1 to 11, incrementing each time it is called, and sets the light green, white or red depending on the sentiment value sent down.


    The trick to be able to switch between flows (at least for now) is to set a global variable that coresponds to the incoming button press. The Switch node can act on the value of a Global so it simply is an On/Off switch for the flow. The Sentiment node returns a -1<0<1 number that I convert to a simpler 1, 2 or 3 using the Switch and the 3 nodes of Positive Neutral and Negative. The flow is posted below except for the Twitter node to preserve my Twitter credentials.

    [{"id":"52c99067.58b35","type":"ibmiot in","z":"b65029bf.13b428","authentication":"boundService","apiKey":"","inputType":"evt","deviceId":"GGParticle1","applicationId":"","deviceType":"Particle","eventType":"+","commandType":"","format":"json","name":"ParticleIn","service":"registered","allDevices":"","allApplications":"","allDeviceTypes":"","allEvents":true,"allCommands":"","allFormats":"","qos":0,"x":600,"y":460,"wires":[["7b530d19.3f85dc","619f171b.7bd088","c55ba3d1.625f9"]]},{"id":"7b530d19.3f85dc","type":"debug","z":"b65029bf.13b428","name":"","active":true,"console":"false","complete":"payload.button","x":853,"y":421.25,"wires":[]},{"id":"369674f1.f535b4","type":"ibmiot out","z":"b65029bf.13b428","authentication":"boundService","apiKey":"","outputType":"cmd","deviceId":"GGParticle1","deviceType":"Particle","eventCommandType":"lightUp","format":"json","data":"1","qos":0,"name":"Send lightUp cmd","service":"registered","x":1358,"y":222,"wires":[]},{"id":"ba979aff.270a8","type":"sentiment","z":"b65029bf.13b428","name":"","x":614.75,"y":227.75,"wires":[["fd4520ba.01cd6"]]},{"id":"fd4520ba.01cd6","type":"switch","z":"b65029bf.13b428","name":"","property":"sentiment.score","propertyType":"msg","rules":[{"t":"gt","v":"0","vt":"str"},{"t":"eq","v":"0","vt":"str"},{"t":"lt","v":"0","vt":"str"}],"checkall":"false","outputs":3,"x":754.75,"y":227.75,"wires":[["703a6392.a8fb14"],["37ff43f4.2c2cdc"],["c1a8a58.9d45358"]]},{"id":"703a6392.a8fb14","type":"change","z":"b65029bf.13b428","name":"Positive","rules":[{"t":"set","p":"payload","pt":"msg","to":"1","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":905,"y":149.75,"wires":[["a61771ba.76894"]]},{"id":"37ff43f4.2c2cdc","type":"change","z":"b65029bf.13b428","name":"Neutral","rules":[{"t":"set","p":"payload","pt":"msg","to":"2","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":934.75,"y":214.75,"wires":[["a61771ba.76894"]]},{"id":"c1a8a58.9d45358","type":"change","z":"b65029bf.13b428","name":"Negative","rules":[{"t":"set","p":"payload","pt":"msg","to":"3","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":936.75,"y":299.75,"wires":[["a61771ba.76894"]]},{"id":"df5aa381.8e3198","type":"debug","z":"b65029bf.13b428","name":"","active":false,"console":"false","complete":"false","x":1242,"y":96.5,"wires":[]},{"id":"a61771ba.76894","type":"json","z":"b65029bf.13b428","name":"","x":1156,"y":205.75,"wires":[["df5aa381.8e3198","369674f1.f535b4"]]},{"id":"ef327a52.5f0f88","type":"delay","z":"b65029bf.13b428","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"5","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":430,"y":340,"wires":[["ba979aff.270a8"]]},{"id":"5b5ec17f.d31258","type":"link out","z":"b65029bf.13b428","name":"Weather","links":["99e6532.f350cb"],"x":995,"y":580,"wires":[]},{"id":"619f171b.7bd088","type":"function","z":"b65029bf.13b428","name":"Set","func":"global.set(\"stream\" , msg.payload.button);\nreturn msg;","outputs":1,"noerr":0,"x":830,"y":480,"wires":[[]]},{"id":"c55ba3d1.625f9","type":"switch","z":"b65029bf.13b428","name":"","property":"payload.button","propertyType":"msg","rules":[{"t":"eq","v":"4","vt":"str"}],"checkall":"true","outputs":1,"x":820,"y":580,"wires":[["5b5ec17f.d31258"]]},{"id":"3e6b3821.5a5368","type":"inject","z":"b65029bf.13b428","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":520,"y":60,"wires":[["7031ffa7.ac2da8"]]},{"id":"7031ffa7.ac2da8","type":"function","z":"b65029bf.13b428","name":"Set","func":"global.set(\"stream\" , 1);\nreturn msg;","outputs":1,"noerr":0,"x":730,"y":60,"wires":[[]]},{"id":"4c33f927.ce16d8","type":"switch","z":"b65029bf.13b428","name":"Stream == 1","property":"stream","propertyType":"global","rules":[{"t":"eq","v":"1","vt":"str"}],"checkall":"true","outputs":1,"x":210,"y":140,"wires":[["ef327a52.5f0f88"]]},{"id":"de506462.9f84d","type":"switch","z":"b65029bf.13b428","name":"Stream == 2","property":"stream","propertyType":"global","rules":[{"t":"eq","v":"2","vt":"str"}],"checkall":"true","outputs":1,"x":210,"y":340,"wires":[["ef327a52.5f0f88"]]},{"id":"6691e85e.d955a8","type":"switch","z":"b65029bf.13b428","name":"Stream == 3","property":"stream","propertyType":"global","rules":[{"t":"eq","v":"3","vt":"str"}],"checkall":"true","outputs":1,"x":190,"y":480,"wires":[["ef327a52.5f0f88"]]},{"id":"894a7f55.b93068","type":"switch","z":"b65029bf.13b428","name":"Stream == 4","property":"stream","propertyType":"global","rules":[{"t":"eq","v":"4","vt":"str"}],"checkall":"true","outputs":1,"x":190,"y":620,"wires":[["ef327a52.5f0f88"]]}]

    You will need to add a Twitter node and modify the “send lightUp command” node to talk to your device.

  4. Create NodeRED flow for buttons

    This flow is super simple – just receive the button message and decode which button is pressed. Buttons 1 and 2 enable and disable flows on the output side to select which Twitter stream to analyze and display.

    Button 4, and as you can see it sends a message off the flow to another page of my NodeRED instance. That page communicates with one of my Raspberry Pi devices, and will cause it to speak the current weather conditions at my office.



    The device also has accelerometers and a little noisemaker too, eventually I want to do something fun with those too!

  5. Have fun!

    I put a pin on the back of mine so I can wear it…makes a very geeky conversation starter! It runs for hours on a small battery so it's great to use at events to show the power of Watson Cognitive technology in a simple device.

    It's also fun to select between the 2 feeds – to see the difference and to also illustrate the command/response capabilities of the Watson IoT Platform.

5 comments on"Monitoring Twitter sentiment using NodeRED and Particle Internet Button"

  1. Hi Greg … great post … thanks for sharing
    The NeoPixel with a Particle Photon would work great for this as well.

    • hi Markus, yes it would and I’ve seen other wearable things around IBM made from those. Last I looked they were all out of stock so I found this one instead! Thanks for the link.

  2. Hi Greg, I build this one with the NeoPixel.. I also used Watson Tone Analyzer to make it more cognitive. it is nice to attract people and tell them about Bluemix/IoT/Watson.

  3. I updated the flows and code to show switching between 2 Twitter feeds now, had some help figuring it out! Future: hopefully we’ll have a way to send search terms into the Twitter node so that it is easier and more efficient than having 2 feeds running at the same time.

Join The Discussion