Skip to content

Commit 0c160e1

Browse files
authored
Merge pull request #157 from tobiasschuerg/feat/buckets
Feat: buckets management
2 parents 2359f0a + 4b1b70c commit 0c160e1

24 files changed

+1745
-487
lines changed

examples/Buckets/Buckets.ino

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/**
2+
* Buckets management Example code for InfluxDBClient library for Arduino
3+
* Enter WiFi and InfluxDB parameters below
4+
*
5+
* This example supports only InfluxDB running from unsecure (http://...)
6+
* For secure (https://...) or Influx Cloud 2 connection check SecureWrite example to
7+
* see how connect using secured connection (https)
8+
**/
9+
10+
#if defined(ESP32)
11+
#include <WiFiMulti.h>
12+
WiFiMulti wifiMulti;
13+
#define DEVICE "ESP32"
14+
#elif defined(ESP8266)
15+
#include <ESP8266WiFiMulti.h>
16+
ESP8266WiFiMulti wifiMulti;
17+
#define DEVICE "ESP8266"
18+
#endif
19+
20+
#include <InfluxDbClient.h>
21+
22+
// WiFi AP SSID
23+
#define WIFI_SSID "ssid"
24+
// WiFi password
25+
#define WIFI_PASSWORD "password"
26+
// InfluxDB server url. Don't use localhost, always server name or ip address.
27+
// E.g. http://192.168.1.48:8086 (In InfluxDB 2 UI -> Load Data -> Client Libraries),
28+
#define INFLUXDB_URL "influxdb-url"
29+
// InfluxDB 2 server or cloud API authentication token (Use: InfluxDB UI -> Load Data -> Tokens -> <select token>)
30+
// This token must have all buckets permission
31+
#define INFLUXDB_TOKEN "toked-id"
32+
// InfluxDB 2 organization id (Use: InfluxDB UI -> Settings -> Profile -> <name under tile> )
33+
#define INFLUXDB_ORG "org"
34+
// Bucket name that doesn't exist in the db yet
35+
#define INFLUXDB_BUCKET "test-bucket"
36+
37+
void setup() {
38+
Serial.begin(74880);
39+
40+
// Connect WiFi
41+
Serial.println("Connecting to " WIFI_SSID);
42+
WiFi.mode(WIFI_STA);
43+
wifiMulti.addAP(WIFI_SSID, WIFI_PASSWORD);
44+
while (wifiMulti.run() != WL_CONNECTED) {
45+
Serial.print(".");
46+
delay(500);
47+
}
48+
Serial.println();
49+
}
50+
51+
// Creates client, bucket, writes data, verifies data and deletes bucket
52+
void testClient() {
53+
// InfluxDB client instance
54+
InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN);
55+
56+
// Check server connection
57+
if (client.validateConnection()) {
58+
Serial.print("Connected to InfluxDB: ");
59+
Serial.println(client.getServerUrl());
60+
} else {
61+
Serial.print("InfluxDB connection failed: ");
62+
Serial.println(client.getLastErrorMessage());
63+
return;
64+
}
65+
66+
// Get dedicated client for buckets management
67+
BucketsClient buckets = client.getBucketsClient();
68+
69+
// Verify bucket does not exist, or delete it
70+
if(buckets.checkBucketExists(INFLUXDB_BUCKET)) {
71+
Serial.println("Bucket " INFLUXDB_BUCKET " already exists, deleting" );
72+
// get reference
73+
Bucket b = buckets.findBucket(INFLUXDB_BUCKET);
74+
// Delete bucket
75+
buckets.deleteBucket(b.getID());
76+
}
77+
78+
// create a bucket with retention policy one month. Leave out or set zero to infinity
79+
uint32_t monthSec = 30*24*3600;
80+
Bucket b = buckets.createBucket(INFLUXDB_BUCKET, monthSec);
81+
if(!b) {
82+
// some error occurred
83+
Serial.print("Bucket creating error: ");
84+
Serial.println(buckets.getLastErrorMessage());
85+
return;
86+
}
87+
Serial.print("Created bucket: ");
88+
Serial.println(b.toString());
89+
90+
int numPoints = 10;
91+
// Write some points
92+
for(int i=0;i<numPoints;i++) {
93+
Point point("test");
94+
point.addTag("device_name", DEVICE);
95+
point.addField("temperature", random(-20, 40) * 1.1f);
96+
point.addField("humidity", random(10, 90));
97+
if(!client.writePoint(point)) {
98+
Serial.print("Write error: ");
99+
Serial.println(client.getLastErrorMessage());
100+
}
101+
}
102+
// verify written points
103+
String query= "from(bucket: \"" INFLUXDB_BUCKET "\") |> range(start: -1h) |> pivot(rowKey:[\"_time\"],columnKey: [\"_field\"],valueColumn: \"_value\") |> count(column: \"humidity\")";
104+
FluxQueryResult result = client.query(query);
105+
// We expect one row
106+
if(result.next()) {
107+
// Get count value
108+
FluxValue val = result.getValueByName("humidity");
109+
if(val.getLong() != numPoints) {
110+
Serial.print("Test failure, expected ");
111+
Serial.print(numPoints);
112+
Serial.print(" got ");
113+
Serial.println(val.getLong());
114+
} else {
115+
Serial.println("Test successfull");
116+
}
117+
// Advance to the end
118+
result.next();
119+
} else {
120+
Serial.print("Query error: ");
121+
Serial.println(result.getError());
122+
};
123+
result.close();
124+
125+
buckets.deleteBucket(b.getID());
126+
}
127+
128+
void loop() {
129+
// Lets do an E2E test
130+
// call a client test
131+
testClient();
132+
133+
Serial.println("Stopping");
134+
// Stop here, don't loop
135+
while(1) delay(1);
136+
}

src/BucketsClient.cpp

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/**
2+
*
3+
* BucketsClient.cpp: InfluxDB Buckets Client
4+
*
5+
* MIT License
6+
*
7+
* Copyright (c) 2020 InfluxData
8+
*
9+
* Permission is hereby granted, free of charge, to any person obtaining a copy
10+
* of this software and associated documentation files (the "Software"), to deal
11+
* in the Software without restriction, including without limitation the rights
12+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
* copies of the Software, and to permit persons to whom the Software is
14+
* furnished to do so, subject to the following conditions:
15+
*
16+
* The above copyright notice and this permission notice shall be included in all
17+
* copies or substantial portions of the Software.
18+
*
19+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25+
* SOFTWARE.
26+
*/
27+
#include "BucketsClient.h"
28+
#include "util/helpers.h"
29+
30+
//#define INFLUXDB_CLIENT_DEBUG_ENABLE
31+
#include "util/debug.h"
32+
33+
static const char *propTemplate PROGMEM = "\"%s\":";
34+
// Finds first id property from JSON response
35+
enum class PropType {
36+
String,
37+
Number
38+
};
39+
40+
static String findProperty(const char *prop,const String &json, PropType type = PropType::String);
41+
42+
static String findProperty(const char *prop,const String &json, PropType type) {
43+
INFLUXDB_CLIENT_DEBUG("[D] Searching for %s in %s\n", prop, json.c_str());
44+
int propLen = strlen_P(propTemplate)+strlen(prop)-2;
45+
char *propSearch = new char[propLen+1];
46+
sprintf_P(propSearch, propTemplate, prop);
47+
int i = json.indexOf(propSearch);
48+
delete [] propSearch;
49+
if(i>-1) {
50+
INFLUXDB_CLIENT_DEBUG("[D] Found at %d\n", i);
51+
switch(type) {
52+
case PropType::String:
53+
i = json.indexOf("\"", i+propLen);
54+
if(i>-1) {
55+
INFLUXDB_CLIENT_DEBUG("[D] Found starting \" at %d\n", i);
56+
int e = json.indexOf("\"", i+1);
57+
if(e>-1) {
58+
INFLUXDB_CLIENT_DEBUG("[D] Found ending \" at %d\n", e);
59+
return json.substring(i+1, e);
60+
}
61+
}
62+
break;
63+
case PropType::Number:
64+
i = i+propLen;
65+
while(json[i] == ' ') {
66+
i++;
67+
}
68+
INFLUXDB_CLIENT_DEBUG("[D] Found beginning of number at %d\n", i);
69+
int e = json.indexOf(",", i+1);
70+
if(e>-1) {
71+
INFLUXDB_CLIENT_DEBUG("[D] Found , at %d\n", e);
72+
return json.substring(i, e);
73+
}
74+
break;
75+
}
76+
}
77+
return "";
78+
}
79+
80+
char *copyChars(const char *str) {
81+
char *ret = new char[strlen(str)+1];
82+
strcpy(ret, str);
83+
return ret;
84+
}
85+
86+
Bucket::Bucket():_data(nullptr) {
87+
}
88+
89+
Bucket::Bucket(const char *id, const char *name, const uint32_t expire) {
90+
_data = std::make_shared<Data>(id, name, expire);
91+
}
92+
93+
Bucket::Bucket(const Bucket &other) {
94+
_data = other._data;
95+
}
96+
97+
Bucket& Bucket::operator=(const Bucket& other) {
98+
if(this != &other) {
99+
_data = other._data;
100+
}
101+
return *this;
102+
}
103+
104+
Bucket::~Bucket() {
105+
}
106+
107+
108+
Bucket::Data::Data(const char *id, const char *name, const uint32_t expire) {
109+
this->id = copyChars(id);
110+
this->name = copyChars(name);
111+
this->expire = expire;
112+
}
113+
114+
Bucket::Data::~Data() {
115+
delete [] id;
116+
delete [] name;
117+
}
118+
119+
120+
const char *toStringTmplt PROGMEM = "Bucket: ID %s, Name %s, expire %u";
121+
String Bucket::toString() const {
122+
int len = strlen_P(toStringTmplt) + (_data?strlen(_data->name):0) + (_data?strlen(_data->id):0) + 10 + 1; //10 is maximum length of string representation of expire
123+
char *buff = new char[len];
124+
sprintf_P(buff, toStringTmplt, getID(), getName(), getExpire());
125+
String ret = buff;
126+
return ret;
127+
}
128+
129+
BucketsClient::BucketsClient() {
130+
_data = nullptr;
131+
}
132+
133+
BucketsClient::BucketsClient(ConnectionInfo *pConnInfo, HTTPService *service) {
134+
_data = std::make_shared<Data>(pConnInfo, service);
135+
}
136+
137+
BucketsClient::BucketsClient(const BucketsClient &other) {
138+
_data = other._data;
139+
}
140+
141+
BucketsClient &BucketsClient::operator=(const BucketsClient &other) {
142+
if(this != &other) {
143+
_data = other._data;
144+
}
145+
return *this;
146+
}
147+
148+
BucketsClient &BucketsClient::operator=(std::nullptr_t) {
149+
_data = nullptr;
150+
return *this;
151+
}
152+
153+
String BucketsClient::getOrgID(const char *org) {
154+
if(!_data) {
155+
return "";
156+
}
157+
if(isValidID(org)) {
158+
return org;
159+
}
160+
String url = _data->pService->getServerAPIURL();
161+
url += "orgs?org=";
162+
url += urlEncode(org);
163+
String id;
164+
INFLUXDB_CLIENT_DEBUG("[D] getOrgID: url %s\n", url.c_str());
165+
_data->pService->doGET(url.c_str(), 200, [&id](HTTPClient *client){
166+
id = findProperty("id",client->getString());
167+
return true;
168+
});
169+
return id;
170+
}
171+
172+
bool BucketsClient::checkBucketExists(const char *bucketName) {
173+
Bucket b = findBucket(bucketName);
174+
return !b.isNull();
175+
}
176+
177+
static const char *CreateBucketTemplate PROGMEM = "{\"name\":\"%s\",\"orgID\":\"%s\",\"retentionRules\":[{\"everySeconds\":%u}]}";
178+
179+
Bucket BucketsClient::createBucket(const char *bucketName, uint32_t expiresSec) {
180+
Bucket b;
181+
if(_data) {
182+
String orgID = getOrgID(_data->pConnInfo->org.c_str());
183+
184+
if(!orgID.length()) {
185+
return b;
186+
}
187+
int expireLen = 0;
188+
uint32_t e = expiresSec;
189+
do {
190+
expireLen++;
191+
e /=10;
192+
} while(e > 0);
193+
int len = strlen_P(CreateBucketTemplate) + strlen(bucketName) + orgID.length() + expireLen+1;
194+
char *body = new char[len];
195+
sprintf_P(body, CreateBucketTemplate, bucketName, orgID.c_str(), expiresSec);
196+
String url = _data->pService->getServerAPIURL();
197+
url += "buckets";
198+
INFLUXDB_CLIENT_DEBUG("[D] CreateBucket: url %s, body %s\n", url.c_str(), body);
199+
_data->pService->doPOST(url.c_str(), body, "application/json", 201, [&b](HTTPClient *client){
200+
String resp = client->getString();
201+
String id = findProperty("id", resp);
202+
String name = findProperty("name", resp);
203+
String expireStr = findProperty("everySeconds", resp, PropType::Number);
204+
uint32_t expire = strtoul(expireStr.c_str(), nullptr, 10);
205+
b = Bucket(id.c_str(), name.c_str(), expire);
206+
return true;
207+
});
208+
delete [] body;
209+
}
210+
return b;
211+
}
212+
213+
bool BucketsClient::deleteBucket(const char *id) {
214+
if(!_data) {
215+
216+
return false;
217+
}
218+
String url = _data->pService->getServerAPIURL();
219+
url += "buckets/";
220+
url += id;
221+
INFLUXDB_CLIENT_DEBUG("[D] deleteBucket: url %s\n", url.c_str());
222+
return _data->pService->doDELETE(url.c_str(), 204, nullptr);
223+
}
224+
225+
Bucket BucketsClient::findBucket(const char *bucketName) {
226+
Bucket b;
227+
if(_data) {
228+
String url = _data->pService->getServerAPIURL();
229+
url += "buckets?name=";
230+
url += urlEncode(bucketName);
231+
INFLUXDB_CLIENT_DEBUG("[D] findBucket: url %s\n", url.c_str());
232+
_data->pService->doGET(url.c_str(), 200, [&b](HTTPClient *client){
233+
String resp = client->getString();
234+
String id = findProperty("id", resp);
235+
if(id.length()) {
236+
String name = findProperty("name", resp);
237+
String expireStr = findProperty("everySeconds", resp, PropType::Number);
238+
uint32_t expire = strtoul(expireStr.c_str(), nullptr, 10);
239+
b = Bucket(id.c_str(), name.c_str(), expire);
240+
}
241+
return true;
242+
});
243+
}
244+
return b;
245+
}

0 commit comments

Comments
 (0)