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