Skip to content

Commit bb695fa

Browse files
committed
Begin chron based update checker (#31)
1 parent 8a8e652 commit bb695fa

File tree

4 files changed

+324
-0
lines changed

4 files changed

+324
-0
lines changed

micro/app/mcron/__init__.py

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
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)

micro/app/mcron/decorators.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import app.mcron
2+
3+
RUN_TIMES_MEM_ID = const(2)
4+
5+
6+
def run_times(times):
7+
"""
8+
The decorator determines how many times the given callback can be started.
9+
10+
:param times: number of start-ups
11+
:return:
12+
"""
13+
14+
def decorator(callback):
15+
def wrapper(callback_id, current_time, callback_memory):
16+
callback_memory[RUN_TIMES_MEM_ID] = callback_memory.setdefault(RUN_TIMES_MEM_ID, 0) + 1
17+
out = callback(callback_id, current_time, callback_memory)
18+
if callback_memory[RUN_TIMES_MEM_ID] >= times:
19+
mcron.remove(callback_id)
20+
return out
21+
22+
return wrapper
23+
24+
return decorator
25+
26+
27+
SUCCESSFULLY_RUN_TIMES_MEM_ID = const(3)
28+
29+
30+
def successfully_run_times(times):
31+
"""
32+
The decorator determines how many times the given callback can be started.
33+
34+
Launching a task is considered correct only if the callback returns True.
35+
36+
:param times: number of start-ups
37+
:return:
38+
"""
39+
MEM_ID = '__srt'
40+
41+
def decorator(callback):
42+
def wrapper(callback_id, current_time, callback_memory):
43+
out = callback(callback_id, current_time, callback_memory)
44+
callback_memory[SUCCESSFULLY_RUN_TIMES_MEM_ID] = \
45+
callback_memory.setdefault(SUCCESSFULLY_RUN_TIMES_MEM_ID, 0) + int(bool(out))
46+
if callback_memory[SUCCESSFULLY_RUN_TIMES_MEM_ID] >= times:
47+
mcron.remove(callback_id)
48+
return out
49+
50+
return wrapper
51+
52+
return decorator
53+
54+
55+
CALL_COUNTER_MEM_ID = const(4)
56+
57+
58+
def call_counter(callback):
59+
"""
60+
Decorator counts the number of callback calls.
61+
62+
The number of calls is stored in memory[call_counter.ID].
63+
64+
:param callback:
65+
:return:
66+
"""
67+
68+
def decorator(callback_id, current_time, callback_memory):
69+
callback_memory[CALL_COUNTER_MEM_ID] = callback_memory.setdefault(CALL_COUNTER_MEM_ID, 0) + 1
70+
return callback(callback_id, current_time, callback_memory)
71+
72+
return decorator
73+
74+
75+
def debug_call(callback):
76+
"""
77+
The decorator displays information about the current call
78+
79+
:param callback:
80+
:return:
81+
"""
82+
83+
@call_counter
84+
def wrap(callback_id, current_time, callback_memory):
85+
import utime
86+
print(
87+
'START call(%3d): %25s, pointer%18s' % (
88+
callback_memory[CALL_COUNTER_MEM_ID],
89+
callback_id,
90+
str(utime.localtime(current_time))
91+
)
92+
)
93+
mem_before = dict([(k, d) for k, d in callback_memory.items() if not k.startswith('__')])
94+
print(' Memory before call: %s' % mem_before)
95+
out = callback(callback_id, current_time, callback_memory)
96+
mem_after = dict([(k, d) for k, d in callback_memory.items() if not k.startswith('__')])
97+
print(' Memory after call: %s' % mem_after)
98+
print(
99+
'END call(%3d): %25s, pointer%18s' % (
100+
callback_memory[CALL_COUNTER_MEM_ID],
101+
callback_id,
102+
str(utime.localtime(current_time))
103+
)
104+
)
105+
print()
106+
return out
107+
108+
return wrap

micro/app/mcron/version.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = '1.1.2'

micro/app/startup.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import app.input as inputio
88
from app.led import animate
99
from app.commander import start_commander
10+
import app.mcron as mcron
11+
import app.mcron.decorators
1012

1113
def check_update_and_install():
1214
otaUpdater = OTAUpdater('https://github.com/alexwohlbruck/covalent', github_src_dir='micro', main_dir='app')
@@ -17,6 +19,17 @@ def check_update_and_install():
1719
del(otaUpdater)
1820
gc.collect()
1921

22+
def counter(callback_id, current_time, callback_memory):
23+
check_update_and_install()
24+
25+
def start_updater_chron():
26+
mcron.init_timer()
27+
mcron.insert(15, {0}, '15s', counter)
28+
29+
def my_exception_processor(e):
30+
print(e)
31+
32+
mcron.callback_exception_processors.append(my_exception_processor)
2033

2134
def run_startup():
2235
wifi_success = connect_wifi_from_config()
@@ -29,6 +42,7 @@ def run_startup():
2942

3043
# Start commander service
3144
start_commander()
45+
start_updater_chron()
3246

3347
# Run LED animation loop
3448
# animate()

0 commit comments

Comments
 (0)