MySensors Library & Examples  2.3.2-62-ge298769
LocalFile.h
1 
2 #pragma once
3 
4 #include "FileLockFunctions.h"
5 
6 #include "PJON.h"
7 
8 // The maximum number of messages in the content file
9 #ifndef LF_QUEUESIZE
10 #define LF_QUEUESIZE 20
11 #endif
12 
13 // The name of the content file
14 #ifndef LF_FILENAME
15 #define LF_FILENAME "../PJONLocalFile.dat"
16 #endif
17 
18 // Delay in ms between each check for new packets on the disk
19 #ifndef LF_POLLDELAY
20 #define LF_POLLDELAY 10
21 #endif
22 
23 // Recommended receive time for this strategy, in microseconds
24 #ifndef LF_RECEIVE_TIME
25 #define LF_RECEIVE_TIME 0
26 #endif
27 
28 #define PJON_LF_DEBUG
29 
30 class LocalFile
31 {
32 private:
33  int fn = -1;
34 
35  /* The last record read from file, this is remembered to decide which
36  records have been read and which are unread. Note that record number 0
37  is never used, even when overflowing/wrapping around. */
38  uint16_t lastRecordIdRead = 0;
39 
40  uint16_t last_send_result = PJON_ACK;
41 
42  struct Record {
43  uint16_t length;
44  uint8_t message[PJON_PACKET_MAX_LENGTH];
45  Record()
46  {
47  memset(this, 0, sizeof(Record));
48  }
49  };
50 
51  void doOpen(const bool create = false)
52  {
53  if(fn != -1) {
54  return; // Already open
55  }
56  int mode = O_RDWR,
57  permissions = S_IREAD | S_IWRITE;
58  if(create) {
59  mode |= O_CREAT;
60  }
61 #ifdef _WIN32
62  mode |= O_RANDOM | O_BINARY;
63  _sopen_s(&fn, LF_FILENAME, mode, _SH_DENYNO, permissions);
64 #else
65  permissions |= S_IRGRP | S_IWGRP | S_IROTH;
66  // rw for owner+group, r for others
67  fn = open(LF_FILENAME, mode, permissions);
68 #endif
69  };
70 
71  bool openContentFile()
72  {
73  if(fn != -1) {
74  return true;
75  }
76  bool file_exists = CheckIfFile(LF_FILENAME);
77  if(!file_exists) {
78  // Create and initialize the file
79  uint16_t lastRecordId = 0, index[LF_QUEUESIZE];
80  memset(index, 0, LF_QUEUESIZE * sizeof(uint16_t));
81  doOpen(true);
82  if(fn != -1) {
83  lock();
84  write(fn, (const char*)&lastRecordId, sizeof(lastRecordId));
85  write(fn, (const char*)index, LF_QUEUESIZE*sizeof(uint16_t));
86  Record record;
87  for(uint8_t i=0; i<LF_QUEUESIZE; i++) {
88  write(fn, (const char*)&record, sizeof(Record));
89  }
90  unlock();
91  }
92  }
93  if(fn == -1) {
94  doOpen();
95  }
96  return true;
97  };
98 
99  void closeContentFile()
100  {
101  if(fn != -1) {
102  close(fn);
103  fn = -1;
104  }
105  };
106 
107  bool readIndex(uint16_t &lastRecordId, uint16_t index[LF_QUEUESIZE])
108  {
109  lseek(fn, 0, SEEK_SET);
110  read(fn, (char*)&lastRecordId, sizeof(uint16_t));
111  read(fn, (char *)index, LF_QUEUESIZE*sizeof(uint16_t));
112  return true;
113  };
114 
115  bool updateIndex(uint16_t recordId, uint8_t indexPos)
116  {
117  lseek(fn, 0, SEEK_SET);
118  write(fn, (const char*)&recordId, sizeof(uint16_t));
119  lseek(fn, sizeof recordId + indexPos*sizeof(uint16_t), SEEK_SET);
120  write(fn, (const char*)&recordId, sizeof(uint16_t));
121  return true;
122  };
123 
124  void lock()
125  {
126  if(fn != -1) {
127  LockFileSection(fn, 0, 1, 1, 1, 0);
128  }
129  };
130 
131  void unlock()
132  {
133  if(fn != -1) {
134  LockFileSection(fn, 0, 1, 0, 1, 0);
135  }
136  };
137 
138  bool writePacketToFile(const Record &record)
139  {
140  bool success = false;
141  lock();
142  uint16_t lastRecordId, index[LF_QUEUESIZE];
143  if(readIndex(lastRecordId, index)) {
144  // Find a position then write the record to the file
145  uint8_t recordPos = findReusablePosition(lastRecordId, index);
146  if(recordPos != PJON_NOT_ASSIGNED) {
147 #ifdef PJON_LF_DEBUG
148  uint16_t diff = (uint16_t)(lastRecordId - index[recordPos]);
149  bool rollover = lastRecordId < index[recordPos];
150  if(
151  (!rollover && (diff+1 != LF_QUEUESIZE)) ||
152  (rollover && (diff != LF_QUEUESIZE))
153  ) printf(
154  "countermismatch=%d/%d/%d/(%d) ",
155  recordPos,
156  index[recordPos],
157  lastRecordId,
158  diff
159  );
160 #endif
161  uint32_t filePos =
162  sizeof(uint16_t) +
163  LF_QUEUESIZE * sizeof(uint16_t) +
164  recordPos * sizeof(Record)
165  ;
166  lseek(fn, filePos, SEEK_SET);
167  write(fn, (const char*)&record, sizeof(Record));
168  uint16_t nextRecordId = (uint16_t)(lastRecordId + 1);
169  if(nextRecordId == 0) {
170  nextRecordId = 1; // Avoid record id 0, used to mark unused slot
171  }
172  success = updateIndex(nextRecordId, recordPos);
173  }
174  }
175  unlock();
176  return success;
177  };
178 
179  bool readNextPacketFromFile(Record &record)
180  {
181  bool success = false;
182  uint16_t lastRecordId, index[LF_QUEUESIZE];
183  lock();
184  if(readIndex(lastRecordId, index)) {
185  // Find a position then read the record from the file
186  if(lastRecordId != 0) {
187  uint8_t recordPos = findNextUnreadPosition(lastRecordId, index);
188  if(recordPos != PJON_NOT_ASSIGNED) {
189  uint32_t filePos =
190  sizeof(uint16_t) +
191  LF_QUEUESIZE * sizeof(uint16_t) +
192  recordPos * sizeof(Record)
193  ;
194  lseek(fn, filePos, SEEK_SET);
195  read(fn, (char*)&record, sizeof(Record));
196  lastRecordIdRead = index[recordPos];
197  success = true;
198  }
199  }
200  }
201  unlock();
202  return success;
203  };
204 
205  uint8_t findReusablePosition(
206  const uint16_t lastRecordId,
207  const uint16_t index[LF_QUEUESIZE]
208  )
209  {
210  uint16_t maxDiff = 0;
211  uint8_t pos = PJON_NOT_ASSIGNED;
212  for(uint8_t i = 0; i < LF_QUEUESIZE; i++) {
213  if(index[i] == 0) {
214  return i; // Never used
215  }
216  uint16_t diff = (uint16_t)(lastRecordId - index[i]);
217  if(diff > maxDiff) {
218  maxDiff = diff;
219  pos = i;
220  }
221  }
222  return pos;
223  };
224 
225  uint8_t findNextUnreadPosition(
226  const uint16_t lastRecordId,
227  const uint16_t index[LF_QUEUESIZE]
228  )
229  {
230  uint8_t pos = PJON_NOT_ASSIGNED;
231  if(lastRecordIdRead == 0) {
232  lastRecordIdRead = lastRecordId - LF_QUEUESIZE;
233  }
234  if(lastRecordId == lastRecordIdRead) {
235  return pos; // Nothing new has arrived
236  }
237  uint16_t minDiff = 0xFFFF;
238  for(uint8_t i = 0; i < LF_QUEUESIZE; i++) {
239  if(index[i] == 0 || index[i] == lastRecordIdRead) {
240  continue; // Never used or already read
241  }
242  uint16_t diff = (uint16_t)(index[i] - lastRecordIdRead);
243  if(diff < 0x8FFF && diff < minDiff) {
244  minDiff = diff;
245  pos = i;
246  }
247  }
248  return pos;
249  };
250 
251 public:
252 
253  ~LocalFile()
254  {
255  closeContentFile();
256  };
257 
258  uint32_t back_off(uint8_t attempts)
259  {
260  return 1000 * attempts;
261  };
262 
263  bool begin(uint8_t did)
264  {
265  return openContentFile();
266  };
267 
268  void handle_collision()
269  {
270  PJON_DELAY(10);
271  };
272 
273  bool can_start()
274  {
275  if(fn == -1) {
276  openContentFile();
277  }
278  return fn != -1;
279  };
280 
281  uint8_t get_max_attempts()
282  {
283  return 10;
284  };
285 
286  static uint16_t get_receive_time()
287  {
288  return LF_RECEIVE_TIME;
289  };
290 
291  uint16_t receive_frame(uint8_t *data, uint16_t max_length)
292  {
293  Record record;
294  if(readNextPacketFromFile(record)) {
295  uint16_t length =
296  record.length < max_length ? record.length : max_length;
297  memcpy(data, record.message, length);
298  return length;
299  // Relax polling to avoid stressing the disk and CPU too much
300  } else {
301  PJON_DELAY(LF_POLLDELAY);
302  }
303  return PJON_FAIL;
304  };
305 
306  uint16_t receive_response()
307  {
308  return last_send_result;
309  };
310 
311  void send_response(uint8_t response) { };
312 
313  void send_frame(uint8_t *data, uint16_t length)
314  {
315  Record record;
316  memcpy(&record.message, data, length);
317  record.length = length;
318  bool ok = writePacketToFile(record);
319  last_send_result = ok ? PJON_ACK : PJON_FAIL;
320  };
321 };
data
char data[MAX_PAYLOAD_SIZE+1]
Buffer for raw payload data.
Definition: MyMessage.h:654
LocalFile
Definition: LocalFile.h:30