4 | * http://angryelectron.com
5 | *
6 | * The ThingSpeak Java Client is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or (at your
9 | * option) any later version.
10 | *
11 | * The ThingSpeak Java Client is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14 | * Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License along with
17 | * theThingSpeak Java Client. If not, see .
18 | */
19 |
20 | package com.angryelectron.thingspeak;
21 |
22 | import java.text.SimpleDateFormat;
23 | import java.util.Date;
24 | import java.util.HashMap;
25 |
26 | /**
27 | *
28 | * Optional parameters which control the format of a {@link Feed}.
29 | * All ThingSpeak optional parameters are supported except 'key' (because it is set via
30 | * {@link Channel}) and 'format' (which must be JSON in order to parse the feed).
31 | *
32 | *
33 | * To retrieve a feed with optional parameters, setup a {@link FeedParameters}
34 | * object with the required parameters, then pass it to one of the
35 | * {@link Channel} methods to retrieve the desired feed. For example, to
36 | * include the latitude, longitude, elevation, and status fields in a feed for
37 | * channel 1234:
38 | *
39 | * {@code
40 | * Channel channel = new Channel(1234);
41 | * FeedParameters options = new FeedParameters();
42 | * options.location(true);
43 | * options.status(true);
44 | * options.results(100);
45 | * Feed feed = channel.getChannelFeed(options);
46 | * }
47 | *
48 | */
49 | public class FeedParameters {
50 |
51 | /**
52 | * Pre-defined time periods. ThingSpeak only accepts certain values.
53 | * For use with:
54 | *
55 | * - {@link #timescale(com.angryelectron.thingspeak.FeedParameters.Period)}
56 | * - {@link #sum(com.angryelectron.thingspeak.FeedParameters.Period)}
57 | * - {@link #average(com.angryelectron.thingspeak.FeedParameters.Period)}
58 | * - {@link #median(com.angryelectron.thingspeak.FeedParameters.Period)}
59 | *
60 | */
61 | public enum Period {
62 | /**
63 | * 10 minutes.
64 | */
65 | T10m(10),
66 |
67 | /**
68 | * 15 minutes.
69 | */
70 | T15m(15),
71 |
72 | /**
73 | * 20 minutes.
74 | */
75 | T20m(20),
76 |
77 | /**
78 | * 30 minutes.
79 | */
80 | T30m(30),
81 |
82 | /**
83 | * 1 hour / 60 minutes.
84 | */
85 | T1h(60),
86 |
87 | /**
88 | * 4 hours / 240 minutes.
89 | */
90 | T4h(240),
91 |
92 | /**
93 | * 12 hours / 720 minutes.
94 | */
95 | T12h(720),
96 |
97 | /**
98 | * 24 hours / 1440 minutes.
99 | */
100 | T24h(1440);
101 |
102 | private final Integer minutes;
103 |
104 | private Period(Integer minutes) {
105 | this.minutes = minutes;
106 | }
107 |
108 | Integer minutes() {
109 | return this.minutes;
110 | }
111 |
112 | }
113 |
114 | /**
115 | * A map to store the parameter names and values.
116 | */
117 | HashMap fields = new HashMap<>();
118 |
119 | /**
120 | * The date format required by ThingSpeak.
121 | */
122 | private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
123 |
124 | /**
125 | * Select the number of results to be returned. Feeds that return more than 100
126 | * results are cached for 5 minutes, so set results < 100 for real time
127 | * applications. By default, all results up to a maximum of 8000 will be returned.
128 | * @param results 8000 max, or less than 100 to disable 5 minute data cache.
129 | */
130 | public void results(Integer results) {
131 | if (results > 8000) {
132 | throw new IllegalArgumentException("Feed cannot return more than 8000 results.");
133 | }
134 | fields.put("results", results);
135 | }
136 | /**
137 | * Limit results to the past number of days.
138 | * @param days Number of days prior to now to include in feed.
139 | */
140 | public void days(Integer days) {
141 | fields.put("days", days);
142 | }
143 |
144 | /**
145 | * Limit results to entries recorded on or after a start date.
146 | * @param date Start date.
147 | */
148 | public void start(Date date) {
149 | fields.put("start", formatter.format(date));
150 | }
151 |
152 | /**
153 | * Limit results to entries recorded on or before an end date.
154 | * @param date End date.
155 | */
156 | public void end(Date date) {
157 | fields.put("end", formatter.format(date));
158 | }
159 |
160 | /**
161 | * Timezone offset. Default is UTC. Applies to all dates returned in the
162 | * feed.
163 | * @param hours Offset (+/-) in hours.
164 | */
165 | public void offset(Integer hours) {
166 | fields.put("offset", hours);
167 | }
168 |
169 | /**
170 | * Include the status field for each entry in the feed. By default,
171 | * the status field is not included.
172 | * @param include Feed includes the status field when True.
173 | */
174 | public void status(Boolean include) {
175 | fields.put("status", include);
176 | }
177 |
178 | /**
179 | * Include location information for each entry in the feed. By default,
180 | * latitude, longitude, and elevation are not included.
181 | * @param include Feed includes location fields when True.
182 | */
183 | public void location(Boolean include) {
184 | fields.put("location", include);
185 | }
186 |
187 | /**
188 | * Include only entries with fields greater or equal to a minimum value in the
189 | * feed.
190 | * @param value Minimum value.
191 | */
192 | public void min(Double value) {
193 | fields.put("min", value);
194 | }
195 |
196 | /**
197 | * Include only entries with fields less than or equal to a maximum value.
198 | * @param value Maximum value.
199 | */
200 | public void max(Double value) {
201 | fields.put("max", value);
202 | }
203 |
204 | /**
205 | * Round fields to a certain number of decimal places.
206 | * @param places Round to this many decimal places.
207 | */
208 | public void round(Integer places) {
209 | fields.put("round", places);
210 | }
211 |
212 | /**
213 | * Include only the first value in the given period.
214 | * @param t Time period.
215 | */
216 | public void timescale(Period t) {
217 | fields.put("timescale", t.minutes());
218 | }
219 |
220 | /**
221 | * For each field, sum values in the given period.
222 | * @param t Time period.
223 | */
224 | public void sum(Period t) {
225 | fields.put("sum", t.minutes());
226 | }
227 |
228 | /**
229 | * For each field, average the values over the given period.
230 | * @param t Time period.
231 | */
232 | public void average(Period t) {
233 | fields.put("average", t.minutes());
234 | }
235 |
236 | /**
237 | * For each field, find the median value in the given period.
238 | * @param t Time period.
239 | */
240 | public void median(Period t) {
241 | fields.put("median", t.minutes());
242 | }
243 |
244 | }
245 |
--------------------------------------------------------------------------------
/src/com/angryelectron/thingspeak/ThingSpeakException.java:
--------------------------------------------------------------------------------
1 | /**
2 | * ThingSpeak Java Client
3 | * Copyright 2014, Andrew Bythell
4 | * http://angryelectron.com
5 | *
6 | * The ThingSpeak Java Client is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or (at your
9 | * option) any later version.
10 | *
11 | * The ThingSpeak Java Client is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14 | * Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License along with
17 | * theThingSpeak Java Client. If not, see .
18 | */
19 |
20 | package com.angryelectron.thingspeak;
21 |
22 | /**
23 | * Thrown when the ThingSpeak API rejects requests due to invalid API keys, arguments,
24 | * or data formats.
25 | */
26 | public class ThingSpeakException extends Exception {
27 | public ThingSpeakException(String message) {
28 | super(message);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/com/angryelectron/thingspeak/log4j/ThingSpeakAppender.java:
--------------------------------------------------------------------------------
1 | /**
2 | * ThingSpeak Appender for log4j Copyright 2014, Andrew Bythell
3 | *
4 | * http://angryelectron.com
5 | *
6 | * The ThingSpeak Java Client is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or (at your
9 | * option) any later version.
10 | *
11 | * The ThingSpeak Java Client is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14 | * Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License along with
17 | * the ThingSpeak Appender. If not, see .
18 | */
19 | package com.angryelectron.thingspeak.log4j;
20 |
21 | import com.angryelectron.thingspeak.Channel;
22 | import com.angryelectron.thingspeak.Entry;
23 | import com.angryelectron.thingspeak.ThingSpeakException;
24 | import com.mashape.unirest.http.exceptions.UnirestException;
25 | import java.io.IOException;
26 | import java.text.DateFormat;
27 | import java.text.SimpleDateFormat;
28 | import java.util.Date;
29 | import java.util.Properties;
30 | import java.util.logging.Level;
31 | import java.util.logging.Logger;
32 | import org.apache.log4j.AppenderSkeleton;
33 | import org.apache.log4j.spi.LoggingEvent;
34 |
35 | /**
36 | *
37 | * Appender for log4j that logs messages to ThingSpeak. To prepare a channel for
38 | * logging, create a new ThingSpeak channel with three fields (names not
39 | * important):
40 | *
41 | * - Date
42 | * - Level
43 | * - Message
44 | *
45 | *
46 | *
47 | * Then create and configure a new appender. Use
48 | * {@link #configureChannel(Integer, String, String) configureChannel}
49 | * to configure the appender, or set via log4j.properties:
50 | *
51 | * - log4j.appender.ThingSpeak=com.angryelectron.thingspeak.log4j.ThingSpeakAppender
52 | * - com.angryelectron.thingspeak.log4j.channelNumber = [channel number]
53 | * - com.angryelectron.thingspeak.log4j.apiWriteKey = [channel api write key]
54 | * - (optional) com.angryelectron.thingspeak.log4j.server = http://your.thingspeak.server
55 | *
56 | *
57 | * If the server is not specified, thingspeak.com will be used. Remember that
58 | * you must use an alternate server if your application logs faster than the
59 | * rate limits imposed by thingspeak.com.
60 | *
61 | * Next, set your root logger to use the new appender:
62 | *
63 | * - log4j.rootLogger=INFO, ThingSpeak
64 | *
65 | *
66 | */
67 | public class ThingSpeakAppender extends AppenderSkeleton {
68 |
69 | private final Properties properties = new Properties();
70 | private final String channelPropertyKey = "com.angryelectron.thingspeak.log4j.channelNumber";
71 | private final String apiPropertyKey = "com.angryelectron.thingspeak.log4j.apiWriteKey";
72 | private final String serverPropertyKey = "com.angryelectron.thingspeak.log4j.server";
73 | private Channel channel;
74 |
75 | /**
76 | * Constructor.
77 | */
78 | public ThingSpeakAppender() {
79 | try {
80 | properties.load(getClass().getResourceAsStream("/log4j.properties"));
81 | String apiWriteKey = properties.getProperty(apiPropertyKey);
82 | Integer channelNumber = Integer.parseInt(properties.getProperty(channelPropertyKey));
83 | String server = properties.getProperty(serverPropertyKey);
84 | channel = new Channel(channelNumber, apiWriteKey);
85 | if (!server.isEmpty()) {
86 | channel.setUrl(server);
87 | }
88 | } catch (IOException | NumberFormatException | NullPointerException ex) {
89 | /* ignore - will be caught and logged in append() */
90 | }
91 | }
92 |
93 | /**
94 | * Configure the channel. Use to configure the appender in code (vs.
95 | * log4j.properties).
96 | *
97 | * @param channelNumber ThingSpeak channel number.
98 | * @param apiWriteKey ThinSpeak API write key for the channel.
99 | * @param url URL of thingspeak server. If null, the public server
100 | * (thingpspeak.com) will be used.
101 | */
102 | public void configureChannel(Integer channelNumber, String apiWriteKey, String url) {
103 | channel = new Channel(channelNumber, apiWriteKey);
104 | if (url != null) {
105 | channel.setUrl(url);
106 | }
107 | }
108 |
109 | /**
110 | * Internal. Append log messages as an entry in a ThingSpeak channel.
111 | *
112 | * @param event log4j event.
113 | */
114 | @Override
115 | protected void append(LoggingEvent event) {
116 | Date timeStamp = new Date(event.timeStamp);
117 | DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
118 | Entry entry = new Entry();
119 | entry.setField(1, dateFormat.format(timeStamp));
120 | entry.setField(2, event.getLevel().toString());
121 | entry.setField(3, event.getMessage().toString());
122 | try {
123 | channel.update(entry);
124 | } catch (UnirestException | ThingSpeakException ex) {
125 | Logger.getLogger(ThingSpeakAppender.class.getName()).log(Level.SEVERE, null, ex);
126 | }
127 | }
128 |
129 | /**
130 | * Internal.
131 | */
132 | @Override
133 | public void close() {
134 | /* nothing to do */
135 | }
136 |
137 | /**
138 | * Internal. Thingspeak maps log data directly to channel "fields", so no
139 | * layout is required.
140 | *
141 | * @return false
142 | */
143 | @Override
144 | public boolean requiresLayout() {
145 | return false;
146 | }
147 |
148 | }
149 |
--------------------------------------------------------------------------------
/src/com/angryelectron/thingspeak/log4j/package.html:
--------------------------------------------------------------------------------
1 |
2 | ThingSpeak appender for log4j. Send log messages to ThingSpeak channels.
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/com/angryelectron/thingspeak/package.html:
--------------------------------------------------------------------------------
1 |
2 | Read and write ThingSpeak channels.
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/com/angryelectron/thingspeak/pub/PublicChannel.java:
--------------------------------------------------------------------------------
1 | /**
2 | * ThingSpeak Java Client
3 | * Copyright 2014, Andrew Bythell
4 | * http://angryelectron.com
5 | *
6 | * The ThingSpeak Java Client is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or (at your
9 | * option) any later version.
10 | *
11 | * The ThingSpeak Java Client is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14 | * Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License along with
17 | * theThingSpeak Java Client. If not, see .
18 | */
19 |
20 | package com.angryelectron.thingspeak.pub;
21 |
22 | import java.util.ArrayList;
23 | import java.util.Date;
24 | import java.util.List;
25 |
26 | /**
27 | * A ThingSpeak public channel listing.
28 | * @author abythell
29 | */
30 | public class PublicChannel {
31 |
32 | /**
33 | * This class must match the JSON returned by ThingSpeak.
34 | */
35 | private class Tag {
36 | private Integer id;
37 | private String name;
38 | }
39 |
40 | /**
41 | * These fields must match the JSON returned by ThingSpeak. They
42 | * are populated by de-serializing a JSON stream using GSON in the
43 | * PublicIterator class.
44 | */
45 | private Date created_at;
46 | private String description;
47 | private Double elevation;
48 | private Integer id;
49 | private Integer last_entry_id;
50 | private Double latitude;
51 | private Double longitude;
52 | private String name;
53 | private Integer ranking;
54 | private String username;
55 | private ArrayList tags = new ArrayList<>();
56 |
57 | /**
58 | * Get channel name.
59 | * @return Name.
60 | */
61 | public String getName() {
62 | return name;
63 | }
64 |
65 | /**
66 | * Get channel creation date.
67 | * @return Date.
68 | */
69 | public Date getCreatedAt() {
70 | return created_at;
71 | }
72 |
73 | /**
74 | * Get channel description.
75 | * @return Description.
76 | */
77 | public String getDescription() {
78 | return description;
79 | }
80 |
81 | /**
82 | * Get channel elevation.
83 | * @return Elevation, or 0.0 if no elevation was set for the channel.
84 | */
85 | public Double getElevation() {
86 | return elevation;
87 | }
88 |
89 | /**
90 | * Get channel id. The id is required to access the channel's feed.
91 | * @return Channel id.
92 | */
93 | public Integer getId() {
94 | return id;
95 | }
96 |
97 | /**
98 | * Get the id of the last entry posted to this channel.
99 | * @return Entry id.
100 | */
101 | public Integer getLastEntryId() {
102 | return last_entry_id;
103 | }
104 |
105 | /**
106 | * Get the latitude of this channel.
107 | * @return Latitude in decimal degrees, or 0.0 if no latitude was set for
108 | * this channel.
109 | */
110 | public Double getLatitude() {
111 | return latitude;
112 | }
113 |
114 | /**
115 | * Get the longitude of this channel.
116 | * @return Longitude in decimal degrees, or 0.0 if no longitude was set for
117 | * this channel.
118 | */
119 | public Double getLongitude() {
120 | return longitude;
121 | }
122 |
123 | /**
124 | * Get the channel's ranking. It is unclear to the author how rankings
125 | * are compiled.
126 | * @return Rank.
127 | */
128 | public Integer getRanking() {
129 | return ranking;
130 | }
131 |
132 | /**
133 | * Get the name of the user who owns this channel.
134 | * @return Username.
135 | */
136 | public String getUsername() {
137 | return username;
138 | }
139 |
140 | /**
141 | * Get the channel's tags.
142 | * @return A List of tags, which can be passed to
143 | * {@link PublicChannelCollection#PublicChannelCollection(java.lang.String)}
144 | * to obtain a list of other public channels containing the same tag.
145 | */
146 | public List getTags() {
147 | List list = new ArrayList<>();
148 | for (Tag t : tags) {
149 | list.add(t.name);
150 | }
151 | return list;
152 | }
153 |
154 | }
155 |
--------------------------------------------------------------------------------
/src/com/angryelectron/thingspeak/pub/PublicChannelCollection.java:
--------------------------------------------------------------------------------
1 | /**
2 | * ThingSpeak Java Client
3 | * Copyright 2014, Andrew Bythell
4 | * http://angryelectron.com
5 | *
6 | * The ThingSpeak Java Client is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or (at your
9 | * option) any later version.
10 | *
11 | * The ThingSpeak Java Client is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14 | * Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License along with
17 | * theThingSpeak Java Client. If not, see .
18 | */
19 |
20 | package com.angryelectron.thingspeak.pub;
21 |
22 | import java.util.AbstractCollection;
23 | import java.util.Iterator;
24 |
25 | /**
26 | * A collection of public ThingSpeak channels. The ThingSpeak Public API returns
27 | * paginated results, presumably because it could return a large set of
28 | * channels. This collection transparently fetches additional pages on-demand,
29 | * instead of trying to load all results into memory. Access the
30 | * {@link PublicChannel} objects in this collection by iterating:
31 | *
32 | * {@code
33 | * PublicChannelCollection pl = new PublicChannelCollection("temperature");
34 | * Iterator publicIterator = pl.iterator();
35 | * while (publicIterator.hasNext()) {
36 | * PublicChannel p = publicIterator.next();
37 | * }
38 | * }
39 | *
or a for loop:
40 | *
41 | * {@code
42 | * PublicChannelCollection publicChannels = new PublicChannelCollection("cheerlights");
43 | * for (PublicChannel channel : publicChannels) {
44 | * //do something with channel
45 | * }
46 | * }
47 | *
48 | *
49 | * @author abythell
50 | */
51 | public class PublicChannelCollection extends AbstractCollection {
52 |
53 | private final String url = "http://api.thingspeak.com";
54 | private final String tag;
55 | private Integer size;
56 |
57 | /**
58 | * Create a collection containing all public channels.
59 | */
60 | public PublicChannelCollection() {
61 | tag = null;
62 | }
63 |
64 | /**
65 | * Create a collection containing all public channels with the given tag.
66 | * @param tag Tag.
67 | */
68 | public PublicChannelCollection(String tag) {
69 | this.tag = tag;
70 | }
71 |
72 | /**
73 | * Use a server other than thingspeak.com. If you are hosting your own
74 | * Thingspeak server, set the url of the server here.
75 | * @param url eg. http://localhost, http://thingspeak.local:3000, etc.
76 | */
77 | public void setUrl(String url) {
78 | throw new UnsupportedOperationException("Public API is not implemented in open-source servers.");
79 | }
80 |
81 | /**
82 | * Get a PublicChannel iterator, for iterating through the collection.
83 | * @return Iterator.
84 | */
85 | @Override
86 | public Iterator iterator() {
87 | PublicIterator iterator = new PublicIterator(url, tag);
88 | size = iterator.size();
89 | return iterator;
90 | }
91 |
92 | /**
93 | * Get the number of public channels in this collection.
94 | * @return Number of channels.
95 | */
96 | @Override
97 | public int size() {
98 | return size;
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/src/com/angryelectron/thingspeak/pub/PublicIterator.java:
--------------------------------------------------------------------------------
1 | /**
2 | * ThingSpeak Java Client Copyright 2014, Andrew Bythell
3 | * http://angryelectron.com
4 | *
5 | * The ThingSpeak Java Client is free software: you can redistribute it and/or
6 | * modify it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or (at your
8 | * option) any later version.
9 | *
10 | * The ThingSpeak Java Client is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 | * Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License along with
16 | * theThingSpeak Java Client. If not, see .
17 | */
18 | package com.angryelectron.thingspeak.pub;
19 |
20 | import com.angryelectron.thingspeak.ThingSpeakException;
21 | import com.google.gson.Gson;
22 | import com.google.gson.GsonBuilder;
23 | import com.google.gson.JsonDeserializationContext;
24 | import com.google.gson.JsonDeserializer;
25 | import com.google.gson.JsonElement;
26 | import com.google.gson.JsonParseException;
27 | import com.mashape.unirest.http.HttpResponse;
28 | import com.mashape.unirest.http.JsonNode;
29 | import com.mashape.unirest.http.Unirest;
30 | import com.mashape.unirest.http.exceptions.UnirestException;
31 | import com.mashape.unirest.request.GetRequest;
32 | import java.lang.reflect.Type;
33 | import java.util.Iterator;
34 | import java.util.logging.Level;
35 | import java.util.logging.Logger;
36 |
37 | /**
38 | * Provides a custom iterator for PublicChannelCollections which works with
39 | * ThingSpeak's paginated results.
40 | *
41 | * @author abythell
42 | */
43 | class PublicIterator implements Iterator {
44 |
45 | /**
46 | * URL of the Thingspeak server.
47 | */
48 | private final String url;
49 |
50 | /**
51 | * Optional tag to search for. If null, all channels are returned.
52 | */
53 | private final String tag;
54 |
55 | /**
56 | * Current page of results and an iterator to the list of PublicChannel
57 | * channels it contains.
58 | */
59 | private PublicJSONResult results;
60 | private Iterator iterator;
61 |
62 | /**
63 | * ThingSpeak channels don't always contain elevation, latitude, or
64 | * longitude data. These empty strings cause NumberFormatExceptions when
65 | * using the default GSON de-serializer for Double. This replacement
66 | * de-serializes empty strings to 0.0.
67 | */
68 | private static class LocationDeserializer implements JsonDeserializer {
69 |
70 | @Override
71 | public Double deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
72 | try {
73 | return Double.parseDouble(json.getAsString());
74 | } catch (NumberFormatException ex) {
75 | return 0.0;
76 | }
77 | }
78 | }
79 |
80 | /**
81 | * Build a GSON de-serializer for the ThingSpeak PublicChannel Channel JSON.
82 | */
83 | private final Gson gson = new GsonBuilder()
84 | .registerTypeAdapter(Double.class, new LocationDeserializer())
85 | .setDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").create();
86 |
87 | /**
88 | * Constructor.
89 | *
90 | * @param url ThingSpeak server URL (eg. http://api.thingspeak.com).
91 | * @param tag Get channels with this tag only, or null to return all
92 | * channels.
93 | */
94 | protected PublicIterator(String url, String tag) {
95 | this.url = url;
96 | this.tag = tag;
97 | thingRequest(1);
98 | }
99 |
100 | /**
101 | * Make requests to the ThingSpeak server and parse the results.
102 | *
103 | * @param page The page of results to request.
104 | */
105 | private void thingRequest(Integer page) {
106 | try {
107 | GetRequest request = Unirest.get(url + "/channels/public.json");
108 | if (tag != null) {
109 | request.field("tag", tag);
110 | }
111 | request.field("page", page);
112 | HttpResponse response = request.asJson();
113 | if (response.getCode() != 200) {
114 | throw new ThingSpeakException("Request failed with code " + response.getCode());
115 | }
116 | results = gson.fromJson(response.getBody().toString(), PublicJSONResult.class);
117 | iterator = results.iterator();
118 | } catch (UnirestException | ThingSpeakException ex) {
119 | Logger.getLogger(PublicIterator.class.getName()).log(Level.SEVERE, null, ex);
120 | results = null;
121 | }
122 | }
123 |
124 | @Override
125 | public boolean hasNext() {
126 | if (iterator.hasNext()) {
127 | /* current page still has unreturned channels */
128 | return true;
129 | } else if (results.isLastPage()) {
130 | /* current page has returned all channels and there
131 | * are no more pages left */
132 | return false;
133 | } else {
134 | /* current page has returned all channels but there
135 | * are more pages remaining. */
136 | thingRequest(results.getCurrentPage() + 1);
137 | return true;
138 | }
139 | }
140 |
141 | @Override
142 | public PublicChannel next() {
143 | return iterator.next();
144 | }
145 |
146 | @Override
147 | public void remove() {
148 | iterator.remove();
149 | }
150 |
151 | Integer size() {
152 | return results.getTotalEntries();
153 | }
154 |
155 | }
156 |
--------------------------------------------------------------------------------
/src/com/angryelectron/thingspeak/pub/PublicJSONResult.java:
--------------------------------------------------------------------------------
1 | /**
2 | * ThingSpeak Java Client
3 | * Copyright 2014, Andrew Bythell
4 | * http://angryelectron.com
5 | *
6 | * The ThingSpeak Java Client is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or (at your
9 | * option) any later version.
10 | *
11 | * The ThingSpeak Java Client is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14 | * Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License along with
17 | * theThingSpeak Java Client. If not, see .
18 | */
19 |
20 | package com.angryelectron.thingspeak.pub;
21 |
22 | import java.util.ArrayList;
23 | import java.util.Iterator;
24 |
25 | /**
26 | * POJO to handle de-serialized JSON Public Channel data. Provides methods to
27 | * PublicIterator for accessing paging info.
28 | * @author abythell
29 | */
30 | class PublicJSONResult {
31 |
32 | /**
33 | * This class must match the JSON returned by Thingspeak.
34 | */
35 | private class Pagination {
36 | private Integer current_page;
37 | private Integer per_page;
38 | private Integer total_entries;
39 | }
40 |
41 | /**
42 | * These members must match the JSON returned by Thingspeak.
43 | */
44 | private final Pagination pagination = new Pagination();
45 | private final ArrayList channels = new ArrayList<>();
46 |
47 | /**
48 | * Get the current page represented by the data in channels.
49 | * @return Page number.
50 | */
51 | Integer getCurrentPage() {
52 | return pagination.current_page;
53 | }
54 |
55 | /**
56 | * Determine if the current page is the last one in the set.
57 | * @return True if this is the last page.
58 | */
59 | Boolean isLastPage() {
60 | if (pagination.total_entries <= pagination.per_page) {
61 | return true;
62 | } else {
63 | Double pages = (double) pagination.total_entries / pagination.per_page;
64 | return (pages.intValue() == pagination.current_page);
65 | }
66 | }
67 |
68 | /**
69 | * Get the iterator for the channel data. Used by PublicIterator to access
70 | * the PublicChannel objects stored in the current page.
71 | * @return
72 | */
73 | Iterator iterator() {
74 | return channels.iterator();
75 | }
76 |
77 | /**
78 | * Get a total count of all public channels.
79 | * @return Number of public channels in this result.
80 | */
81 | Integer getTotalEntries() {
82 | return pagination.total_entries;
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/com/angryelectron/thingspeak/pub/package.html:
--------------------------------------------------------------------------------
1 |
2 | Access Thingspeak public channel listings.
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/com/angryelectron/thingspeak/FeedTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * ThingSpeak Java Client
3 | * Copyright 2014, Andrew Bythell
4 | * http://angryelectron.com
5 | *
6 | * The ThingSpeak Java Client is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or (at your
9 | * option) any later version.
10 | *
11 | * The ThingSpeak Java Client is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14 | * Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License along with
17 | * theThingSpeak Java Client. If not, see .
18 | */
19 |
20 | package com.angryelectron.thingspeak;
21 |
22 | import java.text.SimpleDateFormat;
23 | import java.util.ArrayList;
24 | import java.util.Date;
25 | import org.apache.log4j.BasicConfigurator;
26 | import org.apache.log4j.Level;
27 | import org.apache.log4j.Logger;
28 | import org.junit.AfterClass;
29 | import static org.junit.Assert.assertEquals;
30 | import static org.junit.Assert.assertNotNull;
31 | import static org.junit.Assert.assertNull;
32 | import org.junit.BeforeClass;
33 | import org.junit.Test;
34 |
35 | public class FeedTest {
36 |
37 | /**
38 | * Update the channel with some sample data that will be read-back by all of
39 | * the Feed Tests.
40 | * @throws java.lang.Exception
41 | */
42 | @BeforeClass
43 | public static void setUpClass() throws Exception {
44 | BasicConfigurator.resetConfiguration();
45 | BasicConfigurator.configure();
46 | Logger.getLogger("org.apache.http").setLevel(Level.OFF);
47 | Entry entry = new Entry();
48 | entry.setElevation(0.0);
49 | entry.setField(1, "nonsense-data1");
50 | entry.setField(2, "nonsense-data2");
51 | entry.setField(3, "nonsense-data3");
52 | entry.setField(4, "nonsense-data4");
53 | entry.setField(5, "nonsense-data5");
54 | entry.setField(6, "nonsense-data6");
55 | entry.setField(7, "nonsense-data7");
56 | entry.setField(8, "nonsense-data8");
57 | entry.setLatitude(0.0);
58 | entry.setLong(0.0);
59 | entry.setStatus("unimportant status");
60 | entry.setTweet("irrelevant tweet");
61 | entry.setTwitter("invalid twitter user");
62 |
63 | Channel publicChannel = new Channel(TestChannelSettings.publicChannelID, TestChannelSettings.publicChannelWriteKey);
64 | Channel privateChannel = new Channel(TestChannelSettings.privateChannelID, TestChannelSettings.privateChannelWriteKey, TestChannelSettings.privateChannelReadKey);
65 |
66 | publicChannel.setUrl(TestChannelSettings.server);
67 | privateChannel.setUrl(TestChannelSettings.server);
68 |
69 | publicChannel.update(entry);
70 | privateChannel.update(entry);
71 | }
72 |
73 | /**
74 | * When testing is complete, pause before running other tests to prevent
75 | * hitting the API rate limit.
76 | * @throws Exception
77 | */
78 | @AfterClass
79 | public static void tearDownClass() throws Exception {
80 | Thread.sleep(TestChannelSettings.rateLimit);
81 | }
82 |
83 | @Test
84 | public void testGetChannelFeedPublic() throws Exception {
85 | System.out.println("testGetChannelFeedPublic");
86 | Channel publicChannel = new Channel(TestChannelSettings.publicChannelID, TestChannelSettings.publicChannelWriteKey);
87 | publicChannel.setUrl(TestChannelSettings.server);
88 | Feed publicFeed = publicChannel.getChannelFeed();
89 | assertEquals(TestChannelSettings.publicChannelID, publicFeed.getChannelId());
90 | assertNotNull(publicFeed.getChannelCreationDate());
91 | assertNotNull(publicFeed.getChannelDescription());
92 | assertNotNull(publicFeed.getChannelName());
93 | assertNotNull(publicFeed.getChannelUpdateDate());
94 | assertNotNull(publicFeed.getChannelLastEntryId());
95 | assertNotNull(publicFeed.getEntryList());
96 | assertNotNull(publicFeed.getEntryMap());
97 | assertNotNull(publicFeed.getFieldName(1));
98 | }
99 |
100 | @Test
101 | public void testGetChannelFeedPrivate() throws Exception {
102 | System.out.println("testGetChannelFeedPrivate");
103 | Channel publicChannel = new Channel(TestChannelSettings.privateChannelID, TestChannelSettings.privateChannelWriteKey, TestChannelSettings.privateChannelReadKey);
104 | publicChannel.setUrl(TestChannelSettings.server);
105 | Feed publicFeed = publicChannel.getChannelFeed();
106 | assertEquals(TestChannelSettings.privateChannelID, publicFeed.getChannelId());
107 | assertNotNull(publicFeed.getChannelCreationDate());
108 | assertNotNull(publicFeed.getChannelDescription());
109 | assertNotNull(publicFeed.getChannelName());
110 | assertNotNull(publicFeed.getChannelUpdateDate());
111 | assertNotNull(publicFeed.getChannelLastEntryId());
112 | assertNotNull(publicFeed.getEntry(publicFeed.getChannelLastEntryId()));
113 | assertNotNull(publicFeed.getEntryList());
114 | assertNotNull(publicFeed.getEntryMap());
115 | assertNotNull(publicFeed.getFieldName(1));
116 |
117 | Entry entry = publicChannel.getLastChannelEntry();
118 |
119 | /**
120 | * any one of these may fail if not defined in the Channel Settings
121 | * via the web.
122 | */
123 | assertNotNull(entry.getField(1));
124 | assertNotNull(entry.getField(2));
125 | assertNotNull(entry.getField(3));
126 | assertNotNull(entry.getField(4));
127 | assertNotNull(entry.getField(5));
128 | assertNotNull(entry.getField(6));
129 | assertNotNull(entry.getField(7));
130 | assertNotNull(entry.getField(8));
131 | }
132 |
133 | @Test
134 | public void testGetChannelFeedWithOptions() throws Exception {
135 | System.out.println("testGetChannelFeedWithOptions");
136 | Channel publicChannel = new Channel(TestChannelSettings.publicChannelID, TestChannelSettings.publicChannelWriteKey);
137 | publicChannel.setUrl(TestChannelSettings.server);
138 | FeedParameters options = new FeedParameters();
139 | options.location(true);
140 | options.status(true);
141 | Feed publicFeed = publicChannel.getChannelFeed(options);
142 | Entry entry = publicFeed.getChannelLastEntry();
143 | assertNotNull(entry.getStatus());
144 | assertNotNull(entry.getElevation());
145 | assertNotNull(entry.getLatitude());
146 | assertNotNull(entry.getLongitude());
147 | }
148 |
149 | @Test
150 | public void testGetLastEntry() throws Exception {
151 | System.out.println("testGetLastEntry");
152 | Channel publicChannel = new Channel(TestChannelSettings.publicChannelID, TestChannelSettings.publicChannelWriteKey);
153 | publicChannel.setUrl(TestChannelSettings.server);
154 | Entry entry = publicChannel.getLastChannelEntry();
155 |
156 | /**
157 | * any one of these may fail if not defined in the Channel Settings
158 | * via the web.
159 | */
160 | assertNotNull(entry.getField(1));
161 | assertNotNull(entry.getField(2));
162 | assertNotNull(entry.getField(3));
163 | assertNotNull(entry.getField(4));
164 | assertNotNull(entry.getField(5));
165 | assertNotNull(entry.getField(6));
166 | assertNotNull(entry.getField(7));
167 | assertNotNull(entry.getField(8));
168 | }
169 |
170 | @Test
171 | public void testGetLastEntryWithOptions() throws Exception {
172 | System.out.println("testGetLastEntryWithOptions");
173 | Channel publicChannel = new Channel(TestChannelSettings.publicChannelID, TestChannelSettings.publicChannelWriteKey);
174 | publicChannel.setUrl(TestChannelSettings.server);
175 | FeedParameters options = new FeedParameters();
176 | options.location(true);
177 | options.status(true);
178 | Entry entry = publicChannel.getLastChannelEntry(options);
179 | assertNotNull(entry.getStatus());
180 | assertNotNull(entry.getElevation());
181 | assertNotNull(entry.getLatitude());
182 | assertNotNull(entry.getLongitude());
183 | }
184 |
185 | @Test
186 | public void testGetLastEntryWithTimezoneOffset() throws Exception {
187 | System.out.println("testGetLastEntryWithTimezoneOffset");
188 | Channel channel = new Channel(TestChannelSettings.publicChannelID);
189 | channel.setUrl(TestChannelSettings.server);
190 | FeedParameters options = new FeedParameters();
191 | options.offset(-8);
192 | Date pst = channel.getLastChannelEntry(options).getCreated();
193 | SimpleDateFormat df = new SimpleDateFormat("Z");
194 | assert(df.format(pst).equals("-0800"));
195 | }
196 |
197 | @Test
198 | public void testGetFieldFeed() throws Exception {
199 | System.out.println("testGetFieldFeed");
200 | Channel publicChannel = new Channel(TestChannelSettings.publicChannelID);
201 | publicChannel.setUrl(TestChannelSettings.server);
202 | Feed feed = publicChannel.getFieldFeed(1);
203 | assertEquals(TestChannelSettings.publicChannelID, feed.getChannelId());
204 | assertNotNull(feed.getChannelCreationDate());
205 | assertNotNull(feed.getChannelDescription());
206 | assertNotNull(feed.getChannelName());
207 | assertNotNull(feed.getChannelUpdateDate());
208 | assertNotNull(feed.getChannelLastEntryId());
209 | assertNotNull(feed.getEntry(feed.getChannelLastEntryId()));
210 | assertNotNull(feed.getEntryList());
211 | assertNotNull(feed.getEntryMap());
212 | assertNotNull(feed.getFieldName(1));
213 |
214 | Entry entry = feed.getChannelLastEntry();
215 | assertNotNull(entry.getField(1));
216 | assertNull(entry.getField(2));
217 |
218 | }
219 |
220 | @Test
221 | public void testGetFieldFeedWithOptions() throws Exception {
222 | System.out.println("testGetFieldFeedWithOptions");
223 | Channel publicChannel = new Channel(TestChannelSettings.publicChannelID);
224 | publicChannel.setUrl(TestChannelSettings.server);
225 | FeedParameters options = new FeedParameters();
226 | options.status(true);
227 | options.location(true);
228 | Feed feed = publicChannel.getFieldFeed(1, options);
229 | Entry entry = feed.getChannelLastEntry();
230 | assertNotNull(entry.getField(1));
231 | assertNull(entry.getField(2));
232 | assertNotNull(entry.getStatus());
233 | assertNotNull(entry.getElevation());
234 | assertNotNull(entry.getLatitude());
235 | assertNotNull(entry.getLongitude());
236 | }
237 |
238 |
239 | @Test
240 | public void testGetFieldFeedLastEntry() throws Exception {
241 | System.out.println("testGetFieldFeedLastEntry");
242 | Channel publicChannel = new Channel(TestChannelSettings.publicChannelID);
243 | publicChannel.setUrl(TestChannelSettings.server);
244 | Entry entry = publicChannel.getLastFieldEntry(1);
245 | assertNotNull(entry.getField(1));
246 | assertNull(entry.getField(2));
247 | }
248 |
249 | @Test
250 | public void testGetFieldFeedLastEntryWithOptions() throws Exception {
251 | System.out.println("testGetFieldFeedLastEntryWithOptions");
252 | Channel publicChannel = new Channel(TestChannelSettings.publicChannelID);
253 | publicChannel.setUrl(TestChannelSettings.server);
254 | FeedParameters options = new FeedParameters();
255 | options.status(true);
256 | options.location(true);
257 | Entry entry = publicChannel.getLastFieldEntry(1, options);
258 | assertNotNull(entry.getField(1));
259 | assertNull(entry.getField(2));
260 | assertNotNull(entry.getStatus());
261 | assertNotNull(entry.getElevation());
262 | assertNotNull(entry.getLatitude());
263 | assertNotNull(entry.getLongitude());
264 | }
265 |
266 | @Test
267 | public void testGetStatus() throws Exception {
268 | System.out.println("testGetStatus");
269 | Channel channel = new Channel(TestChannelSettings.publicChannelID);
270 | channel.setUrl(TestChannelSettings.server);
271 | Feed feed = channel.getStatusFeed();
272 | ArrayList entry = feed.getEntryList();
273 | Entry last = entry.get(entry.size() -1);
274 | assertNotNull(last.getStatus());
275 | }
276 |
277 | @Test
278 | public void testGetStatusWithOptions() throws Exception {
279 | System.out.println("testGetStatusWithOptions");
280 | Channel channel = new Channel(TestChannelSettings.publicChannelID);
281 | channel.setUrl(TestChannelSettings.server);
282 | FeedParameters options = new FeedParameters();
283 | options.offset(-8);
284 | Feed feed = channel.getStatusFeed(options);
285 | ArrayList entry = feed.getEntryList();
286 | Entry last = entry.get(entry.size() -1);
287 | Date pst = last.getCreated();
288 | SimpleDateFormat df = new SimpleDateFormat("Z");
289 | assert(df.format(pst).equals("-0800"));
290 | }
291 |
292 | @Test
293 | public void testGetStatus_emptyFields() throws Exception {
294 | System.out.println("testGetStatusEmptyFields");
295 | Channel channel = new Channel(TestChannelSettings.publicChannelID);
296 | channel.setUrl(TestChannelSettings.server);
297 | Feed statusFeed = channel.getStatusFeed();
298 | assertNull(statusFeed.getChannelCreationDate());
299 | assertNull(statusFeed.getChannelDescription());
300 | assertNull(statusFeed.getChannelId());
301 | try {
302 | assertNull(statusFeed.getChannelLastEntry());
303 | } catch (ThingSpeakException ex) {
304 | /* this is expected */
305 | }
306 | assertNull(statusFeed.getChannelLastEntryId());
307 | assertNotNull(statusFeed.getChannelName());
308 | assertNull(statusFeed.getChannelUpdateDate());
309 | assertNull(statusFeed.getFieldName(1));
310 | for (Entry entry : statusFeed.getEntryList()) {
311 | assertNull(entry.getElevation());
312 | assertNull(entry.getField(1));
313 | assertNull(entry.getLatitude());
314 | assertNull(entry.getLongitude());
315 | }
316 |
317 | }
318 |
319 | }
320 |
--------------------------------------------------------------------------------
/test/com/angryelectron/thingspeak/TestChannelSettings.java:
--------------------------------------------------------------------------------
1 | /**
2 | * ThingSpeak Java Client
3 | * Copyright 2014, Andrew Bythell
4 | * http://angryelectron.com
5 | *
6 | * The ThingSpeak Java Client is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or (at your
9 | * option) any later version.
10 | *
11 | * The ThingSpeak Java Client is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14 | * Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License along with
17 | * theThingSpeak Java Client. If not, see .
18 | */
19 |
20 | package com.angryelectron.thingspeak;
21 |
22 | /**
23 | * Statics for accessing ThingSpeak channels used for Unit Testing. Before testing
24 | * you must create a private and a public channel on the server via the web
25 | * interface. Each channel must have a name, description, and all 8 fields defined.
26 | * The non-public channel must have an API Read Key generated. Enter the
27 | * server and channel information below.
28 | *
29 | * Also note that after 100 tests, data starts to be cached and tests may start
30 | * to fail. Clear all the feed data from the channel to avoid this problem.
31 | */
32 | public class TestChannelSettings {
33 |
34 | public static String server = "http://api.thingspeak.com";
35 | public static Integer publicChannelID = 9235;
36 | public static String publicChannelWriteKey = "8OBEPNQB06X9WDMZ";
37 | public static Integer privateChannelID = 9438;
38 | public static String privateChannelWriteKey = "AATBGE761SG6QE7J ";
39 | public static String privateChannelReadKey = "O7YHHVZMQSXRNJI2";
40 | public static Integer rateLimit = 16000;
41 |
42 | /*
43 | public static Integer publicChannelID = 1;
44 | public static String publicChannelWriteKey = "N6ES1RM97J3846NU ";
45 | public static Integer privateChannelID = 2;
46 | public static String privateChannelWriteKey = "UGN3RZYIVFKZLIHZ ";
47 | public static String privateChannelReadKey = "PIHR34GGPEKCOINR";
48 | public static Integer rateLimit = 0;
49 | public static String server = "http://192.168.2.190:3000";
50 | */
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/test/com/angryelectron/thingspeak/UpdateTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * ThingSpeak Java Client Copyright 2014, Andrew Bythell
3 | * http://angryelectron.com
4 | *
5 | * The ThingSpeak Java Client is free software: you can redistribute it and/or
6 | * modify it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or (at your
8 | * option) any later version.
9 | *
10 | * The ThingSpeak Java Client is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 | * Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License along with
16 | * theThingSpeak Java Client. If not, see .
17 | */
18 | package com.angryelectron.thingspeak;
19 |
20 | import org.apache.log4j.BasicConfigurator;
21 | import org.apache.log4j.Level;
22 | import org.apache.log4j.Logger;
23 | import org.junit.BeforeClass;
24 | import org.junit.Test;
25 |
26 | public class UpdateTest {
27 |
28 | @BeforeClass
29 | public static void setUpClass() throws Exception {
30 | BasicConfigurator.resetConfiguration();
31 | BasicConfigurator.configure();
32 | Logger.getLogger("org.apache.http").setLevel(Level.OFF);
33 | pauseForAPIRateLimit();
34 | }
35 |
36 | /**
37 | * Pause to prevent multiple update requests from exceeding the API rate
38 | * limit. Call all the end of each test to prevent subsequent tests from
39 | * failing. By appending to the end of each test instead of calling it using
40 | * \@After, time can be saved when running tests that throw exceptions and
41 | * don't actually do a successful update.
42 | *
43 | * @throws InterruptedException
44 | */
45 | private static void pauseForAPIRateLimit() throws InterruptedException {
46 | System.out.println("Waiting for rate limit to expire.");
47 | Thread.sleep(TestChannelSettings.rateLimit);
48 | }
49 |
50 | @Test
51 | public void testUpdateChannel() throws Exception {
52 | System.out.println("testUpdateChannel");
53 | Channel channel = new Channel(TestChannelSettings.publicChannelID, TestChannelSettings.publicChannelWriteKey);
54 | channel.setUrl(TestChannelSettings.server);
55 | Integer result = channel.update(new Entry());
56 | assert (result != 0);
57 | pauseForAPIRateLimit();
58 | }
59 |
60 | @Test(expected = ThingSpeakException.class)
61 | public void testUpdateChannelWithInvalidAPIKey() throws Exception {
62 | System.out.println("testUpdatePublicChannelWithInvalidAPIKey");
63 | Channel channel = new Channel(TestChannelSettings.publicChannelID, "invalidChannelKey");
64 | channel.setUrl(TestChannelSettings.server);
65 | try {
66 | channel.update(new Entry());
67 | } finally {
68 | pauseForAPIRateLimit();
69 | }
70 | }
71 |
72 | @Test
73 | public void testUpdateSlowerThanAPIRateLimit() throws Exception {
74 | System.out.println("testUpdateSlowerThanAPIRateLimit");
75 | /**
76 | * Do an update an make sure it succeeds.
77 | */
78 | Channel channel = new Channel(TestChannelSettings.publicChannelID, TestChannelSettings.publicChannelWriteKey);
79 | channel.setUrl(TestChannelSettings.server);
80 | Integer result0 = channel.update(new Entry());
81 | assert (result0 != 0);
82 |
83 | /**
84 | * Pause until the rate limit has passed.
85 | */
86 | Thread.sleep(TestChannelSettings.rateLimit);
87 |
88 | /**
89 | * Do another update and make sure it succeeds.
90 | */
91 | Integer result1 = channel.update(new Entry());
92 | assert (result1 != 0);
93 |
94 | pauseForAPIRateLimit();
95 | }
96 |
97 | @Test(expected = ThingSpeakException.class)
98 | public void testUpdateFasterThanAPIRateLimit() throws Exception {
99 | System.out.println("testUpdateFasterThanAPIRateLimit");
100 | /**
101 | * On self-hosted servers, there is no rate limiting
102 | */
103 | if (TestChannelSettings.rateLimit == 0) {
104 | throw new ThingSpeakException("Rate limiting not supported");
105 | }
106 |
107 | /**
108 | * Do an update and make sure it succeeds.
109 | */
110 | Channel channel = new Channel(TestChannelSettings.publicChannelID, TestChannelSettings.publicChannelWriteKey);
111 | channel.setUrl(TestChannelSettings.server);
112 | Integer result = channel.update(new Entry());
113 | assert (result != 0);
114 |
115 | /**
116 | * Don't wait for the rate limit to pass - send another update right
117 | * away. This should cause a ThingSpeakException.
118 | */
119 | try {
120 | channel.update(new Entry());
121 | } finally {
122 | pauseForAPIRateLimit();
123 | }
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/test/com/angryelectron/thingspeak/log4j/ThingSpeakAppenderTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * ThingSpeak Appender for log4j Copyright 2014, Andrew Bythell
3 | *
4 | * http://angryelectron.com
5 | *
6 | * The ThingSpeak Java Client is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or (at your
9 | * option) any later version.
10 | *
11 | * The ThingSpeak Java Client is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14 | * Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License along with
17 | * the ThingSpeak Appender. If not, see .
18 | */
19 |
20 | package com.angryelectron.thingspeak.log4j;
21 |
22 | import com.angryelectron.thingspeak.TestChannelSettings;
23 | import org.apache.log4j.BasicConfigurator;
24 | import org.apache.log4j.Level;
25 | import org.apache.log4j.Logger;
26 | import org.junit.BeforeClass;
27 | import org.junit.Test;
28 |
29 | /**
30 | * Test ThingSpeakAppender.
31 | */
32 | public class ThingSpeakAppenderTest {
33 |
34 | /**
35 | * Credentials for a test ThingSpeak channel.
36 | */
37 | private final static Integer channelNumber = 15662;
38 | private final static String apiWriteKey = "I5X9EPC34LPX1HRP";
39 |
40 | public ThingSpeakAppenderTest() {
41 | }
42 |
43 | @BeforeClass
44 | public static void setUpClass() throws Exception {
45 | BasicConfigurator.resetConfiguration();
46 | BasicConfigurator.configure();
47 | Logger.getLogger("org.apache.http").setLevel(Level.OFF);
48 | pauseForAPIRateLimit();
49 | }
50 |
51 | private static void pauseForAPIRateLimit() throws InterruptedException {
52 | System.out.println("Waiting for rate limit to expire.");
53 | Thread.sleep(TestChannelSettings.rateLimit);
54 | }
55 |
56 | /**
57 | * Test of configureChannel method, of class ThingSpeakAppender. To view the
58 | * logged data on ThingSpeak, visit https://thingspeak.com/channels/15662/feeds.
59 | * @throws java.lang.InterruptedException
60 | */
61 | @Test
62 | public void testAppend() throws InterruptedException {
63 | System.out.println("testAppend");
64 | ThingSpeakAppender appender = new ThingSpeakAppender();
65 | appender.configureChannel(channelNumber, apiWriteKey, null);
66 | appender.setThreshold(Level.INFO);
67 | appender.activateOptions();
68 | Logger.getRootLogger().addAppender(appender);
69 | Logger.getLogger(this.getClass()).log(Level.INFO, "Test message from ThingSpeakAppender");
70 | }
71 | }
72 |
--------------------------------------------------------------------------------