├── LICENSE-2.0.txt ├── README.md ├── SnowPlowTracker.cpp ├── SnowPlowTracker.h ├── examples ├── basicPing │ └── basicPing.ino ├── doublePing │ └── doublePing.ino ├── floatPing │ └── floatPing.ino └── intPing │ └── intPing.ino └── keywords.txt /LICENSE-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Snowplow Analytics for Arduino 2 | 3 | ## Overview 4 | 5 | Add analytics to sketches on IP-connected Arduino boards with our event tracker for [Arduino] [arduino], our first event tracker for the [Internet of Things] [iot]. 6 | 7 | With this tracker you can collect sensor- and eventstream data from your Arduino-based projects and send it back to your [SnowPlow] [snowplow] collector. Some project ideas: 8 | 9 | * Track the movement of products around your shop/warehouse/factory using Arduino, [RFID readers] [arduino-rfid] and SnowPlow 10 | * Deploy a set of SnowPlow-connected Arduinos to monitor the environment (temperature, humidity, light levels etc) in your home 11 | * Send vehicle fleet information (locations, speeds, fuel levels etc) back to SnowPlow using Arduino's [3G and GPS] [3g-gps] shields 12 | 13 | For a real project using this tracker to log temperature sensor data to SnowPlow, see [arduino-temp-tracker] [arduino-temp-tracker]. 14 | 15 | ## Find out more 16 | 17 | | Technical Docs | Setup Guide | Roadmap & Contributing | 18 | |---------------------------------|---------------------------|--------------------------------------| 19 | | ![i1] [techdocs-image] | ![i2] [setup-image] | ![i3] [roadmap-image] | 20 | | **[Technical Docs] [techdocs]** | **[Setup Guide] [setup]** | _coming soon_ | 21 | 22 | ## Copyright and license 23 | 24 | The SnowPlow Arduino Tracker is copyright 2012-2013 SnowPlow Analytics Ltd. 25 | 26 | Licensed under the **[Apache License, Version 2.0] [license]** (the "License"); 27 | you may not use this software except in compliance with the License. 28 | 29 | Unless required by applicable law or agreed to in writing, software 30 | distributed under the License is distributed on an "AS IS" BASIS, 31 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32 | See the License for the specific language governing permissions and 33 | limitations under the License. 34 | 35 | [arduino]: http://arduino.cc/ 36 | [iot]: http://www.forbes.com/sites/ericsavitz/2013/01/14/ces-2013-the-break-out-year-for-the-internet-of-things/ 37 | [snowplow]: https://github.com/snowplow/snowplow 38 | [arduino-rfid]: http://arduino.cc/blog/category/wireless/rfid/ 39 | [3g-gps]: http://www.cooking-hacks.com/index.php/documentation/tutorials/arduino-3g-gprs-gsm-gps 40 | 41 | [arduino-temp-tracker]: https://github.com/alexanderdean/arduino-temp-tracker 42 | 43 | [techdocs-image]: https://d3i6fms1cm1j0i.cloudfront.net/github/images/techdocs.png 44 | [setup-image]: https://d3i6fms1cm1j0i.cloudfront.net/github/images/setup.png 45 | [roadmap-image]: https://d3i6fms1cm1j0i.cloudfront.net/github/images/roadmap.png 46 | [techdocs]: https://github.com/snowplow/snowplow/wiki/Arduino-Tracker 47 | [setup]: https://github.com/snowplow/snowplow/wiki/Arduino-Tracker-Setup 48 | 49 | [license]: http://www.apache.org/licenses/LICENSE-2.0 50 | -------------------------------------------------------------------------------- /SnowPlowTracker.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SnowPlow Arduino Tracker 3 | * 4 | * @description Arduino tracker for SnowPlow 5 | * @version 0.1.0 6 | * @author Alex Dean 7 | * @copyright SnowPlow Analytics Ltd 8 | * @license Apache License Version 2.0 9 | * 10 | * Copyright (c) 2012-2013 SnowPlow Analytics Ltd. All rights reserved. 11 | * 12 | * This program is licensed to you under the Apache License Version 2.0, 13 | * and you may not use this file except in compliance with the Apache License Version 2.0. 14 | * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. 15 | * 16 | * Unless required by applicable law or agreed to in writing, 17 | * software distributed under the Apache License Version 2.0 is distributed on an 18 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #define LOG_LEVEL 0x03 // Change to 0x00 when you've finished testing 29 | #include "SnowPlowTracker.h" 30 | 31 | // Initialize constants 32 | const char *SnowPlowTracker::kUserAgent = "Arduino/2.0"; 33 | const char *SnowPlowTracker::kTrackerPlatform = "iot"; // Internet of things 34 | const char *SnowPlowTracker::kTrackerVersion = "arduino-0.1.0"; 35 | 36 | /** 37 | * Constructor for the SnowPlowTracker 38 | * class. 39 | * 40 | * @param aEthernet Pointer to the 41 | * EthernetClass (initialised 42 | * outside of this library) 43 | * @param aMac The MAC address of the 44 | * Arduino's WiFi or Ethernet 45 | * Shield 46 | * @param aAppId The SnowPlow application 47 | * ID 48 | **/ 49 | SnowPlowTracker::SnowPlowTracker(EthernetClass *aEthernet, const byte* aMac, const char *aAppId) { 50 | this->ethernet = aEthernet; 51 | this->mac = (byte*)aMac; 52 | this->appId = (char*)aAppId; 53 | } 54 | 55 | /** 56 | * Initializes the SnowPlow tracker to 57 | * talk to a collector hosted on 58 | * CloudFront. 59 | * 60 | * @param aCfSubdomain The subdomain 61 | * of the CloudFront collector 62 | * e.g. "d3rkrsqgmqf" 63 | */ 64 | void SnowPlowTracker::initCf(const char *aCfSubdomain) { 65 | const size_t hostLength = strlen(aCfSubdomain) + 16; // .cloudfront.net\0 = 16 66 | char *host = (char*)malloc(hostLength); 67 | snprintf(host, hostLength, "%s.cloudfront.net", aCfSubdomain); 68 | this->init(host); 69 | } 70 | 71 | /** 72 | * Initializes the SnowPlow tracker 73 | * to speak to a URL-based (self- 74 | * hosted) collector. 75 | * 76 | * @param aHost The hostname of the 77 | * URL hosting the collector 78 | * e.g. tracking.mysite.com 79 | */ 80 | void SnowPlowTracker::initUrl(const char *aHost) { 81 | this->init(aHost); 82 | } 83 | 84 | /** 85 | * Sets the User Id. 86 | * 87 | * @param @aUserId The new User Id 88 | */ 89 | void SnowPlowTracker::setUserId(const char *aUserId) { 90 | this->userId = (char*)aUserId; 91 | 92 | LOG_INFO("SnowPlow user id updated to ["); 93 | LOG_INFO(aUserId); 94 | LOGLN_INFO("]"); 95 | } 96 | 97 | /** 98 | * Tracks a structured event to a 99 | * SnowPlow collector: version 100 | * where the value field is an int. 101 | * 102 | * @param aCategory The name you supply for 103 | * the group of objects you want to track 104 | * @param aAction A char *that is uniquely 105 | * paired with each category, and commonly 106 | * used to define the type of user 107 | * interaction for the web object 108 | * @param aLabel An optional string 109 | * to provide additional dimensions to the 110 | * event data 111 | * @param aProperty An optional string 112 | * describing the object or the action 113 | * performed on it. This might be the 114 | * quantity of an item added to basket 115 | * @param aValue An integer value that 116 | * you can use to provide numerical data 117 | * about the user event 118 | * @return An integer indicating the success/failure 119 | * of logging the event to SnowPlow 120 | */ 121 | int SnowPlowTracker::trackStructEvent( 122 | const char *aCategory, 123 | const char *aAction, 124 | const char *aLabel, 125 | const char *aProperty, 126 | const int aValue) const { 127 | 128 | char *value = int2Chars(aValue); 129 | const int status = this->_trackStructEvent(aCategory, aAction, aLabel, aProperty, value); 130 | free(value); 131 | return status; 132 | } 133 | 134 | /** 135 | * Tracks a structured event to a 136 | * SnowPlow collector: version 137 | * where the value field is a float. 138 | * 139 | * @param aCategory The name you supply for 140 | * the group of objects you want to track 141 | * @param aAction A char *that is uniquely 142 | * paired with each category, and commonly 143 | * used to define the type of user 144 | * interaction for the web object 145 | * @param aLabel An optional string 146 | * to provide additional dimensions to the 147 | * event data 148 | * @param aProperty An optional string 149 | * describing the object or the action 150 | * performed on it. This might be the 151 | * quantity of an item added to basket 152 | * @param aValue A double value that 153 | * you can use to provide numerical data 154 | * about the user event 155 | * @param aValuePrecision How many digits to keep 156 | * after the decimal sign 157 | * @return An integer indicating the success/failure 158 | * of logging the event to SnowPlow 159 | */ 160 | int SnowPlowTracker::trackStructEvent( 161 | const char *aCategory, 162 | const char *aAction, 163 | const char *aLabel, 164 | const char *aProperty, 165 | const double aValue, 166 | const int aValuePrecision) const { 167 | 168 | char *value = double2Chars(aValue, aValuePrecision); 169 | const int status = this->_trackStructEvent(aCategory, aAction, aLabel, aProperty, value); 170 | free(value); 171 | return status; 172 | } 173 | 174 | /** 175 | * Tracks a structured event to a 176 | * SnowPlow collector: version 177 | * where the value field is a float. 178 | * 179 | * @param aCategory The name you supply for 180 | * the group of objects you want to track 181 | * @param aAction A char *that is uniquely 182 | * paired with each category, and commonly 183 | * used to define the type of user 184 | * interaction for the web object 185 | * @param aLabel An optional string 186 | * to provide additional dimensions to the 187 | * event data 188 | * @param aProperty An optional string 189 | * describing the object or the action 190 | * performed on it. This might be the 191 | * quantity of an item added to basket 192 | * @param aValue A float value that 193 | * you can use to provide numerical data 194 | * about the user event 195 | * @param aValuePrecision How many digits to keep 196 | * after the decimal sign 197 | * @return An integer indicating the success/failure 198 | * of logging the event to SnowPlow 199 | */ 200 | int SnowPlowTracker::trackStructEvent( 201 | const char *aCategory, 202 | const char *aAction, 203 | const char *aLabel, 204 | const char *aProperty, 205 | const float aValue, 206 | const int aValuePrecision) const { 207 | 208 | char *value = double2Chars(aValue, aValuePrecision); 209 | const int status = this->_trackStructEvent(aCategory, aAction, aLabel, aProperty, value); 210 | free(value); 211 | return status; 212 | } 213 | 214 | /** 215 | * Tracks a structured event to a 216 | * SnowPlow collector: version 217 | * where there is no value field. 218 | * 219 | * @param aCategory The name you supply for 220 | * the group of objects you want to track 221 | * @param aAction A char *that is uniquely 222 | * paired with each category, and commonly 223 | * used to define the type of user 224 | * interaction for the web object 225 | * @param aLabel An optional string 226 | * to provide additional dimensions to the 227 | * event data 228 | * @param aProperty An optional string 229 | * describing the object or the action 230 | * performed on it. This might be the 231 | * quantity of an item added to basket 232 | 233 | * @return An integer indicating the success/failure 234 | * of logging the event to SnowPlow 235 | */ 236 | int SnowPlowTracker::trackStructEvent( 237 | const char *aCategory, 238 | const char *aAction, 239 | const char *aLabel, 240 | const char *aProperty) const { 241 | 242 | return this->_trackStructEvent(aCategory, aAction, aLabel, aProperty, NULL); 243 | } 244 | 245 | /** 246 | * Sends a structured event to a SnowPlow 247 | * collector. Builds the set of event 248 | * name=value pairs and then passes them 249 | * to the general-purpose track() to track 250 | * the event. 251 | * 252 | * @param aCategory The name you supply for 253 | * the group of objects you want to track 254 | * @param aAction A char *that is uniquely 255 | * paired with each category, and commonly 256 | * used to define the type of user 257 | * interaction for the web object 258 | * @param aLabel An optional string 259 | * to provide additional dimensions to the 260 | * event data 261 | * @param aProperty An optional string 262 | * describing the object or the action 263 | * performed on it. This might be the 264 | * quantity of an item added to basket 265 | * @param aValue A char *value that 266 | * you can use to provide non-numerical data 267 | * about the user event 268 | * @return An integer indicating the success/failure 269 | * of logging the event to SnowPlow 270 | */ 271 | int SnowPlowTracker::_trackStructEvent( 272 | const char *aCategory, 273 | const char *aAction, 274 | const char *aLabel, 275 | const char *aProperty, 276 | const char *aValue) const { 277 | 278 | LOG_INFO("Tracking structured event: category ["); 279 | LOG_INFO(aCategory); 280 | LOG_INFO("], action ["); 281 | LOG_INFO(aAction); 282 | LOG_INFO("], label ["); 283 | LOG_INFO(aLabel); 284 | LOG_INFO("], property ["); 285 | LOG_INFO(aProperty); 286 | LOG_INFO("], value ["); 287 | LOG_INFO(aValue); 288 | LOGLN_INFO("]"); 289 | 290 | // Validate that we have our category and action 291 | if (aCategory == NULL || aAction == NULL) { 292 | return SnowPlowTracker::ERROR_MISSING_ARGUMENT; 293 | } 294 | 295 | const QuerystringPair eventPairs[] = { 296 | { "e", "se" }, // Structured event 297 | { "ev_ca", (char*)aCategory }, 298 | { "ev_ac", (char*)aAction }, 299 | { "ev_la", (char*)aLabel }, 300 | { "ev_pr", (char*)aProperty }, 301 | { "ev_va", (char*)aValue }, 302 | { NULL, NULL } // Signals end of array 303 | }; 304 | 305 | const int status = this->track(eventPairs); 306 | return status; 307 | } 308 | 309 | /** 310 | * Common initialization, called by 311 | * both initCf and initUrl. 312 | * 313 | * @param aHost The hostname of the 314 | * URL hosting the collector 315 | * e.g. tracking.mysite.com 316 | * or d3rkrsqgmqf.cloudfront.net 317 | */ 318 | void SnowPlowTracker::init(const char *aHost) { 319 | 320 | // Set collectorHost and userId 321 | this->collectorHost = (char*)aHost; 322 | this->macAddress = mac2Chars(this->mac); 323 | 324 | // Boot the Ethernet connection 325 | this->ethernet->begin((byte*)this->mac); 326 | delay(1000); // Wait 1 sec 327 | this->client = new EthernetClient(); 328 | 329 | LOG_INFO("Ethernet booted with MAC address ["); 330 | LOG_INFO(this->macAddress); 331 | LOG_INFO("], local IP address ["); 332 | LOG_INFO(this->ethernet->localIP()); 333 | LOGLN_INFO("]"); 334 | 335 | LOG_INFO("SnowPlowTracker initialized with collector host ["); 336 | LOG_INFO(this->collectorHost); 337 | LOGLN_INFO("]"); 338 | } 339 | 340 | /** 341 | * A wrapper around getUri to send 342 | * an event to the SnowPlow collector 343 | * via a GET. 344 | * 345 | * @param aEventPairs the name-value 346 | * pairs specific to this event 347 | * to add to our GET 348 | * @return An integer indicating the 349 | * success/failure of logging 350 | * the event to SnowPlow 351 | */ 352 | int SnowPlowTracker::track(const QuerystringPair aEventPairs[]) const { 353 | 354 | char *txnId = this->getTransactionId(); 355 | 356 | const int fixedPairCount = 6; // Update this if more pairs added below. 357 | QuerystringPair qsPairs[fixedPairCount + this->kMaxEventPairs] = { 358 | { "tid", (char*)txnId }, 359 | { "p", (char*)this->kTrackerPlatform }, 360 | { "mac", (char*)this->macAddress }, 361 | { "uid", (char*)this->userId }, 362 | { "aid", (char*)this->appId }, 363 | { "tv", (char*)this->kTrackerVersion } 364 | }; 365 | 366 | const int eventPairCount = countPairs(aEventPairs); 367 | for (int i = 0; i < eventPairCount; i++) { 368 | qsPairs[fixedPairCount + i].name = aEventPairs[i].name; 369 | qsPairs[fixedPairCount + i].value = aEventPairs[i].value; 370 | } 371 | 372 | const int status = this->getUri(this->collectorHost, this->kCollectorPort, "/i", qsPairs); 373 | free(txnId); 374 | 375 | switch (status) { 376 | case ERROR_CONNECTION_FAILED: 377 | LOGLN_ERROR("Tracking returned ERROR_CONNECTION_FAILED"); 378 | break; 379 | case ERROR_TIMED_OUT: 380 | LOGLN_ERROR("Tracking returned ERROR_TIMED_OUT"); 381 | break; 382 | case ERROR_INVALID_RESPONSE: 383 | LOGLN_ERROR("Tracking returned ERROR_INVALID_RESPONSE"); 384 | break; 385 | default: 386 | LOG_INFO("Tracking returned HTTP Status Code: "); 387 | LOGLN_INFO(status); 388 | break; 389 | } 390 | return status; 391 | } 392 | 393 | /** 394 | * Returns the transaction ID for this 395 | * track event. Uses random(). 396 | * 397 | * IMPORTANT: be sure to free() the returned 398 | * string after use 399 | * 400 | * @return the transaction ID, effectively 401 | * millis() cast to an integer 402 | */ 403 | char *SnowPlowTracker::getTransactionId() { 404 | const int rndm = (int)random(INT_MAX); // Restrict to int range 405 | return int2Chars(rndm); 406 | } 407 | 408 | /** 409 | * Converts a MAC address byte array 410 | * into a String. Generated char *is 411 | * of the format: "00:01:0A:2E:05:0B" 412 | * 413 | * IMPORTANT: be sure to free() the returned 414 | * string after use 415 | * 416 | * @param aMac The MAC address, in bytes, 417 | * to convert 418 | * @return the MAC address as a String 419 | */ 420 | char *SnowPlowTracker::mac2Chars(const byte* aMac) { 421 | const size_t bufferLength = 18; // 17 chars plus \0 422 | char *buffer = (char*)malloc(bufferLength); 423 | snprintf(buffer, bufferLength, "%02X:%02X:%02X:%02X:%02X:%02X", 424 | aMac[0], 425 | aMac[1], 426 | aMac[2], 427 | aMac[3], 428 | aMac[4], 429 | aMac[5]); 430 | return buffer; 431 | } 432 | 433 | /** 434 | * Returns the length of an array 435 | * of QuerystringPairs. Assumes 436 | * the array ends with a sentinel 437 | * value of NULL. 438 | * 439 | * @param aPairs The QuerystringPairs 440 | * to count 441 | * @return the number of pairs in the 442 | * array 443 | */ 444 | int SnowPlowTracker::countPairs(const QuerystringPair aPairs[]) { 445 | int i = 1; 446 | while (aPairs[i].name != NULL) { 447 | i++; 448 | } 449 | return i; 450 | } 451 | 452 | /** 453 | * Converts an int into a stringified float. 454 | * 455 | * IMPORTANT: be sure to free() the returned 456 | * string after use 457 | * 458 | * @param aInt The integer to convert to 459 | * to a stringified float 460 | * @return the converted String 461 | */ 462 | // TODO: can't decide if adding ".0" on the end 463 | // should be the tracker's job or the ETL. 464 | char *SnowPlowTracker::int2Chars(const int aInt) { 465 | const size_t bufferLength = 14; // "-2147483648.0\0" 466 | char *buffer = (char*)malloc(bufferLength); 467 | snprintf(buffer, bufferLength, "%d.0", aInt); 468 | return buffer; 469 | } 470 | 471 | /** 472 | * Converts a double (or a float) 473 | * into a String. Generated char *is 474 | * 1 or more characters long, with the 475 | * number of digits after the decimal 476 | * point specified by `aPrecision`. 477 | * 478 | * IMPORTANT: be sure to free() the returned 479 | * string after use 480 | * 481 | * @param aDbl The double (or float) to 482 | * convert into a String 483 | * @return the converted String 484 | */ 485 | char *SnowPlowTracker::double2Chars(const double aDouble, const int aPrecision) { 486 | const size_t bufferLength = 25; 487 | char *buffer = (char*)malloc(bufferLength); 488 | dtostrf(aDouble, 1, aPrecision, buffer); 489 | return buffer; 490 | } 491 | 492 | /** 493 | * Converts an integer value to its 494 | * hex character 495 | * 496 | * @param aChar The character to hexify 497 | * @return the character in hex form 498 | */ 499 | char SnowPlowTracker::char2Hex(const char aChar) { 500 | static char hex[] = "0123456789abcdef"; 501 | return hex[aChar & 15]; 502 | } 503 | 504 | /** 505 | * URL-encodes a string. Using code 506 | * adapted from: 507 | * 508 | * http://www.geekhideout.com/urlcode.shtml 509 | * http://hardwarefun.com/tutorials/url-encoding-in-arduino 510 | * 511 | * IMPORTANT: be sure to free() the returned 512 | * string after use 513 | * 514 | * @param aStr The characters to URL-encode. 515 | * @return the encoded String 516 | */ 517 | char *SnowPlowTracker::urlEncode(const char* aStr) 518 | { 519 | const size_t bufferLength = strlen(aStr) * 3 + 1; 520 | char *pstr = (char*)aStr, *encodedStr = (char*)malloc(bufferLength), *pbuf = encodedStr; 521 | 522 | while (*pstr) { 523 | if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~') 524 | *pbuf++ = *pstr; 525 | else 526 | *pbuf++ = '%', *pbuf++ = char2Hex(*pstr >> 4), *pbuf++ = char2Hex(*pstr & 15); 527 | pstr++; 528 | } 529 | *pbuf = '\0'; // Tail it 530 | 531 | return encodedStr; 532 | } 533 | 534 | /** 535 | * Return the HTTP status code from this request. 536 | * 537 | * Parses a Status-Line like: 538 | * HTTP-Version SP Status-Code SP Reason-Phrase CRLF 539 | * Where HTTP-Version is of the form: 540 | * HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT 541 | * 542 | * Simplified from: 543 | * 544 | * https://github.com/amcewen/HttpClient/blob/master/HttpClient.cpp 545 | * https://github.com/exosite-garage/arduino_exosite_library/blob/master/Exosite.cpp 546 | * 547 | * @return the HTTP status code as an int 548 | */ 549 | int SnowPlowTracker::getResponseCode() const { 550 | int statusCode; 551 | HttpState httpState; 552 | 553 | char c = '\0'; 554 | do 555 | { 556 | // Make sure the status code is reset, and likewise the state. 557 | // Lets us easily cope with 1xx informational responses by just 558 | // ignoring them really, and reading the next line for a proper response 559 | statusCode = 0; 560 | httpState = eRequestSent; 561 | 562 | unsigned long timeoutStart = millis(); 563 | // Psuedo-regexp we're expecting before the status-code 564 | const char* statusPrefix = "HTTP/*.* "; 565 | const char* statusPtr = statusPrefix; 566 | 567 | // Whilst we haven't timed out & haven't reached the end of the headers 568 | while ((c != '\n') && ((millis() - timeoutStart) < this->kHttpResponseTimeout )) { 569 | if (this->client->available()) { 570 | c = this->client->read(); 571 | if (c != -1) { 572 | switch (httpState) { 573 | case eRequestSent: 574 | // We haven't reached the status code yet 575 | if ((*statusPtr == '*') || (*statusPtr == c)) { 576 | // This character matches, just move along 577 | statusPtr++; 578 | if (*statusPtr == '\0') { 579 | // We've reached the end of the prefix 580 | httpState = eReadingStatusCode; 581 | } 582 | } else { 583 | return SnowPlowTracker::ERROR_INVALID_RESPONSE; 584 | } 585 | break; 586 | case eReadingStatusCode: 587 | if (isdigit(c)) { 588 | // This assumes we won't get more than the 3 digits we want 589 | statusCode = statusCode*10 + (c - '0'); 590 | } else { 591 | // We've reached the end of the status code 592 | httpState = eStatusCodeRead; 593 | } 594 | break; 595 | case eStatusCodeRead: 596 | // We're just waiting for the end of the line now 597 | break; 598 | }; 599 | // We read something, reset the timeout counter 600 | timeoutStart = millis(); 601 | } 602 | } else { 603 | // No data available, so pause to allow some to arrive 604 | delay(this->kHttpWaitForDataDelay); 605 | } 606 | } 607 | 608 | // Have we reached the end of an informational status line? 609 | if ((c == '\n') && (statusCode < 200)) { 610 | c = '\0'; // Clear c so we'll go back into the data reading loop 611 | } 612 | 613 | // If we've read a status code successfully but it's informational (1xx) 614 | // loop back to the start 615 | } while ((httpState == eStatusCodeRead) && (statusCode < 200)); 616 | 617 | // Now return status code as appropriate 618 | if ((c == '\n') && (httpState == eStatusCodeRead)) { 619 | // We've read the status-line successfully, check if it's an error code 620 | if (statusCode < 400) { 621 | return statusCode; 622 | } else { 623 | return SnowPlowTracker::ERROR_HTTP_STATUS; 624 | } 625 | } else if (c != '\n') { 626 | // We must've timed out before we reached the end of the line 627 | return SnowPlowTracker::ERROR_TIMED_OUT; 628 | } 629 | 630 | // Fallback: not a properly formed status line, or not one we could understand 631 | return SnowPlowTracker::ERROR_INVALID_RESPONSE; 632 | } 633 | 634 | /** 635 | * Performs a GET against the 636 | * specified URI, passing in 637 | * the given parameters and 638 | * headers. 639 | * 640 | * @param aHost The hostname of 641 | * the URI to GET 642 | * @param aPort The port of the 643 | * URI to GET 644 | * @param aPath The path of the 645 | * URI to GET 646 | * @param aParameters The name- 647 | * value pairs to append 648 | * on the querystring 649 | * @return An integer indicating the 650 | * success/failure of logging 651 | * the event to SnowPlow 652 | */ 653 | int SnowPlowTracker::getUri( 654 | const char *aHost, 655 | const int aPort, 656 | const char *aPath, 657 | const QuerystringPair aPairs[]) const { 658 | 659 | // Connect to the host 660 | if (this->client->connect(aHost, aPort)) { 661 | // Build our GET line from: 662 | // 1. The URI path... 663 | this->client->print("GET "); 664 | LOG_DEBUG("GET "); 665 | this->client->print(aPath); 666 | LOG_DEBUG(aPath); 667 | 668 | // 2. The querychar *name-value pairs 669 | if (aPairs != NULL) { 670 | this->client->print("?"); 671 | LOG_DEBUG("?"); 672 | char idx = 0; 673 | QuerystringPair* pair = (QuerystringPair*)&aPairs[0]; 674 | // Loop for all pairs 675 | while (pair->name != NULL) { 676 | // Only add if value is not null 677 | if (pair->value != NULL) { 678 | if (idx > 0) { 679 | this->client->print("&"); 680 | LOG_DEBUG("&"); 681 | } 682 | 683 | this->client->print(pair->name); 684 | LOG_DEBUG(pair->name); 685 | 686 | this->client->print("="); 687 | LOG_DEBUG("="); 688 | 689 | char *encoded = urlEncode(pair->value); 690 | this->client->print(encoded); 691 | LOG_DEBUG(encoded); 692 | free(encoded); 693 | } 694 | pair = (QuerystringPair*)&aPairs[++idx]; 695 | } 696 | } 697 | 698 | // 3. Finish the GET definition 699 | this->client->println(" HTTP/1.1"); 700 | LOGLN_DEBUG(" HTTP/1.1"); 701 | 702 | // Headers 703 | this->client->print("Host: "); 704 | this->client->println(aHost); 705 | this->client->print("User-Agent: "); 706 | this->client->println(this->kUserAgent); 707 | this->client->println("Connection: close"); 708 | this->client->println(); 709 | // End of headers 710 | 711 | // Check and return HTTP status code value 712 | const int code = getResponseCode(); 713 | this->client->stop(); // Important: close the connection 714 | return code; 715 | } else { 716 | // Connection didn't work 717 | return SnowPlowTracker::ERROR_CONNECTION_FAILED; 718 | } 719 | } 720 | -------------------------------------------------------------------------------- /SnowPlowTracker.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SnowPlow Arduino Tracker 3 | * 4 | * @description Arduino tracker for SnowPlow 5 | * @version 0.1.0 6 | * @author Alex Dean 7 | * @copyright SnowPlow Analytics Ltd 8 | * @license Apache License Version 2.0 9 | * 10 | * Copyright (c) 2012-2013 SnowPlow Analytics Ltd. All rights reserved. 11 | * 12 | * This program is licensed to you under the Apache License Version 2.0, 13 | * and you may not use this file except in compliance with the Apache License Version 2.0. 14 | * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. 15 | * 16 | * Unless required by applicable law or agreed to in writing, 17 | * software distributed under the Apache License Version 2.0 is distributed on an 18 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. 20 | */ 21 | 22 | #ifndef SnowPlowTracker_h 23 | #define SnowPlowTracker_h 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | // Logging - adapted from https://github.com/dmcrodrigues/macro-logger 30 | #define NO_LOG 0x00 31 | #define ERROR_LEVEL 0x01 32 | #define INFO_LEVEL 0x02 33 | #define DEBUG_LEVEL 0x03 34 | 35 | #ifndef LOG_LEVEL 36 | #define LOG_LEVEL NO_LOG 37 | #endif 38 | 39 | #define SERIALPRINT(...) Serial.print(__VA_ARGS__) 40 | #define SERIALPRINTLN(...) Serial.println(__VA_ARGS__) 41 | 42 | #if LOG_LEVEL >= DEBUG_LEVEL 43 | #define LOG_DEBUG(...) SERIALPRINT(__VA_ARGS__) 44 | #define LOGLN_DEBUG(...) SERIALPRINTLN(__VA_ARGS__) 45 | #else 46 | #define LOG_DEBUG(...) 47 | #define LOGLN_DEBUG(...) 48 | #endif 49 | 50 | #if LOG_LEVEL >= INFO_LEVEL 51 | #define LOG_INFO(...) SERIALPRINT(__VA_ARGS__) 52 | #define LOGLN_INFO(...) SERIALPRINTLN(__VA_ARGS__) 53 | #else 54 | #define LOG_INFO(...) 55 | #define LOGLN_INFO(...) 56 | #endif 57 | 58 | #if LOG_LEVEL >= ERROR_LEVEL 59 | #define LOG_ERROR(...) SERIALPRINT(__VA_ARGS__) 60 | #define LOGLN_ERROR(...) SERIALPRINTLN(__VA_ARGS__) 61 | #else 62 | #define LOG_ERROR(...) 63 | #define LOGLN_ERROR(...) 64 | #endif 65 | 66 | /** 67 | * SnowPlowTracker encapsulates our Arduino 68 | * tracking code for SnowPlow. 69 | */ 70 | class SnowPlowTracker 71 | { 72 | public: 73 | 74 | // Our return values from tracking an event 75 | // Could not connect to the server 76 | static const int ERROR_CONNECTION_FAILED = -1; 77 | // Spent too long waiting for a reply 78 | static const int ERROR_TIMED_OUT = -2; 79 | // The response from the server is invalid 80 | static const int ERROR_INVALID_RESPONSE = -3; 81 | // Required fields (category & action) missing 82 | static const int ERROR_MISSING_ARGUMENT = -4; 83 | // We had a client or server HTTP error 84 | static const int ERROR_HTTP_STATUS = -5; 85 | 86 | // Constructor 87 | SnowPlowTracker(EthernetClass *aEthernet, const byte* aMac, const char *aAppId); 88 | 89 | // Initialisation options for the HTTP connection 90 | void initCf(const char *aCfSubdomain); 91 | void initUrl(const char *aHost); 92 | 93 | // Manually set the 'user' ID 94 | void setUserId(const char *aUserId); 95 | 96 | // Track structured SnowPlow events 97 | int trackStructEvent(const char *aCategory, const char *aAction, const char *aLabel = NULL, const char *aProperty = NULL) const; 98 | int trackStructEvent(const char *aCategory, const char *aAction, const char *aLabel, const char *aProperty, const int aValue) const; 99 | int trackStructEvent(const char *aCategory, const char *aAction, const char *aLabel, const char *aProperty, const double aValue, const int aValuePrecision = 2) const; 100 | int trackStructEvent(const char *aCategory, const char *aAction, const char *aLabel, const char *aProperty, const float aValue, const int aValuePrecision = 2) const; 101 | 102 | private: 103 | static const char *kUserAgent; 104 | static const char *kTrackerPlatform; 105 | static const char *kTrackerVersion; 106 | static const int kCollectorPort = 80; // Default port 107 | static const int kMaxEventPairs = 7; // 6 fields plus trailing NULL indicator 108 | static const int kHttpResponseTimeout = 15*1000; // ms to wait before sending timeout 109 | static const int kHttpWaitForDataDelay = 750; // ms to wait each time there's no data available 110 | 111 | // Not possible to call _trackStructEvent directly (because aValue can't be any string) 112 | int _trackStructEvent(const char *aCategory, const char *aAction, const char *aLabel, const char *aProperty, const char *aValue) const; 113 | 114 | // Struct to hold a querychar *name-value pair 115 | typedef struct 116 | { 117 | char* name; 118 | char* value; 119 | } QuerystringPair; 120 | 121 | // To track different HTTP statuses 122 | typedef enum { 123 | eIdle, 124 | eRequestStarted, 125 | eRequestSent, 126 | eReadingStatusCode, 127 | eStatusCodeRead, 128 | eReadingContentLength, 129 | eSkipToEndOfHeader, 130 | eLineStartingCRFound, 131 | eReadingBody 132 | } HttpState; 133 | 134 | class EthernetClass* ethernet; 135 | class EthernetClient* client; 136 | 137 | byte* mac; 138 | char *appId; 139 | char *collectorHost; 140 | char *macAddress; 141 | char *userId; 142 | 143 | void init(const char *aHost); 144 | int track(const QuerystringPair aEventPairs[]) const; 145 | int getUri(const char *aHost, const int aPort, const char *aPath, const QuerystringPair aPairs[]) const; 146 | int getResponseCode() const; 147 | 148 | static char *getTransactionId(); 149 | static char *mac2Chars(const byte* aMac); 150 | static char *int2Chars(const int aInt); 151 | static char *double2Chars(const double aDbl, const int aPrecision); 152 | static char char2Hex(const char aChar); 153 | static int countPairs(const QuerystringPair aPairs[]); 154 | static char *urlEncode(const char* aStr); 155 | }; 156 | 157 | #endif 158 | -------------------------------------------------------------------------------- /examples/basicPing/basicPing.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * SnowPlow Arduino Tracker: Basic Ping Example 3 | * 4 | * @description Basic ping example for SnowPlow Arduino Tracker 5 | * @version 0.0.1 6 | * @author Alex Dean 7 | * @copyright SnowPlow Analytics Ltd 8 | * @license Apache License Version 2.0 9 | * 10 | * Copyright (c) 2012 SnowPlow Analytics Ltd. All rights reserved. 11 | * 12 | * This program is licensed to you under the Apache License Version 2.0, 13 | * and you may not use this file except in compliance with the Apache License Version 2.0. 14 | * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. 15 | * 16 | * Unless required by applicable law or agreed to in writing, 17 | * software distributed under the Apache License Version 2.0 is distributed on an 18 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | // MAC address of this Arduino. Update with your shield's MAC address. 27 | const byte mac[] = { 0x90, 0xA2, 0xDA, 0x00, 0xF8, 0xA0 }; 28 | 29 | // SnowPlow CloudFront collector subdomain. Update with your collector. 30 | const char *snowplowCfSubdomain = "d3rkrsqld9gmqf"; 31 | 32 | // SnowPlow app name 33 | const char *snowplowAppName = "arduino-ping-examples"; 34 | 35 | // SnowPlow Tracker 36 | SnowPlowTracker snowplow(&Ethernet, mac, snowplowAppName); 37 | 38 | /* 39 | * setup() runs once when you turn your 40 | * Arduino on: use it to initialize and 41 | * set any initial values. 42 | * 43 | * We just initialize the serial 44 | * connection (for debugging) and 45 | * the SnowPlow tracker. 46 | */ 47 | void setup() 48 | { 49 | // Serial connection lets us debug on the computer 50 | Serial.begin(9600); 51 | 52 | // Setup SnowPlow Arduino tracker 53 | snowplow.initCf(snowplowCfSubdomain); 54 | snowplow.setUserId("my-arduino"); 55 | } 56 | 57 | /* 58 | * loop() runs over and over again. 59 | * An empty loop() takes just a few 60 | * clock cycles to complete. 61 | * 62 | * Every 15 seconds, send a 'ping' 63 | * event to SnowPlow. 64 | */ 65 | void loop() 66 | { 67 | // When did we run last? 68 | static unsigned long prevTime = 0; 69 | 70 | if (millis() - prevTime >= (15000)) 71 | { 72 | // Basic ping: label, property, value all NULL 73 | snowplow.trackStructEvent("example", "basic ping"); 74 | 75 | prevTime = millis(); 76 | } 77 | 78 | delay(500); // Running loop twice a sec is fine 79 | } 80 | -------------------------------------------------------------------------------- /examples/doublePing/doublePing.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * SnowPlow Arduino Tracker: Double Ping Example 3 | * 4 | * @description Double ping example for SnowPlow Arduino Tracker 5 | * @version 0.0.1 6 | * @author Alex Dean 7 | * @copyright SnowPlow Analytics Ltd 8 | * @license Apache License Version 2.0 9 | * 10 | * Copyright (c) 2012 SnowPlow Analytics Ltd. All rights reserved. 11 | * 12 | * This program is licensed to you under the Apache License Version 2.0, 13 | * and you may not use this file except in compliance with the Apache License Version 2.0. 14 | * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. 15 | * 16 | * Unless required by applicable law or agreed to in writing, 17 | * software distributed under the Apache License Version 2.0 is distributed on an 18 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | // MAC address of this Arduino. Update with your shield's MAC address. 27 | const byte mac[] = { 0x90, 0xA2, 0xDA, 0x00, 0xF8, 0xA0 }; 28 | 29 | // SnowPlow CloudFront collector subdomain. Update with your collector. 30 | const char *snowplowCfSubdomain = "d3rkrsqld9gmqf"; 31 | 32 | // SnowPlow app name 33 | const char *snowplowAppName = "arduino-ping-examples"; 34 | 35 | // SnowPlow Tracker 36 | SnowPlowTracker snowplow(&Ethernet, mac, snowplowAppName); 37 | 38 | /* 39 | * setup() runs once when you turn your 40 | * Arduino on: use it to initialize and 41 | * set any initial values. 42 | * 43 | * We just initialize the serial 44 | * connection (for debugging) and 45 | * the SnowPlow tracker. 46 | */ 47 | void setup() 48 | { 49 | // Serial connection lets us debug on the computer 50 | Serial.begin(9600); 51 | 52 | // Setup SnowPlow Arduino tracker 53 | snowplow.initCf(snowplowCfSubdomain); 54 | snowplow.setUserId("my-arduino"); 55 | } 56 | 57 | /* 58 | * loop() runs over and over again. 59 | * An empty loop() takes just a few 60 | * clock cycles to complete. 61 | * 62 | * Every 15 seconds, send a 'ping' 63 | * event to SnowPlow. 64 | */ 65 | void loop() 66 | { 67 | // When did we run last? 68 | static unsigned long prevTime = 0; 69 | 70 | if (millis() - prevTime >= (15000)) 71 | { 72 | // Float ping: all fields but label set 73 | snowplow.trackStructEvent("example", "double ping", NULL, "pi", 3.14159, 5); 74 | 75 | prevTime = millis(); 76 | } 77 | 78 | delay(500); // Running loop twice a sec is fine 79 | } 80 | -------------------------------------------------------------------------------- /examples/floatPing/floatPing.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * SnowPlow Arduino Tracker: Float Ping Example 3 | * 4 | * @description Float ping example for SnowPlow Arduino Tracker 5 | * @version 0.0.1 6 | * @author Alex Dean 7 | * @copyright SnowPlow Analytics Ltd 8 | * @license Apache License Version 2.0 9 | * 10 | * Copyright (c) 2012 SnowPlow Analytics Ltd. All rights reserved. 11 | * 12 | * This program is licensed to you under the Apache License Version 2.0, 13 | * and you may not use this file except in compliance with the Apache License Version 2.0. 14 | * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. 15 | * 16 | * Unless required by applicable law or agreed to in writing, 17 | * software distributed under the Apache License Version 2.0 is distributed on an 18 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | // MAC address of this Arduino. Update with your shield's MAC address. 27 | const byte mac[] = { 0x90, 0xA2, 0xDA, 0x00, 0xF8, 0xA0 }; 28 | 29 | // SnowPlow CloudFront collector subdomain. Update with your collector. 30 | const char *snowplowCfSubdomain = "d3rkrsqld9gmqf"; 31 | 32 | // SnowPlow app name 33 | const char *snowplowAppName = "arduino-ping-examples"; 34 | 35 | // SnowPlow Tracker 36 | SnowPlowTracker snowplow(&Ethernet, mac, snowplowAppName); 37 | 38 | /* 39 | * setup() runs once when you turn your 40 | * Arduino on: use it to initialize and 41 | * set any initial values. 42 | * 43 | * We just initialize the serial 44 | * connection (for debugging) and 45 | * the SnowPlow tracker. 46 | */ 47 | void setup() 48 | { 49 | // Serial connection lets us debug on the computer 50 | Serial.begin(9600); 51 | 52 | // Setup SnowPlow Arduino tracker 53 | snowplow.initCf(snowplowCfSubdomain); 54 | snowplow.setUserId("my-arduino"); 55 | } 56 | 57 | /* 58 | * loop() runs over and over again. 59 | * An empty loop() takes just a few 60 | * clock cycles to complete. 61 | * 62 | * Every 15 seconds, send a 'ping' 63 | * event to SnowPlow. 64 | */ 65 | void loop() 66 | { 67 | // When did we run last? 68 | static unsigned long prevTime = 0; 69 | 70 | if (millis() - prevTime >= (15000)) 71 | { 72 | // Float ping: all fields but label set 73 | snowplow.trackStructEvent("example", "float ping", NULL, "celsius", 15.3f, 1); 74 | 75 | prevTime = millis(); 76 | } 77 | 78 | delay(500); // Running loop twice a sec is fine 79 | } 80 | -------------------------------------------------------------------------------- /examples/intPing/intPing.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * SnowPlow Arduino Tracker: Integer Ping Example 3 | * 4 | * @description Integer ping example for SnowPlow Arduino Tracker 5 | * @version 0.0.1 6 | * @author Alex Dean 7 | * @copyright SnowPlow Analytics Ltd 8 | * @license Apache License Version 2.0 9 | * 10 | * Copyright (c) 2012 SnowPlow Analytics Ltd. All rights reserved. 11 | * 12 | * This program is licensed to you under the Apache License Version 2.0, 13 | * and you may not use this file except in compliance with the Apache License Version 2.0. 14 | * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. 15 | * 16 | * Unless required by applicable law or agreed to in writing, 17 | * software distributed under the Apache License Version 2.0 is distributed on an 18 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | // MAC address of this Arduino. Update with your shield's MAC address. 27 | const byte mac[] = { 0x90, 0xA2, 0xDA, 0x00, 0xF8, 0xA0 }; 28 | 29 | // SnowPlow CloudFront collector subdomain. Update with your collector. 30 | const char *snowplowCfSubdomain = "d3rkrsqld9gmqf"; 31 | 32 | // SnowPlow app name 33 | const char *snowplowAppName = "arduino-ping-examples"; 34 | 35 | // SnowPlow Tracker 36 | SnowPlowTracker snowplow(&Ethernet, mac, snowplowAppName); 37 | 38 | /* 39 | * setup() runs once when you turn your 40 | * Arduino on: use it to initialize and 41 | * set any initial values. 42 | * 43 | * We just initialize the serial 44 | * connection (for debugging) and 45 | * the SnowPlow tracker. 46 | */ 47 | void setup() 48 | { 49 | // Serial connection lets us debug on the computer 50 | Serial.begin(9600); 51 | 52 | // Setup SnowPlow Arduino tracker 53 | snowplow.initCf(snowplowCfSubdomain); 54 | snowplow.setUserId("my-arduino"); 55 | } 56 | 57 | /* 58 | * loop() runs over and over again. 59 | * An empty loop() takes just a few 60 | * clock cycles to complete. 61 | * 62 | * Every 15 seconds, send a 'ping' 63 | * event to SnowPlow. 64 | */ 65 | void loop() 66 | { 67 | // When did we run last? 68 | static unsigned long prevTime = 0; 69 | 70 | if (millis() - prevTime >= (15000)) 71 | { 72 | // Int ping: all fields but property set 73 | snowplow.trackStructEvent("example", "int ping", "age", NULL, 22); 74 | 75 | prevTime = millis(); 76 | } 77 | 78 | delay(500); // Running loop twice a sec is fine 79 | } 80 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For SnowPlowTracker 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | SnowPlowTracker KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions (KEYWORD2) 13 | ####################################### 14 | 15 | initCf KEYWORD2 16 | initUrl KEYWORD2 17 | setUserId KEYWORD2 18 | trackStructEvent KEYWORD2 19 | 20 | ####################################### 21 | # Constants (LITERAL1) 22 | ####################################### 23 | 24 | ERROR_CONNECTION_FAILED LITERAL1 25 | ERROR_TIMED_OUT LITERAL1 26 | ERROR_INVALID_RESPONSE LITERAL1 27 | ERROR_MISSING_ARGUMENT LITERAL1 28 | ERROR_HTTP_STATUS LITERAL1 --------------------------------------------------------------------------------