Skip to content

Commit fafe9a2

Browse files
committed
Update to rebuild timestamps for conditions where the imp boots offline and has no RTC.
This is accomplished by logging hardware.millis() to the spiflashlogger and then syncing hardware.millis() with time(), once our cloud connection is made and time() is restored
1 parent 6d6960f commit fafe9a2

File tree

1 file changed

+93
-31
lines changed

1 file changed

+93
-31
lines changed

Diff for: ImpPager.class.nut

+93-31
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
#require "SPIFlashLogger.class.nut:2.1.0"
22
#require "ConnectionManager.class.nut:1.0.1"
33
#require "Serializer.class.nut:1.0.0"
4-
#require "bullwinkle.class.nut:2.3.1"
4+
#require "bullwinkle.class.nut:2.3.2"
55

66
const IMP_PAGER_MESSAGE_TIMEOUT = 1;
77
const IMP_PAGER_RETRY_PERIOD_SEC = 0.5;
88

99
const IMP_PAGER_CM_DEFAULT_SEND_TIMEOUT = 1;
1010
const IMP_PAGER_CM_DEFAULT_BUFFER_SIZE = 8096;
1111

12+
const RTC_INVALID_TIME = 946684800; //Saturday 1st January 2000 12:00:00 AM UTC - this is what time() returns if the RTC signal from the imp cloud has not been received this boot.
13+
1214
class ImpPager {
1315

1416
// Bullwinkle instance
@@ -23,10 +25,16 @@ class ImpPager {
2325
// Message retry timer
2426
_retryTimer = null;
2527

28+
// The number of boots that this device has had - used to detect if multiple reboots without a RTC have occurred. If bootNumber is not set, we will not attempt to recover from a lack of RTC.
29+
_bootNumber = null;
30+
31+
// array of [lastTSmillis, lastTStimeSec] used for rebuilding timestamps for if/when we didn't have a RTC.
32+
_lastTS = null;
33+
2634
// Debug flag that controlls the debug output
2735
_debug = false;
2836

29-
constructor(connectionManager, bullwinkle = null, spiFlashLogger = null, debug = false) {
37+
constructor(connectionManager, bullwinkle = null, spiFlashLogger = null, bootNumber = null, debug = false) {
3038
_connectionManager = connectionManager;
3139

3240
_bullwinkle = bullwinkle ? bullwinkle : Bullwinkle({"messageTimeout" : IMP_PAGER_MESSAGE_TIMEOUT});
@@ -36,60 +44,75 @@ class ImpPager {
3644
_connectionManager.onConnect(_onConnect.bindenv(this));
3745
_connectionManager.onDisconnect(_onDisconnect.bindenv(this));
3846

47+
// Set the bootNumber. If a BootNumber is provided, we will try to rebuild timestamps for conditions where we boot offline and without a RTC. Otherwise, we won't.
48+
_bootNumber = bootNumber
49+
3950
// Schedule routine to retry sending messages
4051
_scheduleRetryIfConnected();
4152

4253
_debug = debug;
4354
}
4455

45-
function send(messageName, data = null) {
46-
_send(messageName, data);
56+
function send(messageName, data = null, ts = null) {
57+
if(ts == null) ts = time()
58+
if(_bootNumber!= null && ts == RTC_INVALID_TIME) ts = _bootNumber + "-" + hardware.millis() //provides ms accurate delta times that can be up to 25 days (2^31ms) apart. We use typeof(ts) == "string" to detect that our RTC has not been set in the .onFail.
59+
60+
_bullwinkle.send(messageName, data, ts)
61+
.onSuccess(_onSuccess.bindenv(this))
62+
.onFail(_onFail.bindenv(this));
4763
}
4864

4965
function _onSuccess(message) {
5066
// Do nothing
51-
_log_debug("ACKed message name: '" + message.name + "', data: " + message.data);
67+
_log_debug("ACKed message id " + message.id + " with name: '" + message.name + "' and data: " + message.data);
5268
if ("metadata" in message && "addr" in message.metadata && message.metadata.addr) {
5369
local addr = message.metadata.addr;
5470
_spiFlashLogger.erase(addr);
55-
delete message.metadata;
5671
_scheduleRetryIfConnected();
5772
}
5873
}
5974

6075
function _onFail(err, message, retry) {
61-
_log_debug("Failed to deliver message name: '" + message.name + "', data: " + message.data + ", err: " + err);
76+
_log_debug("Failed to deliver message id " + message.id + " with name: '" + message.name + "' and data: " + message.data + ", err: " + err);
77+
6278
// On fail write the message to the SPI Flash for further processing
6379
// only if it's not already there.
6480
if (!("metadata" in message) || !("addr" in message.metadata) || !(message.metadata.addr)) {
81+
delete message.type //Not needed to write to SPIFlash, as the type will always be BULLWINKLE_MESSAGE_TYPE.SEND
82+
83+
if(typeof(message.ts) == "string") { // We have a _bootNumber and invalid RTC - add some metadata so that we can try to restore the timestamp once we have RTC.
84+
message.metadata <- {
85+
"boot": split(message.ts, "-")[0].tointeger()
86+
"rtc": false
87+
}
88+
message.ts = split(message.ts, "-")[1].tointeger()
89+
}
6590
_spiFlashLogger.write(message);
91+
message.type <- BULLWINKLE_MESSAGE_TYPE.TIMEOUT // We are mucking around with the internal logic of Bullwinkle so we need to repair the message object here
6692
}
6793
_scheduleRetryIfConnected();
6894
}
6995

70-
function _send(messageName, data) {
71-
return _bullwinkle.send(messageName, data)
72-
.onSuccess(_onSuccess.bindenv(this))
73-
.onFail(_onFail.bindenv(this));
74-
}
75-
7696
// This is a hack to resend the message with metainformation
77-
function _resendExistingMessage(message) {
78-
_log_debug("Resending message name: '" + message.name + "', message: " + message.data);
79-
local package = Bullwinkle.Package(message)
97+
function _resendLoggedData(dataPoint) {
98+
_log_debug("Resending message id " + dataPoint.id + " with name: '" + dataPoint.name + "' and data: " + dataPoint.data)
99+
100+
dataPoint.type <- BULLWINKLE_MESSAGE_TYPE.SEND;
101+
102+
local package = Bullwinkle.Package(dataPoint)
80103
.onSuccess(_onSuccess.bindenv(this))
81104
.onFail(_onFail.bindenv(this));
82-
message.ts = time();
83-
message.type = BULLWINKLE_MESSAGE_TYPE.SEND;
84-
_bullwinkle._packages[message.id] <- package;
85-
_bullwinkle._sendMessage(message);
105+
106+
_bullwinkle._packages[dataPoint.id] <- package;
107+
_bullwinkle._sendMessage(dataPoint);
86108
}
87109

110+
88111
function _retry() {
89112
_log_debug("Start processing pending messages...");
90113
_spiFlashLogger.read(
91114
function(dataPoint, addr, next) {
92-
_log_debug("Reading from the SPI Flash. Data: " + dataPoint.data + " at addr: " + addr);
115+
_log_debug("Reading from SPI Flash. ID: " + dataPoint.id + " at addr: " + addr);
93116

94117
// There's no point of retrying to send pending messages when disconnected
95118
if (!_connectionManager.isConnected()) {
@@ -98,15 +121,55 @@ class ImpPager {
98121
next(false);
99122
return;
100123
}
124+
125+
if(time() == RTC_INVALID_TIME){ // If time is invalid, we aren't ready to resend any data just yet...
126+
_log_debug("time() was invalid, abort SPI Flash scanning...");
127+
next(false);
128+
return;
129+
}
130+
101131
// Save SPI Flash address in the message metadata
102-
dataPoint.metadata <- {"addr" : addr};
103-
_resendExistingMessage(dataPoint);
132+
if(!("metadata" in dataPoint)) dataPoint.metadata <- {}
133+
dataPoint.metadata.addr <- addr;
134+
135+
if("rtc" in dataPoint.metadata && dataPoint.metadata.rtc == false){
136+
if(_lastTS == null){
137+
_lastTS = [hardware.millis(), time()] //With these two datapoints, we can now re-establish all of our timestamps
138+
_log_debug("Discovered most recent datapoint saved to SPIFlash without RTC - " + dataPoint.id + " attempting to rebuild timestamps with ms = " + _lastTS[0] + " and time = " + _lastTS[1])
139+
}
140+
141+
_log_debug("Found log without RTC. ID=" + dataPoint.id + " and ts=" +dataPoint.ts)
142+
143+
if("boot" in dataPoint.metadata && dataPoint.metadata.boot == _bootNumber){
144+
local deltaTMillis = _lastTS[0] - dataPoint.ts
145+
local deltaTSeconds = deltaTMillis/1000
146+
dataPoint.ts = _lastTS[1] - deltaTSeconds //All integer math, so no need to worry about decimal points
147+
148+
_log_debug("Calculated new ts as " + dataPoint.ts + "(deltaT = " + deltaTMillis + " ms)")
149+
150+
//update _lastTS so that we can have 25 days between datapoints instead of 25 days total of timestamps that we can rebuild
151+
_lastTS[0] -= (deltaTSeconds*1000)
152+
_lastTS[1] = dataPoint.ts
153+
154+
// Our RTC has been reset - delete the metadata
155+
delete dataPoint.metadata.rtc
156+
157+
158+
159+
} else {
160+
server.error("Warning - dataPoint bootNumber " + dpBootNum + " != device bootNumber " + _bootNumber + " for message ID " + dataPoint.id + ". Unable to rebuild ts...")
161+
}
162+
}
163+
164+
_resendLoggedData(dataPoint);
165+
104166
// Don't do any further scanning until we get an ACK for already sent message
105167
next(false);
106168
}.bindenv(this),
107-
function() {
108-
_log_debug("Finished processing all pending messages");
109-
}.bindenv(this)
169+
170+
null,
171+
172+
-1 // Read through the data from most recent (which is important for real-time apps) to oldest, 1 at a time. This also allows us to rebuild our timestamps from newest to oldest
110173
);
111174
}
112175

@@ -156,15 +219,15 @@ class ImpPager.ConnectionManager extends ConnectionManager {
156219
constructor(settings = {}) {
157220
base.constructor(settings);
158221

159-
// Override the timeout to make it a nonzero, but still
160-
// a small value. This is needed to avoid accedental
222+
// Override the timeout to make it a nonzero, but still
223+
// a small value. This is needed to avoid accedental
161224
// imp disconnects when using ConnectionManager library
162-
local sendTimeout = "sendTimeout" in settings ?
225+
local sendTimeout = "sendTimeout" in settings ?
163226
settings.sendTimeout : IMP_PAGER_CM_DEFAULT_SEND_TIMEOUT;
164227
server.setsendtimeoutpolicy(RETURN_ON_ERROR, WAIT_TIL_SENT, sendTimeout);
165228

166229
// Set the recommended buffer size
167-
local sendBufferSize = "sendBufferSize" in settings ?
230+
local sendBufferSize = "sendBufferSize" in settings ?
168231
settings.sendBufferSize : IMP_PAGER_CM_DEFAULT_BUFFER_SIZE;
169232
imp.setsendbuffersize(sendBufferSize);
170233

@@ -209,5 +272,4 @@ class ImpPager.ConnectionManager extends ConnectionManager {
209272
}
210273
);
211274
}
212-
213275
};

0 commit comments

Comments
 (0)