[211] | 1 | #ifndef PubNub_h
|
---|
| 2 | #define PubNub_h
|
---|
| 3 |
|
---|
| 4 |
|
---|
| 5 | #include <stdint.h>
|
---|
| 6 |
|
---|
| 7 |
|
---|
| 8 | /* By default, the PubNub library is built to work with the Ethernet
|
---|
| 9 | * shield. WiFi shield support can be enabled by commenting the
|
---|
| 10 | * following line and commenting out the line after that. Refer
|
---|
| 11 | * to the PubNubJsonWifi sketch for a complete example. */
|
---|
| 12 | #define PubNub_Ethernet
|
---|
| 13 | //#define PubNub_WiFi
|
---|
| 14 |
|
---|
| 15 |
|
---|
| 16 | #if defined(PubNub_Ethernet)
|
---|
| 17 | #include <Ethernet.h>
|
---|
| 18 | #define PubNub_BASE_CLIENT EthernetClient
|
---|
| 19 |
|
---|
| 20 | #elif defined(PubNub_WiFi)
|
---|
| 21 | #include <WiFi.h>
|
---|
| 22 | #define PubNub_BASE_CLIENT WiFiClient
|
---|
| 23 |
|
---|
| 24 | #else
|
---|
| 25 | #error PubNub_BASE_CLIENT set to an invalid value!
|
---|
| 26 | #endif
|
---|
| 27 |
|
---|
| 28 |
|
---|
| 29 | /* Some notes:
|
---|
| 30 | *
|
---|
| 31 | * (i) There is no SSL support on Arduino, it is unfeasible with
|
---|
| 32 | * Arduino Uno or even Arduino Mega's computing power and memory limits.
|
---|
| 33 | * All the traffic goes on the wire unencrypted and unsigned.
|
---|
| 34 | *
|
---|
| 35 | * (ii) We re-resolve the origin server IP address before each request.
|
---|
| 36 | * This means some slow-down for intensive communication, but we rather
|
---|
| 37 | * expect light traffic and very long-running sketches (days, months),
|
---|
| 38 | * where refreshing the IP address is quite desirable.
|
---|
| 39 | *
|
---|
| 40 | * (iii) We let the users read replies at their leisure instead of
|
---|
| 41 | * returning an already preloaded string so that (a) they can do that
|
---|
| 42 | * in loop() code while taking care of other things as well (b) we don't
|
---|
| 43 | * waste precious RAM by pre-allocating buffers that are never needed.
|
---|
| 44 | *
|
---|
| 45 | * (iv) If you are having problems connecting, maybe you have hit
|
---|
| 46 | * a bug in Debian's version of Arduino pertaining the DNS code. Try using
|
---|
| 47 | * an IP address as origin and/or upgrading your Arduino package.
|
---|
| 48 | *
|
---|
| 49 | * (v) The optional timeout parameter allows you to specify a timeout
|
---|
| 50 | * period after which the subscribe call shall be retried. Note
|
---|
| 51 | * that this timeout is applied only for reading response, not for
|
---|
| 52 | * connecting or sending data; use retransmission parameters of
|
---|
| 53 | * the Ethernet library to tune this. As a rule of thumb, timeout
|
---|
| 54 | * smaller than 30 seconds may still block longer with flaky
|
---|
| 55 | * network. Default server-side timeout of PubNub API is 300s.
|
---|
| 56 | *
|
---|
| 57 | * (vi) If some of the PubNub calls fail with your WiFi shield (e.g. you
|
---|
| 58 | * see "subscribe error" and similar messages in serial console), your
|
---|
| 59 | * WiFi shield firmware may be buggy - e.g. a WiFi shield bought
|
---|
| 60 | * commercially in May 2013 had a firmware that had to be upgaded.
|
---|
| 61 | * This is not so difficult to do, simply follow:
|
---|
| 62 | *
|
---|
| 63 | * http://arduino.cc/en/Hacking/WiFiShieldFirmwareUpgrading
|
---|
| 64 | *
|
---|
| 65 | * (vii) The vendor firmware for the WiFi shield has dubious TCP implementation;
|
---|
| 66 | * for example, TCP ports of outgoing connections are always chosen from the
|
---|
| 67 | * same sequence, so if you reset your Arduino, some of the new connections
|
---|
| 68 | * may interfere with an outstanding TCP connection that has not been closed
|
---|
| 69 | * before the reset; i.e. you will typically see a single failed request
|
---|
| 70 | * somewhere down the road after a reset.
|
---|
| 71 | *
|
---|
| 72 | * (viii) It is essential to use a new enough Arduino version so that
|
---|
| 73 | * the WiFi library actually works properly. Most notably, version 1.0.5
|
---|
| 74 | * has been confirmed to work while Arduino 1.0.4 is broken.
|
---|
| 75 | */
|
---|
| 76 |
|
---|
| 77 |
|
---|
| 78 | /* This class is a thin EthernetClient wrapper whose goal is to
|
---|
| 79 | * automatically acquire time token information when reading
|
---|
| 80 | * subscribe call response.
|
---|
| 81 | *
|
---|
| 82 | * (i) The user application sees only the JSON body, not the timetoken.
|
---|
| 83 | * As soon as the body ends, PubSubclient reads the rest of HTTP reply
|
---|
| 84 | * itself and disconnects. The stored timetoken is used in the next call
|
---|
| 85 | * to the PubSub::subscribe method then. */
|
---|
| 86 | class PubSubClient : public PubNub_BASE_CLIENT {
|
---|
| 87 | public:
|
---|
| 88 | PubSubClient() :
|
---|
| 89 | PubNub_BASE_CLIENT(), json_enabled(false)
|
---|
| 90 | {
|
---|
| 91 | strcpy(timetoken, "0");
|
---|
| 92 | }
|
---|
| 93 |
|
---|
| 94 | /* Customized functions that make reading stop as soon as we
|
---|
| 95 | * have hit ',' outside of braces and string, which indicates
|
---|
| 96 | * end of JSON body. */
|
---|
| 97 | virtual int read();
|
---|
| 98 | virtual int read(uint8_t *buf, size_t size);
|
---|
| 99 | virtual void stop();
|
---|
| 100 |
|
---|
| 101 | /* Block until data is available. Returns false in case the
|
---|
| 102 | * connection goes down or timeout expires. */
|
---|
| 103 | bool wait_for_data(int timeout = 310);
|
---|
| 104 |
|
---|
| 105 | /* Enable the JSON state machine. */
|
---|
| 106 | void start_body();
|
---|
| 107 |
|
---|
| 108 | inline char *server_timetoken() { return timetoken; }
|
---|
| 109 |
|
---|
| 110 | private:
|
---|
| 111 | void _state_input(uint8_t ch, uint8_t *nextbuf, size_t nextsize);
|
---|
| 112 | void _grab_timetoken(uint8_t *nextbuf, size_t nextsize);
|
---|
| 113 |
|
---|
| 114 | /* JSON state machine context */
|
---|
| 115 | bool json_enabled:1;
|
---|
| 116 | bool in_string:1;
|
---|
| 117 | bool after_backslash:1;
|
---|
| 118 | int braces_depth;
|
---|
| 119 |
|
---|
| 120 | /* Time token acquired during the last subscribe request. */
|
---|
| 121 | char timetoken[22];
|
---|
| 122 | };
|
---|
| 123 |
|
---|
| 124 |
|
---|
| 125 | enum PubNub_BH {
|
---|
| 126 | PubNub_BH_OK,
|
---|
| 127 | PubNub_BH_ERROR,
|
---|
| 128 | PubNub_BH_TIMEOUT,
|
---|
| 129 | };
|
---|
| 130 |
|
---|
| 131 | class PubNub {
|
---|
| 132 | public:
|
---|
| 133 | /* Init the Pubnub Client API
|
---|
| 134 | *
|
---|
| 135 | * This should be called after Ethernet.begin().
|
---|
| 136 | * Note that the string parameters are not copied; do not
|
---|
| 137 | * overwrite or free the memory where you stored the keys!
|
---|
| 138 | * (If you are passing string literals, don't worry about it.)
|
---|
| 139 | * Note that you should run only a single publish at once.
|
---|
| 140 | *
|
---|
| 141 | * @param string publish_key required key to send messages.
|
---|
| 142 | * @param string subscribe_key required key to receive messages.
|
---|
| 143 | * @param string origin optional setting for cloud origin.
|
---|
| 144 | * @return boolean whether begin() was successful. */
|
---|
| 145 | bool begin(const char *publish_key, const char *subscribe_key, const char *origin = "pubsub.pubnub.com");
|
---|
| 146 |
|
---|
| 147 | /* Set the UUID identification of PubNub client. This is useful
|
---|
| 148 | * e.g. for presence identification.
|
---|
| 149 | *
|
---|
| 150 | * Pass NULL to unset. The string is not copied over (just like
|
---|
| 151 | * in begin()). See the PubNubSubscriber example for simple code
|
---|
| 152 | * that generates a random UUID (although not 100% reliable). */
|
---|
| 153 | void set_uuid(const char *uuid);
|
---|
| 154 |
|
---|
| 155 | /* Set the authorization key/token of PubNub client. This is useful
|
---|
| 156 | * e.g. for access rights validation (PAM).
|
---|
| 157 | *
|
---|
| 158 | * Pass NULL to unset. The string is not copied over (just like
|
---|
| 159 | * in begin()). */
|
---|
| 160 | void set_auth(const char *auth);
|
---|
| 161 |
|
---|
| 162 | /* Publish
|
---|
| 163 | *
|
---|
| 164 | * Send a message (assumed to be well-formed JSON) to a given channel.
|
---|
| 165 | *
|
---|
| 166 | * Note that the reply can be obtained using code like:
|
---|
| 167 | client = publish("demo", "\"lala\"");
|
---|
| 168 | if (!client) return; // error
|
---|
| 169 | while (client->connected()) {
|
---|
| 170 | // More sophisticated code will want to add timeout handling here
|
---|
| 171 | while (client->connected() && !client->available()) ; // wait
|
---|
| 172 | char c = client->read();
|
---|
| 173 | Serial.print(c);
|
---|
| 174 | }
|
---|
| 175 | client->stop();
|
---|
| 176 | * You will get content right away, the header has been already
|
---|
| 177 | * skipped inside the function. If you do not care about
|
---|
| 178 | * the reply, just call client->stop(); immediately.
|
---|
| 179 | *
|
---|
| 180 | * It returns an object that is typically EthernetClient (but it
|
---|
| 181 | * can be a WiFiClient if you enabled the WiFi shield).
|
---|
| 182 | *
|
---|
| 183 | * @param string channel required channel name.
|
---|
| 184 | * @param string message required message string in JSON format.
|
---|
| 185 | * @param string timeout optional timeout in seconds.
|
---|
| 186 | * @return string Stream-ish object with reply message or NULL on error. */
|
---|
| 187 | PubNub_BASE_CLIENT *publish(const char *channel, const char *message, int timeout = 30);
|
---|
| 188 |
|
---|
| 189 | /**
|
---|
| 190 | * Subscribe
|
---|
| 191 | *
|
---|
| 192 | * Listen for a message on a given channel. The function will block
|
---|
| 193 | * and return when a message arrives. Typically, you will run this
|
---|
| 194 | * function from loop() function to keep listening for messages
|
---|
| 195 | * indefinitely.
|
---|
| 196 | *
|
---|
| 197 | * As a reply, you will get a JSON array with messages, e.g.:
|
---|
| 198 | * ["msg1",{msg2:"x"}]
|
---|
| 199 | * and so on. Empty reply [] is also normal and your code must be
|
---|
| 200 | * able to handle that. Note that the reply specifically does not
|
---|
| 201 | * include the time token present in the raw reply.
|
---|
| 202 | *
|
---|
| 203 | * @param string channel required channel name.
|
---|
| 204 | * @param string timeout optional timeout in seconds.
|
---|
| 205 | * @return string Stream-ish object with reply message or NULL on error. */
|
---|
| 206 | PubSubClient *subscribe(const char *channel, int timeout = 310);
|
---|
| 207 |
|
---|
| 208 | /**
|
---|
| 209 | * History
|
---|
| 210 | *
|
---|
| 211 | * Receive list of the last N messages on the given channel.
|
---|
| 212 | *
|
---|
| 213 | * @param string channel required channel name.
|
---|
| 214 | * @param int limit optional number of messages to retrieve.
|
---|
| 215 | * @param string timeout optional timeout in seconds.
|
---|
| 216 | * @return string Stream-ish object with reply message or NULL on error. */
|
---|
| 217 | PubNub_BASE_CLIENT *history(const char *channel, int limit = 10, int timeout = 310);
|
---|
| 218 |
|
---|
| 219 | private:
|
---|
| 220 | enum PubNub_BH _request_bh(PubNub_BASE_CLIENT &client, unsigned long t_start, int timeout, char qparsep);
|
---|
| 221 |
|
---|
| 222 | const char *publish_key, *subscribe_key;
|
---|
| 223 | const char *origin;
|
---|
| 224 | const char *uuid;
|
---|
| 225 | const char *auth;
|
---|
| 226 |
|
---|
| 227 | PubNub_BASE_CLIENT publish_client, history_client;
|
---|
| 228 | PubSubClient subscribe_client;
|
---|
| 229 | };
|
---|
| 230 |
|
---|
| 231 | extern class PubNub PubNub;
|
---|
| 232 |
|
---|
| 233 | #endif
|
---|