|
| 1 | +import utime |
| 2 | +import machine |
| 3 | + |
| 4 | +PERIOD_CENTURY = const(100 * 365 * 24 * 60 * 60) # Warning: average value |
| 5 | +PERIOD_YEAR = const(365 * 24 * 60 * 60) # Warning: average value |
| 6 | +PERIOD_MONTH = const((365 * 24 * 60 * 60) // 12) # Warning: average value |
| 7 | +PERIOD_WEEK = const(7 * 24 * 60 * 60) |
| 8 | +PERIOD_DAY = const(24 * 60 * 60) |
| 9 | +PERIOD_HOUR = const(60 * 60) |
| 10 | +PERIOD_MINUTE = const(60) |
| 11 | + |
| 12 | +# { |
| 13 | +# <period: int>: { |
| 14 | +# period_steps: [<callback_id>, <callback_id>, ...] |
| 15 | +# } |
| 16 | +# } |
| 17 | +timer_table = {} |
| 18 | + |
| 19 | +# { |
| 20 | +# <callback_id>: {} |
| 21 | +# } |
| 22 | +memory_table = {} |
| 23 | + |
| 24 | +# { |
| 25 | +# <callback_id>: <callback> |
| 26 | +# } |
| 27 | +callback_table = {} |
| 28 | + |
| 29 | +# machine.Timer instance |
| 30 | +timer = None |
| 31 | + |
| 32 | +# A list of functions that process the captured exception, received from the running callback. |
| 33 | +# processor_exception_function(exception_instance) |
| 34 | +callback_exception_processors = [lambda e: print('Callback EXCEPTION: %s' % e)] |
| 35 | + |
| 36 | +STEP_TYPE_RANGE = const(1) |
| 37 | +STEP_TYPE_SET = const(2) |
| 38 | + |
| 39 | +_last_run_time = None |
| 40 | +_timer_period = None # You have to turn the timer on by: init_timer |
| 41 | +_max_time_task_calls = None # You have to turn the timer on by: init_timer |
| 42 | + |
| 43 | + |
| 44 | +class TLPTimeException(Exception): |
| 45 | + """ |
| 46 | + Too long processing time. |
| 47 | + The maximum time is: 1000 [ms] - 1.5 * period [ms] |
| 48 | + """ |
| 49 | + pass |
| 50 | + |
| 51 | + |
| 52 | +def insert(period, period_steps, callback_id, callback, period_offset=0, from_now=False): |
| 53 | + """ |
| 54 | +
|
| 55 | + Examples: |
| 56 | + * Starting once after XX seconds. |
| 57 | + insert(<seconds_from_now>+1, {<seconds_from_now>}, 'callback-id', run_times(1)(<callback>), from_now=True) |
| 58 | + * Running twice a day at 23:59 and 6:00 a.m. |
| 59 | + insert(mcron.PERIOD_DAY, {23 * 59 * 59, 6 * 60 * 60}, 'callback-id', <callback>) |
| 60 | +
|
| 61 | + :param period: A period of time during which the steps are repeated. |
| 62 | + :type period: int |
| 63 | + :param period_steps: The steps where the callbacks are called. The steps must be integer type. |
| 64 | + :type period_steps: range or set |
| 65 | + :param callback_id: |
| 66 | + :type callback_id: str |
| 67 | + :param callback: callable(callback_id, current_time, callback_memory) |
| 68 | + :type callback: callable |
| 69 | + :param period_offset: The beginning of the period is shifted by the set value. |
| 70 | + :type period_offset: int |
| 71 | + :param from_now: The period will start when the task is added. |
| 72 | + :return: |
| 73 | + """ |
| 74 | + global timer_table, memory_table, callback_table, callback_exception_processors |
| 75 | + |
| 76 | + if callback_id in callback_table: |
| 77 | + raise Exception('Callback ID - exists') |
| 78 | + |
| 79 | + if type(period) is not int: |
| 80 | + raise TypeError('period is not int') |
| 81 | + |
| 82 | + if from_now: |
| 83 | + period_offset = -1 * utime.time() % period |
| 84 | + |
| 85 | + if type(period_offset) is not int: |
| 86 | + raise TypeError('period_offset is not int') |
| 87 | + |
| 88 | + if type(period_steps).__name__ == 'range': |
| 89 | + period_steps = (STEP_TYPE_RANGE,) + (period_steps.start, period_steps.stop, period_steps.step) |
| 90 | + elif type(period_steps).__name__ == 'set': |
| 91 | + period_steps = (STEP_TYPE_SET,) + tuple(period_steps) |
| 92 | + else: |
| 93 | + raise Exception('period_steps wrong type') |
| 94 | + |
| 95 | + for s in period_steps[1:]: |
| 96 | + if type(s) is not int: |
| 97 | + raise TypeError('period step is not int %s' % str(period_steps)) |
| 98 | + |
| 99 | + callback_table[callback_id] = callback |
| 100 | + |
| 101 | + period_info = (period, period_offset) |
| 102 | + if period_info in timer_table: |
| 103 | + period_data = timer_table[period_info] |
| 104 | + else: |
| 105 | + period_data = {} |
| 106 | + timer_table[period_info] = period_data |
| 107 | + |
| 108 | + if period_steps in period_data: |
| 109 | + callback_ids = period_data[period_steps] |
| 110 | + else: |
| 111 | + callback_ids = set() |
| 112 | + period_data[period_steps] = callback_ids |
| 113 | + callback_ids.add(callback_id) |
| 114 | + |
| 115 | + |
| 116 | +def remove(callback_id): |
| 117 | + global timer_table, memory_table, callback_table |
| 118 | + to_del_pi = [] |
| 119 | + for period_info, period_data in timer_table.items(): |
| 120 | + to_del_ps = [] |
| 121 | + for period_steps, callback_ids in period_data.items(): |
| 122 | + if callback_id in callback_ids: |
| 123 | + callback_ids.remove(callback_id) |
| 124 | + if callback_id in memory_table: |
| 125 | + memory_table.pop(callback_id) |
| 126 | + if len(callback_ids) <= 0: |
| 127 | + to_del_ps.append(period_steps) |
| 128 | + for period_steps in to_del_ps: |
| 129 | + period_data.pop(period_steps) |
| 130 | + del to_del_ps |
| 131 | + if not period_data: |
| 132 | + to_del_pi.append(period_info) |
| 133 | + for period_info in to_del_pi: |
| 134 | + timer_table.pop(period_info) |
| 135 | + del to_del_pi |
| 136 | + if callback_id in callback_table: |
| 137 | + callback_table.pop(callback_id) |
| 138 | + |
| 139 | + |
| 140 | +def remove_all(): |
| 141 | + global callback_table |
| 142 | + for cid in callback_table.keys(): |
| 143 | + remove(cid) |
| 144 | + |
| 145 | + |
| 146 | +def get_actions(current_time): |
| 147 | + global timer_table, memory_table, callback_table, callback_exception_processors |
| 148 | + for period_info, period_data in timer_table.items(): |
| 149 | + period, period_offset = period_info |
| 150 | + period_pointer = (current_time + period_offset) % period |
| 151 | + for period_steps, callback_ids in period_data.items(): |
| 152 | + if STEP_TYPE_SET == period_steps[0] and period_pointer in period_steps[1:] or \ |
| 153 | + STEP_TYPE_RANGE == period_steps[0] and period_pointer in range(*period_steps[1:]): |
| 154 | + yield from callback_ids |
| 155 | + |
| 156 | + |
| 157 | +def run_actions(current_time): |
| 158 | + global timer_table, memory_table, callback_table, callback_exception_processors |
| 159 | + for callback_id in get_actions(current_time): |
| 160 | + callback_memory = memory_table.setdefault(callback_id, {}) |
| 161 | + action_callback = callback_table[callback_id] |
| 162 | + |
| 163 | + try: |
| 164 | + action_callback(callback_id, current_time, callback_memory) |
| 165 | + except Exception as e: |
| 166 | + for processor in callback_exception_processors: |
| 167 | + processor(e) |
| 168 | + |
| 169 | + |
| 170 | +def run_actions_callback(*args, **kwargs): |
| 171 | + global timer_table, memory_table, callback_table, callback_exception_processors, _last_run_time, _timer_period, _max_time_task_calls |
| 172 | + |
| 173 | + current_time = utime.time() |
| 174 | + if current_time == _last_run_time: |
| 175 | + return |
| 176 | + _last_run_time = current_time |
| 177 | + start = utime.ticks_ms() |
| 178 | + |
| 179 | + run_actions(current_time) |
| 180 | + |
| 181 | + stop = utime.ticks_ms() |
| 182 | + processing_time = utime.ticks_diff(stop, start) |
| 183 | + if processing_time > _max_time_task_calls: |
| 184 | + e = TLPTimeException(current_time, processing_time, ' '.join(get_actions(current_time))) |
| 185 | + for processor in callback_exception_processors: |
| 186 | + processor(e) |
| 187 | + |
| 188 | + |
| 189 | +def init_timer(timer_id=3, timer_period=250): |
| 190 | + """ |
| 191 | +
|
| 192 | + :param timer_id: |
| 193 | + :param timer_period: Number of milliseconds between run_actions calls. The recommended value is 250ms. For values greater than 1000ms some action calls may be omitted. |
| 194 | + :type timer_period: int |
| 195 | + :return: |
| 196 | + """ |
| 197 | + global timer, _timer_period, _max_time_task_calls, timer |
| 198 | + _timer_period = timer_period |
| 199 | + _max_time_task_calls = 1000 - 1.5 * _timer_period |
| 200 | + timer = machine.Timer(timer_id) |
| 201 | + timer.init(period=_timer_period, mode=machine.Timer.PERIODIC, callback=run_actions_callback) |
0 commit comments