Skip to content

Commit d47df3d

Browse files
authored
Merge pull request #77 from bonitoo-io/fix/timestamp
fix: better default timestamp generating
2 parents 6e47386 + fc14731 commit d47df3d

File tree

8 files changed

+118
-38
lines changed

8 files changed

+118
-38
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# Changelog
2+
23
## Version 3.3.0 (in progress)
34
- [NEW] Added possibility skip server certification validation (`setInsecure()` method)
4-
- [NEW] Added possibility to query flux on InfuxDB 1.8 using V1 approach
5+
- [NEW] Added possibility to query flux on secured InfuxDB 1.8 using V1 approach
56
- [NEW] `validateConnection()` can be used also for the [forward compatibility](https://docs.influxdata.com/influxdb/latest/tools/api/#influxdb-2-0-api-compatibility-endpoints) connection to InfluxDB 1.8
7+
- [FIX] More precice default timestamp generating, up to microseconds
68
- [FIX] Debug compilation error
79
- [FIX] SecureBatchWrite compile error
810

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ InfluxDB client for Arduino can write data in batches. A batch is simply a set o
153153
If using batch writes, the timestamp should be employed. Timestamp specifies the time where data was gathered and it is used in the form of a number of seconds (milliseconds, etc) from epoch (1.1.1970) UTC.
154154
If points have no timestamp assigned, InfluxDB assigns timestamp at the time of writing, which could happen much later than the data has been obtained, because final batch write will happen when the batch is full (or when [flush buffer](#buffer-handling-and-retrying) is forced).
155155
156-
InfuxDB allows sending timestamp in various precisions - nanoseconds, microseconds, milliseconds or seconds. The milliseconds precision is usually enough for using on Arduino.
156+
InfuxDB allows sending timestamp in various precisions - nanoseconds, microseconds, milliseconds or seconds. The milliseconds precision is usually enough for using on Arduino. Maximum avavailable precision is microseconds. Setting to nanosecond will just add zeroes for microseconds fraction.
157157
158158
The client has to be configured with time precision. The default settings is not using the timestamp. The `setWriteOptions` functions allow setting various parameters and one of them is __write precision__:
159159
``` cpp
@@ -164,8 +164,10 @@ When a write precision is configured, the client will automatically assign curre
164164

165165
If you want to manage timestamp on your own, there are several ways how to set timestamp explicitly.
166166
- `setTime(WritePrecision writePrecision)` - Sets timestamp to actual time in desired precision
167-
- `setTime(unsigned long seconds)` - Sets timestamp in seconds since epoch. Write precision must be set to `S`
168-
- `setTime(String timestamp)` - Set custom timestamp in precision specified in InfluxDBClient.
167+
- `setTime(unsigned long long timestamp)` - Sets timestamp in an offset since epoch. Correct precision must be set InfluxDBClient::setWriteOptions.
168+
- `setTime(String timestamp)` - Sets timestamp in an offset since epoch. Correct precision must be set InfluxDBClient::setWriteOptions.
169+
170+
The `getTime()` method allows copying timestamp between points.
169171

170172

171173
### Configure Time

src/InfluxDbClient.cpp

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -112,32 +112,30 @@ String Point::toLineProtocol() const {
112112
}
113113

114114
void Point::setTime(WritePrecision precision) {
115-
static char buff[10];
116-
time_t now = time(nullptr);
115+
struct timeval tv;
116+
gettimeofday(&tv, NULL);
117+
117118
switch(precision) {
118119
case WritePrecision::NS:
119-
sprintf(buff, "%06d000", micros()%1000000uL);
120-
_timestamp = String(now) + buff;
120+
setTime(getTimeStamp(&tv,9));
121121
break;
122122
case WritePrecision::US:
123-
sprintf(buff, "%06d", micros()%1000000uL);
124-
_timestamp = String(now) + buff;
123+
setTime(getTimeStamp(&tv,6));
125124
break;
126-
case WritePrecision::MS:
127-
sprintf(buff, "%03d", millis()%1000u);
128-
_timestamp = String(now) + buff;
125+
case WritePrecision::MS:
126+
setTime(getTimeStamp(&tv,3));
127+
break;
128+
case WritePrecision::S:
129+
setTime(getTimeStamp(&tv,0));
129130
break;
130131
case WritePrecision::NoTime:
131132
_timestamp = "";
132133
break;
133-
case WritePrecision::S:
134-
_timestamp = String(now);
135-
break;
136134
}
137135
}
138136

139-
void Point::setTime(unsigned long timestamp) {
140-
_timestamp = String(timestamp);
137+
void Point::setTime(unsigned long long timestamp) {
138+
_timestamp = timeStampToString(timestamp);
141139
}
142140

143141
void Point::setTime(String timestamp) {

src/InfluxDbClient.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ class Point {
8484
void addField(String name, const char *value);
8585
// Set timestamp to `now()` and store it in specified precision, nanoseconds by default. Date and time must be already set. See `configTime` in the device API
8686
void setTime(WritePrecision writePrecision = WritePrecision::NS);
87-
// Set timestamp in seconds since epoch (1.1.1970). Precision should be set to `S`
88-
void setTime(unsigned long seconds);
89-
// Set timestamp in desired precision (specified in InfluxDBClient) since epoch (1.1.1970 00:00:00)
87+
// Set timestamp in offset since epoch (1.1.1970). Correct precision must be set InfluxDBClient::setWriteOptions.
88+
void setTime(unsigned long long timestamp);
89+
// Set timestamp in offset since epoch (1.1.1970 00:00:00). Correct precision must be set InfluxDBClient::setWriteOptions.
9090
void setTime(String timestamp);
9191
// Clear all fields. Usefull for reusing point
9292
void clearFields();
@@ -96,10 +96,12 @@ class Point {
9696
bool hasFields() const { return _fields.length() > 0; }
9797
// True if a point contains at least one tag
9898
bool hasTags() const { return _tags.length() > 0; }
99-
// True if a point contains timestamp
99+
// True if a point contains timestamp
100100
bool hasTime() const { return _timestamp.length() > 0; }
101101
// Creates line protocol
102102
String toLineProtocol() const;
103+
// returns current timestamp
104+
String getTime() const { return _timestamp; }
103105
protected:
104106
String _measurement;
105107
String _tags;

src/util/helpers.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,33 @@ void timeSync(const char *tzInfo, const char* ntpServer1, const char* ntpServer2
4747
Serial.println(ctime(&tnow));
4848
}
4949

50+
unsigned long long getTimeStamp(struct timeval *tv, int secFracDigits) {
51+
unsigned long long tsVal = 0;
52+
switch(secFracDigits) {
53+
case 0:
54+
tsVal = tv->tv_sec;
55+
break;
56+
case 6:
57+
tsVal = tv->tv_sec * 1000000LL + tv->tv_usec;
58+
break;
59+
case 9:
60+
tsVal = tv->tv_sec * 1000000000LL + tv->tv_usec * 1000LL;
61+
break;
62+
case 3:
63+
default:
64+
tsVal = tv->tv_sec * 1000LL + tv->tv_usec / 1000LL;
65+
break;
66+
67+
}
68+
return tsVal;
69+
}
70+
71+
String timeStampToString(unsigned long long timestamp) {
72+
static char buff[50];
73+
snprintf(buff, 50, "%llu", timestamp);
74+
return String(buff);
75+
}
76+
5077
String escapeKey(String key, bool escapeEqual) {
5178
String ret;
5279
ret.reserve(key.length()+5); //5 is estimate of chars needs to escape,

src/util/helpers.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,22 @@
2828
#define _INFLUXDB_CLIENT_HELPERS_H
2929

3030
#include <Arduino.h>
31+
#include <sys/time.h>
3132

3233
// Synchronize time with NTP servers and waits for completition. Prints waiting progress and final synchronized time to the serial.
3334
// Accurate time is necessary for certificate validion and writing points in batch
3435
// For the fastest time sync find NTP servers in your area: https://www.pool.ntp.org/zone/
3536
void timeSync(const char *tzInfo, const char* ntpServer1, const char* ntpServer2 = nullptr, const char* ntpServer3 = nullptr);
3637

38+
// Create timestamp in offset from epoch. secFracDigits specify resulution. 0 - seconds, 3 - milliseconds, etc. Maximum and default is 9 - nanoseconds.
39+
unsigned long long getTimeStamp(struct timeval *tv, int secFracDigits = 3);
40+
41+
// Converts unsigned long long timestamp to String
42+
String timeStampToString(unsigned long long timestamp);
43+
3744
// Escape invalid chars in measurement, tag key, tag value and field key
3845
String escapeKey(String key, bool escapeEqual = true);
46+
3947
// Escape invalid chars in field value
4048
String escapeValue(const char *value);
4149

test/TestSupport.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
#ifndef _TEST_SUPPORT_H_
22
#define _TEST_SUPPORT_H_
33

4-
#define TEST_INIT(name) int temp = failures; const char *testName = name; do { Serial.println(testName)
5-
#define TEST_END() } while(0); Serial.printf("%s %s\n",testName,failures == temp?"SUCCEEDED":"FAILED")
6-
#define TEST_ASSERT(a) if(testAssert(__LINE__, (a))) break
7-
#define TEST_ASSERTM(a,m) if(testAssertm(__LINE__, (a),(m))) break
4+
#define TEST_INIT(name) int temp = failures;\
5+
const char *testName = name; \
6+
do { \
7+
Serial.println(testName)
8+
#define TEST_END() } while(0); \
9+
end: Serial.printf("%s %s\n",testName,failures == temp?"SUCCEEDED":"FAILED")
10+
#define TEST_ASSERT(a) if(testAssert(__LINE__, (a))) goto end
11+
#define TEST_ASSERTM(a,m) if(testAssertm(__LINE__, (a),(m))) goto end
812

913
#include "query/FluxParser.h"
1014

test/test.ino

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ void loop() {
5555
testPoint();
5656
testFluxTypes();
5757
testFluxParserEmpty();
58-
5958
testFluxParserSingleTable();
6059
testFluxParserNilValue();
6160
testFluxParserMultiTables(false);
@@ -145,15 +144,22 @@ void testPoint() {
145144

146145
TEST_ASSERT(!p.hasTime());
147146
time_t now = time(nullptr);
147+
String snow(now);
148148
p.setTime(now);
149-
String testLineTime = testLine + " " + now;
149+
String testLineTime = testLine + " " + snow;
150+
line = p.toLineProtocol();
151+
TEST_ASSERTM(line == testLineTime, line);
152+
153+
unsigned long long ts = now*1000000000LL+123456789;
154+
p.setTime(ts);
155+
testLineTime = testLine + " " + snow + "123456789";
150156
line = p.toLineProtocol();
151157
TEST_ASSERTM(line == testLineTime, line);
152158

153159
now += 10;
154-
String nowStr(now);
155-
p.setTime(nowStr);
156-
testLineTime = testLine + " " + nowStr;
160+
snow = now;
161+
p.setTime(snow);
162+
testLineTime = testLine + " " + snow;
157163
line = p.toLineProtocol();
158164
TEST_ASSERTM(line == testLineTime, line);
159165

@@ -162,29 +168,29 @@ void testPoint() {
162168
int partsCount;
163169
String *parts = getParts(line, ' ', partsCount);
164170
TEST_ASSERTM(partsCount == 3, String("3 != ") + partsCount);
165-
TEST_ASSERT(parts[2].length() == nowStr.length());
171+
TEST_ASSERT(parts[2].length() == snow.length());
166172
delete[] parts;
167173

168174
p.setTime(WritePrecision::MS);
169175
TEST_ASSERT(p.hasTime());
170176
line = p.toLineProtocol();
171177
parts = getParts(line, ' ', partsCount);
172178
TEST_ASSERT(partsCount == 3);
173-
TEST_ASSERT(parts[2].length() == nowStr.length() + 3);
179+
TEST_ASSERT(parts[2].length() == snow.length() + 3);
174180
delete[] parts;
175181

176182
p.setTime(WritePrecision::US);
177183
line = p.toLineProtocol();
178184
parts = getParts(line, ' ', partsCount);
179185
TEST_ASSERT(partsCount == 3);
180-
TEST_ASSERT(parts[2].length() == nowStr.length() + 6);
186+
TEST_ASSERT(parts[2].length() == snow.length() + 6);
181187
delete[] parts;
182188

183189
p.setTime(WritePrecision::NS);
184190
line = p.toLineProtocol();
185191
parts = getParts(line, ' ', partsCount);
186192
TEST_ASSERT(partsCount == 3);
187-
TEST_ASSERT(parts[2].length() == nowStr.length() + 9);
193+
TEST_ASSERT(parts[2].length() == snow.length() + 9);
188194
delete[] parts;
189195

190196
p.clearFields();
@@ -749,6 +755,31 @@ void testFailedWrites() {
749755

750756
void testTimestamp() {
751757
TEST_INIT("testTimestamp");
758+
759+
struct timeval tv;
760+
tv.tv_usec = 1234;
761+
tv.tv_sec = 5678;
762+
unsigned long long ts = getTimeStamp(&tv, 0);
763+
TEST_ASSERTM( ts == 5678, timeStampToString(ts));
764+
ts = getTimeStamp(&tv, 3);
765+
TEST_ASSERTM( ts == 5678001, timeStampToString(ts));
766+
ts = getTimeStamp(&tv, 6);
767+
TEST_ASSERTM( ts == 5678001234, timeStampToString(ts));
768+
ts = getTimeStamp(&tv, 9);
769+
TEST_ASSERTM( ts == 5678001234000, timeStampToString(ts));
770+
771+
// Test increasing timestamp
772+
String prev = "";
773+
for(int i = 0;i<2000;i++) {
774+
Point p("test");
775+
p.setTime(WritePrecision::US);
776+
String act = p.getTime();
777+
TEST_ASSERTM( i == 0 || prev < act, String(i) + ": " + prev + " vs " + act);
778+
prev = act;
779+
delayMicroseconds(100);
780+
}
781+
782+
752783
serverLog(INFLUXDB_CLIENT_TESTING_URL, "testTimestamp");
753784
InfluxDBClient client(INFLUXDB_CLIENT_TESTING_URL, INFLUXDB_CLIENT_TESTING_ORG, INFLUXDB_CLIENT_TESTING_BUC, INFLUXDB_CLIENT_TESTING_TOK);
754785
client.setWriteOptions(WritePrecision::S, 1, 5);
@@ -1007,6 +1038,7 @@ bool testFluxDateTimeValue(FluxQueryResult flux, int columnIndex, const char *c
10071038
TEST_ASSERTM(dt.microseconds == us, String(dt.microseconds) + " vs " + String(us));
10081039
return true;
10091040
} while(0);
1041+
end:
10101042
return false;
10111043
}
10121044

@@ -1017,6 +1049,7 @@ bool testStringValue(FluxQueryResult flux, int columnIndex, const char *columnN
10171049
TEST_ASSERTM(flux.getValueByName(columnName).getRawValue() == rawValue, flux.getValueByName(columnName).getRawValue());
10181050
return true;
10191051
} while(0);
1052+
end:
10201053
return false;
10211054
}
10221055

@@ -1027,12 +1060,12 @@ bool testStringVector(std::vector<String> vect, const char *values[], int size)
10271060
if(vect[i] != values[i]) {
10281061
Serial.print("assert failure: ");
10291062
Serial.println(vect[i]);
1030-
goto fail;
1063+
goto end;
10311064
}
10321065
}
10331066
return true;
10341067
} while(0);
1035-
fail:
1068+
end:
10361069
return false;
10371070
}
10381071

@@ -1043,6 +1076,7 @@ bool testDoubleValue(FluxQueryResult flux, int columnIndex, const char *columnN
10431076
TEST_ASSERTM(flux.getValueByName(columnName).getRawValue() == rawValue, flux.getValueByName(columnName).getRawValue());
10441077
return true;
10451078
} while(0);
1079+
end:
10461080
return false;
10471081
}
10481082

@@ -1053,6 +1087,7 @@ bool testLongValue(FluxQueryResult flux, int columnIndex, const char *columnNam
10531087
TEST_ASSERTM(flux.getValueByName(columnName).getRawValue() == rawValue, flux.getValueByName(columnName).getRawValue());
10541088
return true;
10551089
} while(0);
1090+
end:
10561091
return false;
10571092
}
10581093

@@ -1063,6 +1098,7 @@ bool testUnsignedLongValue(FluxQueryResult flux, int columnIndex, const char *c
10631098
TEST_ASSERTM(flux.getValueByName(columnName).getRawValue() == rawValue, flux.getValueByName(columnName).getRawValue());
10641099
return true;
10651100
} while(0);
1101+
end:
10661102
return false;
10671103
}
10681104

@@ -1076,6 +1112,7 @@ bool testTableColumns(FluxQueryResult flux, const char *columns[], int columnsC
10761112
TEST_ASSERTM(flux.getColumnIndex("x") == -1, "flux.getColumnIndex(\"x\")");
10771113
return true;
10781114
} while(0);
1115+
end:
10791116
return false;
10801117
}
10811118

@@ -1500,7 +1537,7 @@ void initInet() {
15001537
Serial.print("Ip: ");
15011538
Serial.println(WiFi.localIP());
15021539

1503-
timeSync("CET-1CEST,M3.5.0,M10.5.0/3", "pool.ntp.org", "0.cz.pool.ntp.org", "1.cz.pool.ntp.org");
1540+
timeSync("CET-1CEST,M3.5.0,M10.5.0/3", "0.cz.pool.ntp.org", "1.cz.pool.ntp.org", "pool.ntp.org");
15041541

15051542
deleteAll(INFLUXDB_CLIENT_TESTING_URL);
15061543
}

0 commit comments

Comments
 (0)