Twitter Connected candy dispenser. agent closes stream after a couple hrs when Device is disconnecte

First I’ll start off with my code and then my log. I’m very new to squirrel. im thinking my issue comes from some timeout values. but would appreciate any input.

My project is a candy machine that opens a twitter stream searching for a specific hashtag. when someone tweets that hashtag it dispenses for half a second

in the log you can see it closes the stream with a response code 28 and then reopens the stream. but after a couple hrs it closes the stream with a response code 200 and never reopens it. i have to go in and reboot the agent to get it running again.

Agent code
`
// Copyright © 2013 Electric Imp
// This file is licensed under the MIT License
// http://opensource.org/licenses/MIT

// Twitter Keys
const API_KEY = “”;
const API_SECRET = “”;
const AUTH_TOKEN = “”;
const TOKEN_SECRET = “”;
const searchTerms = “#AtomicRocks

class Twitter {
// OAuth
_consumerKey = null;
_consumerSecret = null;
_accessToken = null;
_accessSecret = null;

// URLs
streamUrl = "https://stream.twitter.com/1.1/";
tweetUrl = "https://api.twitter.com/1.1/statuses/update.json";

// Streaming
streamingRequest = null;
_reconnectTimeout = null;
_buffer = null;


constructor (consumerKey, consumerSecret, accessToken, accessSecret) {
    this._consumerKey = consumerKey;
    this._consumerSecret = consumerSecret;
    this._accessToken = accessToken;
    this._accessSecret = accessSecret;
    
    this._reconnectTimeout = 60;
    this._buffer = "";
}

/***************************************************************************
 * function: Tweet
 *   Posts a tweet to the user's timeline
 * 
 * Params:
 *   status - the tweet
 *   cb - an optional callback
 * 
 * Return:
 *   bool indicating whether the tweet was successful(if no cb was supplied)
 *   nothing(if a callback was supplied)
 **************************************************************************/
function tweet(status, cb = null) {
    local headers = { };
    
    local request = _oAuth1Request(tweetUrl, headers, { "status": status} );
    if (cb == null) {
        local response = request.sendsync();
        if (response && response.statuscode != 200) {
            server.log(format("Error updating_status tweet. HTTP Status Code %i:\\r\

%s", response.statuscode, response.body));
return false;
} else {
return true;
}
} else {
request.sendasync(cb);
}
}

/***************************************************************************
 * function: Stream
 *   Opens a connection to twitter's streaming API
 * 
 * Params:
 *   searchTerms - what we're searching for
 *   onTweet - callback function that executes whenever there is data
 *   onError - callback function that executes whenever there is an error
 **************************************************************************/
function stream(searchTerms, onTweet, onError = null) {
	server.log("Opening stream for: " + searchTerms);
    // Set default error handler
    if (onError == null) onError = _defaultErrorHandler.bindenv(this);
    
    local method = "statuses/filter.json"
    local headers = { };
    local post = { track = searchTerms };
    local request = _oAuth1Request(streamUrl + method, headers, post);
    
    
    this.streamingRequest = request.sendasync(
        
        function(resp) {
            // connection timeout
            server.log("Stream Closed (" + resp.statuscode + ": " + resp.body + ")");
            // if we have autoreconnect set
            if (resp.statuscode == 28) {
                stream(searchTerms, onTweet, onError);
            } else if (resp.statuscode == 420) {
                imp.wakeup(_reconnectTimeout, function() { stream(searchTerms, onTweet, onError); }.bindenv(this));
                _reconnectTimeout *= 2;
            }
        }.bindenv(this),
        
        function(body) {
             try {
                if (body.len() == 2) {
                    _reconnectTimeout = 60;
                    _buffer = "";
                    return;
                }
                
                local data = null;
                try {
                    data = http.jsondecode(body);
                } catch(ex) {
                    _buffer += body;
                    try {
                        data = http.jsondecode(_buffer);
                    } catch (ex) {
                        return;
                    }
                }
                if (data == null) return;

                // if it's an error
                if ("errors" in data) {
                    server.log("Got an error");
                    onError(data.errors);
                    return;
                } 
                else {
                    if (_looksLikeATweet(data)) {
                        onTweet(data);
                        return;
                    }
                }
            } catch(ex) {
                // if an error occured, invoke error handler
                onError([{ message = "Squirrel Error - " + ex, code = -1 }]);
            }
        }.bindenv(this)
    
    );
}

/***** Private Function - Do Not Call *****/
function _encode(str) {
    return http.urlencode({ s = str }).slice(2);
}

function _oAuth1Request(postUrl, headers, data) {
    local time = time();
    local nonce = time;

    local parm_string = http.urlencode({ oauth_consumer_key = _consumerKey });
    parm_string += "&" + http.urlencode({ oauth_nonce = nonce });
    parm_string += "&" + http.urlencode({ oauth_signature_method = "HMAC-SHA1" });
    parm_string += "&" + http.urlencode({ oauth_timestamp = time });
    parm_string += "&" + http.urlencode({ oauth_token = _accessToken });
    parm_string += "&" + http.urlencode({ oauth_version = "1.0" });
    parm_string += "&" + http.urlencode(data);
    
    local signature_string = "POST&" + _encode(postUrl) + "&" + _encode(parm_string);
    
    local key = format("%s&%s", _encode(_consumerSecret), _encode(_accessSecret));
    local sha1 = _encode(http.base64encode(http.hash.hmacsha1(signature_string, key)));
    
    local auth_header = "oauth_consumer_key=\""+_consumerKey+"\", ";
    auth_header += "oauth_nonce=\""+nonce+"\", ";
    auth_header += "oauth_signature=\""+sha1+"\", ";
    auth_header += "oauth_signature_method=\""+"HMAC-SHA1"+"\", ";
    auth_header += "oauth_timestamp=\""+time+"\", ";
    auth_header += "oauth_token=\""+_accessToken+"\", ";
    auth_header += "oauth_version=\"1.0\"";
    
    local headers = { 
        "Authorization": "OAuth " + auth_header
    };
    
    local url = postUrl + "?" + http.urlencode(data);
    local request = http.post(url, headers, "");
    return request;
}

function _looksLikeATweet(data) {
    return (
        "created_at" in data &&
        "id" in data &&
        "text" in data &&
        "user" in data
    );
}

function _defaultErrorHandler(errors) {
    foreach(error in errors) {
        server.log("ERROR " + error.code + ": " + error.message);
    }
}

}
function onTweet(tweetData) {
// log the tweet, and who tweeted it (there is a LOT more info in tweetData)
server.log(format("%s - %s", tweetData.text, tweetData.user.screen_name));
device.send(“dispense”, 0.5);
}
myTwitter <- Twitter(API_KEY, API_SECRET, AUTH_TOKEN, TOKEN_SECRET);
myTwitter.stream( searchTerms , onTweet);{

    //if (tweetData.txt = 1);   
    //   
    // server.log("Agent: Dispensing Medium");

}
`

I have not seen Twitter streams close with a 200 status before, although it looks like that is documented (and that if your stream closes becauses of status 200, you can reconnect immediately).

I’ve updated our Twitter class to reflect this - give it a try:

https://github.com/electricimp/reference/blob/master/webservices/twitter/twitter.class.nut

Device code
`
// Snack Dispenser
imp.setpowersave(true);

//Configure Pin
motor <- hardware.pin9;
motor.configure(DIGITAL_OUT);
motor.write(0);

agent.on(“dispense”, function(seconds) {
server.log(“Imp Dispensing:” + seconds);
motor.write(1);
imp.wakeup(seconds, function(){ motor.write(0);});
});
`

log after timeout

2014-10-14 16:08:58 UTC-5 [Agent] Opening stream for: #AtomicRocks
2014-10-14 16:18:58 UTC-5 [Agent] Stream Closed (28: )
2014-10-14 16:18:58 UTC-5 [Agent] Opening stream for: #AtomicRocks
2014-10-14 16:28:58 UTC-5 [Agent] Stream Closed (28: )
2014-10-14 16:28:58 UTC-5 [Agent] Opening stream for: #AtomicRocks
2014-10-14 16:38:58 UTC-5 [Agent] Stream Closed (28: )
2014-10-14 16:38:58 UTC-5 [Agent] Opening stream for: #AtomicRocks
2014-10-14 16:48:58 UTC-5 [Agent] Stream Closed (28: )
2014-10-14 16:48:58 UTC-5 [Agent] Opening stream for: #AtomicRocks
2014-10-14 16:58:58 UTC-5 [Agent] Stream Closed (28: )
2014-10-14 16:58:58 UTC-5 [Agent] Opening stream for: #AtomicRocks
2014-10-14 17:08:58 UTC-5 [Agent] Stream Closed (28: )
2014-10-14 17:08:58 UTC-5 [Agent] Opening stream for: #AtomicRocks
2014-10-14 17:18:58 UTC-5 [Agent] Stream Closed (28: )
2014-10-14 17:18:58 UTC-5 [Agent] Opening stream for: #AtomicRocks
2014-10-14 17:28:58 UTC-5 [Agent] Stream Closed (28: )
2014-10-14 17:28:58 UTC-5 [Agent] Opening stream for: #AtomicRocks
2014-10-14 17:38:58 UTC-5 [Agent] Stream Closed (28: )
2014-10-14 17:38:58 UTC-5 [Agent] Opening stream for: #AtomicRocks
2014-10-14 17:48:58 UTC-5 [Agent] Stream Closed (28: )
2014-10-14 17:48:58 UTC-5 [Agent] Opening stream for: #AtomicRocks
2014-10-14 17:58:58 UTC-5 [Agent] Stream Closed (28: )
2014-10-14 17:58:58 UTC-5 [Agent] Opening stream for: #AtomicRocks
2014-10-14 18:04:00 UTC-5 [Agent] Stream Closed (200: )

https://github.com/electricimp/reference/blob/master/webservices/twitter/twitter.agent.nut not good? Broken link may be?

https://github.com/electricimp/reference/blob/master/webservices/twitter/twitter.class.nut

I am getting the same time out problem as geared2299 when I try to stream from Twitter. Of course I am using the same program a geared2299 but not a copy from him. I do not receive any streams. The program hangs up and closes the stream with error 28. Has anyone found out why this is happening?

Did you use the code from the link philmy posted?

I used the code that philmy posted and got the same result as before. …Stream closed (28:) and no streams.

Stream close (28:) typically means that things are working OK.

cURL error code 28 is a timeout, which occurs after the stream has been open for 10 minutes… it should be auto-reconnecting after that message.