PlatformIO package of the Teensy core framework compatible with GCC 10 & C++20
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

303 lines
9.1KB

  1. /*
  2. TimeAlarms.cpp - Arduino Time alarms for use with Time library
  3. Copyright (c) 2008-2011 Michael Margolis.
  4. This library is free software; you can redistribute it and/or
  5. modify it under the terms of the GNU Lesser General Public
  6. License as published by the Free Software Foundation; either
  7. version 2.1 of the License, or (at your option) any later version.
  8. This library is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. Lesser General Public License for more details.
  12. */
  13. /*
  14. 2 July 2011 - replaced alarm types implied from alarm value with enums to make trigger logic more robust
  15. - this fixes bug in repeating weekly alarms - thanks to Vincent Valdy and draythomp for testing
  16. */
  17. #include "TimeAlarms.h"
  18. #define IS_ONESHOT true // constants used in arguments to create method
  19. #define IS_REPEAT false
  20. //**************************************************************
  21. //* Alarm Class Constructor
  22. AlarmClass::AlarmClass()
  23. {
  24. Mode.isEnabled = Mode.isOneShot = 0;
  25. Mode.alarmType = dtNotAllocated;
  26. value = nextTrigger = 0;
  27. onTickHandler = NULL; // prevent a callback until this pointer is explicitly set
  28. }
  29. //**************************************************************
  30. //* Private Methods
  31. void AlarmClass::updateNextTrigger()
  32. {
  33. if (Mode.isEnabled) {
  34. time_t time = now();
  35. if (dtIsAlarm(Mode.alarmType) && nextTrigger <= time) {
  36. // update alarm if next trigger is not yet in the future
  37. if (Mode.alarmType == dtExplicitAlarm) {
  38. // is the value a specific date and time in the future
  39. nextTrigger = value; // yes, trigger on this value
  40. } else if (Mode.alarmType == dtDailyAlarm) {
  41. //if this is a daily alarm
  42. if (value + previousMidnight(now()) <= time) {
  43. // if time has passed then set for tomorrow
  44. nextTrigger = value + nextMidnight(time);
  45. } else {
  46. // set the date to today and add the time given in value
  47. nextTrigger = value + previousMidnight(time);
  48. }
  49. } else if (Mode.alarmType == dtWeeklyAlarm) {
  50. // if this is a weekly alarm
  51. if ((value + previousSunday(now())) <= time) {
  52. // if day has passed then set for the next week.
  53. nextTrigger = value + nextSunday(time);
  54. } else {
  55. // set the date to this week today and add the time given in value
  56. nextTrigger = value + previousSunday(time);
  57. }
  58. } else {
  59. // its not a recognized alarm type - this should not happen
  60. Mode.isEnabled = false; // Disable the alarm
  61. }
  62. }
  63. if (Mode.alarmType == dtTimer) {
  64. // its a timer
  65. nextTrigger = time + value; // add the value to previous time (this ensures delay always at least Value seconds)
  66. }
  67. }
  68. }
  69. //**************************************************************
  70. //* Time Alarms Public Methods
  71. TimeAlarmsClass::TimeAlarmsClass()
  72. {
  73. isServicing = false;
  74. for(uint8_t id = 0; id < dtNBR_ALARMS; id++) {
  75. free(id); // ensure all Alarms are cleared and available for allocation
  76. }
  77. }
  78. void TimeAlarmsClass::enable(AlarmID_t ID)
  79. {
  80. if (isAllocated(ID)) {
  81. if (( !(dtUseAbsoluteValue(Alarm[ID].Mode.alarmType) && (Alarm[ID].value == 0)) ) && (Alarm[ID].onTickHandler != NULL)) {
  82. // only enable if value is non zero and a tick handler has been set
  83. // (is not NULL, value is non zero ONLY for dtTimer & dtExplicitAlarm
  84. // (the rest can have 0 to account for midnight))
  85. Alarm[ID].Mode.isEnabled = true;
  86. Alarm[ID].updateNextTrigger(); // trigger is updated whenever this is called, even if already enabled
  87. } else {
  88. Alarm[ID].Mode.isEnabled = false;
  89. }
  90. }
  91. }
  92. void TimeAlarmsClass::disable(AlarmID_t ID)
  93. {
  94. if (isAllocated(ID)) {
  95. Alarm[ID].Mode.isEnabled = false;
  96. }
  97. }
  98. // write the given value to the given alarm
  99. void TimeAlarmsClass::write(AlarmID_t ID, time_t value)
  100. {
  101. if (isAllocated(ID)) {
  102. Alarm[ID].value = value; //note: we don't check value as we do it in enable()
  103. Alarm[ID].nextTrigger = 0; // clear out previous trigger time (see issue #12)
  104. enable(ID); // update trigger time
  105. }
  106. }
  107. // return the value for the given alarm ID
  108. time_t TimeAlarmsClass::read(AlarmID_t ID) const
  109. {
  110. if (isAllocated(ID)) {
  111. return Alarm[ID].value ;
  112. } else {
  113. return dtINVALID_TIME;
  114. }
  115. }
  116. // return the alarm type for the given alarm ID
  117. dtAlarmPeriod_t TimeAlarmsClass::readType(AlarmID_t ID) const
  118. {
  119. if (isAllocated(ID)) {
  120. return (dtAlarmPeriod_t)Alarm[ID].Mode.alarmType ;
  121. } else {
  122. return dtNotAllocated;
  123. }
  124. }
  125. void TimeAlarmsClass::free(AlarmID_t ID)
  126. {
  127. if (isAllocated(ID)) {
  128. Alarm[ID].Mode.isEnabled = false;
  129. Alarm[ID].Mode.alarmType = dtNotAllocated;
  130. Alarm[ID].onTickHandler = NULL;
  131. Alarm[ID].value = 0;
  132. Alarm[ID].nextTrigger = 0;
  133. }
  134. }
  135. // returns the number of allocated timers
  136. uint8_t TimeAlarmsClass::count() const
  137. {
  138. uint8_t c = 0;
  139. for(uint8_t id = 0; id < dtNBR_ALARMS; id++) {
  140. if (isAllocated(id)) c++;
  141. }
  142. return c;
  143. }
  144. // returns true only if id is allocated and the type is a time based alarm, returns false if not allocated or if its a timer
  145. bool TimeAlarmsClass::isAlarm(AlarmID_t ID) const
  146. {
  147. return( isAllocated(ID) && dtIsAlarm(Alarm[ID].Mode.alarmType) );
  148. }
  149. // returns true if this id is allocated
  150. bool TimeAlarmsClass::isAllocated(AlarmID_t ID) const
  151. {
  152. return (ID < dtNBR_ALARMS && Alarm[ID].Mode.alarmType != dtNotAllocated);
  153. }
  154. // returns the currently triggered alarm id
  155. // returns dtINVALID_ALARM_ID if not invoked from within an alarm handler
  156. AlarmID_t TimeAlarmsClass::getTriggeredAlarmId() const
  157. {
  158. if (isServicing) {
  159. return servicedAlarmId; // new private data member used instead of local loop variable i in serviceAlarms();
  160. } else {
  161. return dtINVALID_ALARM_ID; // valid ids only available when servicing a callback
  162. }
  163. }
  164. // following functions are not Alarm ID specific.
  165. void TimeAlarmsClass::delay(unsigned long ms)
  166. {
  167. unsigned long start = millis();
  168. do {
  169. serviceAlarms();
  170. yield();
  171. } while (millis() - start <= ms);
  172. }
  173. void TimeAlarmsClass::waitForDigits( uint8_t Digits, dtUnits_t Units)
  174. {
  175. while (Digits != getDigitsNow(Units)) {
  176. serviceAlarms();
  177. }
  178. }
  179. void TimeAlarmsClass::waitForRollover( dtUnits_t Units)
  180. {
  181. // if its just rolled over than wait for another rollover
  182. while (getDigitsNow(Units) == 0) {
  183. serviceAlarms();
  184. }
  185. waitForDigits(0, Units);
  186. }
  187. uint8_t TimeAlarmsClass::getDigitsNow( dtUnits_t Units) const
  188. {
  189. time_t time = now();
  190. if (Units == dtSecond) return numberOfSeconds(time);
  191. if (Units == dtMinute) return numberOfMinutes(time);
  192. if (Units == dtHour) return numberOfHours(time);
  193. if (Units == dtDay) return dayOfWeek(time);
  194. return 255; // This should never happen
  195. }
  196. //returns isServicing
  197. bool TimeAlarmsClass::getIsServicing() const
  198. {
  199. return isServicing;
  200. }
  201. //***********************************************************
  202. //* Private Methods
  203. void TimeAlarmsClass::serviceAlarms()
  204. {
  205. if (!isServicing) {
  206. isServicing = true;
  207. for (servicedAlarmId = 0; servicedAlarmId < dtNBR_ALARMS; servicedAlarmId++) {
  208. if (Alarm[servicedAlarmId].Mode.isEnabled && (now() >= Alarm[servicedAlarmId].nextTrigger)) {
  209. OnTick_t TickHandler = Alarm[servicedAlarmId].onTickHandler;
  210. if (Alarm[servicedAlarmId].Mode.isOneShot) {
  211. free(servicedAlarmId); // free the ID if mode is OnShot
  212. } else {
  213. Alarm[servicedAlarmId].updateNextTrigger();
  214. }
  215. if (TickHandler != NULL) {
  216. (*TickHandler)(); // call the handler
  217. }
  218. }
  219. }
  220. isServicing = false;
  221. }
  222. }
  223. // returns the absolute time of the next scheduled alarm, or 0 if none
  224. time_t TimeAlarmsClass::getNextTrigger() const
  225. {
  226. time_t nextTrigger = 0;
  227. for (uint8_t id = 0; id < dtNBR_ALARMS; id++) {
  228. if (isAllocated(id)) {
  229. if (nextTrigger == 0) {
  230. nextTrigger = Alarm[id].nextTrigger;
  231. }
  232. else if (Alarm[id].nextTrigger < nextTrigger) {
  233. nextTrigger = Alarm[id].nextTrigger;
  234. }
  235. }
  236. }
  237. return nextTrigger;
  238. }
  239. time_t TimeAlarmsClass::getNextTrigger(AlarmID_t ID) const
  240. {
  241. if (isAllocated(ID)) {
  242. return Alarm[ID].nextTrigger;
  243. } else {
  244. return 0;
  245. }
  246. }
  247. // attempt to create an alarm and return true if successful
  248. AlarmID_t TimeAlarmsClass::create(time_t value, OnTick_t onTickHandler, uint8_t isOneShot, dtAlarmPeriod_t alarmType)
  249. {
  250. if ( ! ( (dtIsAlarm(alarmType) && now() < SECS_PER_YEAR) || (dtUseAbsoluteValue(alarmType) && (value == 0)) ) ) {
  251. // only create alarm ids if the time is at least Jan 1 1971
  252. for (uint8_t id = 0; id < dtNBR_ALARMS; id++) {
  253. if (Alarm[id].Mode.alarmType == dtNotAllocated) {
  254. // here if there is an Alarm id that is not allocated
  255. Alarm[id].onTickHandler = onTickHandler;
  256. Alarm[id].Mode.isOneShot = isOneShot;
  257. Alarm[id].Mode.alarmType = alarmType;
  258. Alarm[id].value = value;
  259. enable(id);
  260. return id; // alarm created ok
  261. }
  262. }
  263. }
  264. return dtINVALID_ALARM_ID; // no IDs available or time is invalid
  265. }
  266. // make one instance for the user to use
  267. TimeAlarmsClass Alarm = TimeAlarmsClass() ;