Explorar el Código

Initial leyk server implementation

mod_update
Dirk Alders hace 4 años
padre
commit
70bbec4ec7
Se han modificado 13 ficheros con 532 adiciones y 0 borrados
  1. 2
    0
      .gitignore
  2. 24
    0
      .gitmodules
  3. 1
    0
      geo
  4. 17
    0
      leyk.py
  5. 479
    0
      piface_function.py
  6. 1
    0
      protocol
  7. 1
    0
      report
  8. 2
    0
      requirements.txt
  9. 1
    0
      socket_protocol
  10. 1
    0
      state_machine
  11. 1
    0
      stringtools
  12. 1
    0
      task
  13. 1
    0
      tcp_socket

+ 2
- 0
.gitignore Ver fichero

4
 *.py[cod]
4
 *.py[cod]
5
 *$py.class
5
 *$py.class
6
 
6
 
7
+config.py
8
+
7
 # C extensions
9
 # C extensions
8
 *.so
10
 *.so
9
 
11
 

+ 24
- 0
.gitmodules Ver fichero

1
+[submodule "geo"]
2
+	path = geo
3
+	url = https://git.mount-mockery.de/pylib/geo.git
4
+[submodule "report"]
5
+	path = report
6
+	url = https://git.mount-mockery.de/pylib/report.git
7
+[submodule "socket_protocol"]
8
+	path = socket_protocol
9
+	url = https://git.mount-mockery.de/pylib/socket_protocol.git
10
+[submodule "state_machine"]
11
+	path = state_machine
12
+	url = https://git.mount-mockery.de/pylib/state_machine.git
13
+[submodule "stringtools"]
14
+	path = stringtools
15
+	url = https://git.mount-mockery.de/pylib/stringtools.git
16
+[submodule "task"]
17
+	path = task
18
+	url = https://git.mount-mockery.de/pylib/task.git
19
+[submodule "tcp_socket"]
20
+	path = tcp_socket
21
+	url = https://git.mount-mockery.de/pylib/tcp_socket.git
22
+[submodule "protocol"]
23
+	path = protocol
24
+	url = https://git.mount-mockery.de/application/protocol_leyk.git

+ 1
- 0
geo

1
+Subproject commit f59b19ed1fd2a64735ca477fdb1fba24681e8879

+ 17
- 0
leyk.py Ver fichero

1
+#!/usr/bin/env python
2
+# -*- coding: UTF-8 -*-
3
+
4
+import config
5
+import os
6
+import report
7
+import tcp_socket
8
+import protocol
9
+import time
10
+
11
+
12
+if __name__ == '__main__':
13
+    report.appLoggingConfigure(os.path.dirname(__file__), 'logfile', config.loggers)
14
+    s = tcp_socket.tcp_server_stp(config.server_ip, port=config.server_port)
15
+    prot = protocol.my_server_protocol(s, secret=config.secret)
16
+    while True:
17
+        time.sleep(0.1)

+ 479
- 0
piface_function.py Ver fichero

1
+#!/usr/bin/env python
2
+# -*- coding: UTF-8 -*-
3
+
4
+try:
5
+    import pifacedigitalio
6
+except ImportError:
7
+    pifacedigitalio = None
8
+import geo
9
+import logging
10
+import state_machine
11
+import task
12
+import random
13
+import time
14
+
15
+logger = logging.getLogger('APP')
16
+
17
+geo_position = geo.gps.coordinate(lat=49.976596, lon=9.1481443)
18
+
19
+
20
+class pi_face(object):
21
+    LOG_PREFIX = 'PiFace:'
22
+
23
+    PF_OUT_NAMES = ['Output 0',     # 0
24
+                    'Output 1',     # 1
25
+                    'Output 2',     # 2
26
+                    'Ploenlein',    # 3
27
+                    'Bakery',       # 4
28
+                    'Mill',         # 5
29
+                    'Reese House',  # 6
30
+                    'Bake House']   # 7
31
+
32
+    def __init__(self):
33
+        if pifacedigitalio is not None:
34
+            pifacedigitalio.init()
35
+            pi = pifacedigitalio.PiFaceDigital()
36
+            self.__pf_outputs__ = pi.output_pins
37
+        self.__pf_output_states__ = 8 * [False]
38
+        #
39
+        self._reload = 0
40
+        self._task_10ms = task.periodic(0.01 - 0.007, self.task_10ms)
41
+        #
42
+        for i in range(0, 8):
43
+            self.set_output(i, self.__pf_output_states__[i])
44
+
45
+    def set_output(self, index, state, tries=5):
46
+        tries = min(max(1, tries), 5)
47
+        try_txt = ['1st', '2nd', '3rd', '4th', '5th']
48
+        state = state is True
49
+        if pifacedigitalio is not None:
50
+            cnt = 0
51
+            while (cnt < tries) and (self.__pf_outputs__[index].value != state):
52
+                self.__pf_outputs__[index].value = 1 * state
53
+                time.sleep(0.1)
54
+                cnt += 1
55
+            if self.__pf_outputs__[index].value != state:
56
+                logger.warning('%s Control of output[%d] (%s) after %s try not successfull!', self.LOG_PREFIX, index, self.PF_OUT_NAMES[index], try_txt[cnt - 1])
57
+            else:
58
+                logger.info('%s Set output "%s" to %s.', self.LOG_PREFIX, self.PF_OUT_NAMES[index], repr(state))
59
+            self.__pf_output_states__[index] = self.__pf_outputs__[index].value == 1
60
+        else:
61
+            logger.info('%s Set virtual output "%s" to %s.', self.LOG_PREFIX, self.PF_OUT_NAMES[index], repr(state))
62
+            self.__pf_output_states__[index] = state
63
+
64
+    def get_output(self, index):
65
+        return self.__pf_output_states__[index]
66
+
67
+    def get_ploenlein(self):
68
+        return self.get_output(3)
69
+
70
+    def set_bakery(self, state):
71
+        self.set_output(4, state)
72
+
73
+    def get_bakery(self):
74
+        return self.get_output(4)
75
+
76
+    def set_mill(self, state):
77
+        self.set_output(5, state)
78
+
79
+    def get_mill(self):
80
+        return self.get_output(5)
81
+
82
+    def set_ploenlein(self, state):
83
+        self.set_output(3, state)
84
+
85
+    def set_reese_house(self, state):
86
+        self.set_output(6, state)
87
+
88
+    def get_reese_house(self):
89
+        return self.get_output(6)
90
+
91
+    def set_bake_house(self, state):
92
+        if state is True:
93
+            self.set_output(7, state)
94
+            self._task_10ms.run()
95
+        elif state is False:
96
+            self._task_10ms.stop()
97
+            self.join()
98
+            time.sleep(0.1)
99
+            self.set_output(7, state)
100
+
101
+    def get_bake_house(self):
102
+        return not self._task_10ms._stopped
103
+
104
+    def task_10ms(self, task_inst):
105
+        if self._reload <= 0:
106
+            if pifacedigitalio is not None:
107
+                self.__pf_outputs__[7].turn_on()
108
+            self._reload = random.choice(2 * [0] + 20 * [1] + 1 * [2])
109
+        else:
110
+            if pifacedigitalio is not None:
111
+                self.__pf_outputs__[7].turn_off()
112
+            self._reload -= 1
113
+
114
+    def join(self):
115
+        self._task_10ms.join()
116
+
117
+    def stop(self):
118
+        self._task_10ms.stop()
119
+        self._task_10ms.join()
120
+        for i in range(0, 8):
121
+            self.control_output(i, False)
122
+        pifacedigitalio.deinit()
123
+
124
+    def __del__(self):
125
+        self.stop()
126
+
127
+
128
+class state_machine_mode(state_machine.state_machine):
129
+    LOG_PREFIX = 'LeykMode:'
130
+
131
+    STATE_AUTOMATIC = 'automatic'
132
+    STATE_MANUAL = 'manual'
133
+
134
+    CONDITION_EXTERNAL_TRIGGER = 'external_trigger'
135
+
136
+    TRANSITIONS = {
137
+        STATE_AUTOMATIC: (
138
+            (CONDITION_EXTERNAL_TRIGGER, 1, STATE_MANUAL),
139
+        ),
140
+        STATE_MANUAL: (
141
+            (CONDITION_EXTERNAL_TRIGGER, 1, STATE_AUTOMATIC),
142
+        ),
143
+    }
144
+
145
+    def __init__(self, **kwargs):
146
+        state_machine.state_machine.__init__(self, self.STATE_AUTOMATIC, logging.INFO)
147
+        self.__reset_triggers__()
148
+
149
+    def __reset_triggers__(self):
150
+        self.__to_automatic__ = False
151
+        self.__to_manual__ = False
152
+
153
+    def external_trigger(self):
154
+        rv = False
155
+        if self.this_state() == self.STATE_AUTOMATIC:
156
+            rv = self.__to_manual__
157
+        elif self.this_state() == self.STATE_MANUAL:
158
+            rv = self.__to_automatic__
159
+        self.__reset_triggers__()
160
+        return rv
161
+        
162
+    def trigger_to_maual(self):
163
+        if self.this_state() == self.STATE_AUTOMATIC:
164
+            self.__to_manual__ = True
165
+            return True
166
+        return False
167
+
168
+    def trigger_to_automatic(self):
169
+        if self.this_state() == self.STATE_MANUAL:
170
+            self.__to_automatic__ = True
171
+            return True
172
+        return False
173
+
174
+
175
+class state_machine_day_state(state_machine.state_machine):
176
+    LOG_PREFIX = 'LeykState:'
177
+
178
+    STATE_IDLE = 'idle'
179
+    STATE_WAKE = 'wake'
180
+    STATE_SUNRISE = 'sunrise'
181
+    STATE_SUNSET = 'sunset'
182
+    STATE_SLEEP = 'sleep'
183
+
184
+    CONDITION_WAKE = 'condition_wake'
185
+    CONDITION_SUNRISE = 'condition_sunrise'
186
+    CONDITION_SUNSET = 'condition_sunset'
187
+    CONDITION_SLEEP = 'condition_sleep'
188
+    CONDITION_IDLE = 'condition_idle'
189
+
190
+    TRANSITIONS = {
191
+        STATE_IDLE: (
192
+            (CONDITION_WAKE, 1, STATE_WAKE),
193
+            (CONDITION_SUNRISE, 1, STATE_SUNRISE),
194
+            (CONDITION_SUNSET, 1, STATE_SUNSET),
195
+            (CONDITION_SLEEP, 1, STATE_SLEEP),
196
+        ),
197
+        STATE_WAKE: (
198
+            (CONDITION_SUNRISE, 1, STATE_SUNRISE),
199
+            (CONDITION_IDLE, 1, STATE_IDLE),
200
+        ),
201
+        STATE_SUNRISE: (
202
+            (CONDITION_SUNSET, 1, STATE_SUNSET),
203
+            (CONDITION_IDLE, 1, STATE_IDLE),
204
+        ),
205
+        STATE_SUNSET: (
206
+            (CONDITION_SLEEP, 1, STATE_SLEEP),
207
+            (CONDITION_IDLE, 1, STATE_IDLE),
208
+        ),
209
+        STATE_SLEEP: (
210
+            (CONDITION_WAKE, 1, STATE_WAKE),
211
+            (CONDITION_IDLE, 1, STATE_IDLE),
212
+        ),
213
+    }
214
+
215
+    def __init__(self, **kwargs):
216
+        state_machine.state_machine.__init__(self, self.STATE_IDLE, logging.INFO, **kwargs)
217
+
218
+    def __current_state_calc__(self):
219
+        def wake_time():
220
+            tm = time.localtime()
221
+            tm = list(tm)
222
+            tm[3] = 6   # tm_hour
223
+            tm[4] = 0   # tm_min
224
+            tm[5] = 0   # tm_sec=0
225
+            return time.mktime(time.struct_time(tm))
226
+    
227
+        def sunrise_time():
228
+            return time.mktime(geo.sun.sunrise(geo_position)) + 30 * 60
229
+    
230
+        def sunset_time():
231
+            return time.mktime(geo.sun.sunset(geo_position)) - 30 * 60
232
+    
233
+        def sleep_time():
234
+            tm = time.localtime()
235
+            tm = list(tm)
236
+            tm[3] = 21  # tm_hour
237
+            tm[4] = 15  # tm_min
238
+            tm[5] = 0   # tm_sec=0
239
+            return time.mktime(time.struct_time(tm))
240
+
241
+        now = time.mktime(time.localtime())
242
+        if now > sleep_time():
243
+            return self.STATE_SLEEP
244
+        elif now > sunset_time():
245
+            return self.STATE_SUNSET
246
+        elif now > sunrise_time():
247
+            return self.STATE_SUNRISE
248
+        elif now > wake_time():
249
+            return self.STATE_WAKE
250
+        else:
251
+            return self.STATE_IDLE
252
+
253
+    def condition_wake(self):
254
+        if self.condition_idle():
255
+            return False
256
+        return self.__current_state_calc__() == self.STATE_WAKE
257
+
258
+    def condition_sunrise(self):
259
+        if self.condition_idle():
260
+            return False
261
+        return self.__current_state_calc__() == self.STATE_SUNRISE
262
+
263
+    def condition_sunset(self):
264
+        if self.condition_idle():
265
+            return False
266
+        return self.__current_state_calc__() == self.STATE_SUNSET
267
+
268
+    def condition_sleep(self):
269
+        if self.condition_idle():
270
+            return False
271
+        return self.__current_state_calc__() == self.STATE_SLEEP
272
+
273
+    def condition_idle(self):
274
+        return not self.sm_mode.this_state_is(self.sm_mode.STATE_AUTOMATIC)
275
+
276
+    def report_sunset_sunrise(self):
277
+        state_machine.logger.debug('Sunrise: %s - Sunset: %s;', time.strftime("%H:%M", geo.sun.sunrise(geo_position)), time.strftime("%H:%M", geo.sun.sunset(geo_position))
278
+)
279
+
280
+
281
+class leyk(object):
282
+    LOG_PREFIX = 'Leyk:'
283
+
284
+    def __init__(self):
285
+        self.sm_mode = state_machine_mode()
286
+        self.sm_day_state = state_machine_day_state(sm_mode=self.sm_mode)
287
+        self.__pf__ = pi_face()
288
+        self._queue = task.threaded_queue()
289
+        self._queue.run()
290
+        self._task_1s = task.periodic(1, self.task_1s)
291
+        self._task_1s.run()
292
+        self.sm_mode.register_state_change_callback(self.sm_mode.STATE_MANUAL, None, self._queue.clean_queue)
293
+        self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SLEEP, None, self.sm_day_state.report_sunset_sunrise)
294
+        self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SLEEP, None, self.fill_sleep_queue)
295
+        self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SUNRISE, None, self.sm_day_state.report_sunset_sunrise)
296
+        self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SUNRISE, None, self.fill_sunrise_queue)
297
+        self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SUNSET, None, self.sm_day_state.report_sunset_sunrise)
298
+        self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SUNSET, None, self.fill_sunset_queue)
299
+        self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_WAKE, None, self.sm_day_state.report_sunset_sunrise)
300
+        self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_WAKE, None, self.fill_wake_queue)
301
+
302
+    def set_mode(self, mode):
303
+        if mode == self.sm_mode.STATE_AUTOMATIC:
304
+            return self.sm_mode.trigger_to_automatic()
305
+        elif mode == self.sm_mode.STATE_MANUAL:
306
+            return self.sm_mode.trigger_to_maual()
307
+        else:
308
+            return False
309
+
310
+
311
+    def get_mode(self):
312
+        return self.sm_mode.this_state()
313
+
314
+    def get_state(self):
315
+        return self.sm_day_state.this_state()
316
+
317
+    def __queue_wrapper__(self, queue_inst, function, *args, **kwargs):
318
+        function(*args, **kwargs)
319
+
320
+    def set_ploenlein(self, state):
321
+        if self.sm_mode.this_state_is(self.sm_mode.STATE_MANUAL):
322
+            self.__pf__.set_ploenlein(state)
323
+            return True
324
+        else:
325
+            return False
326
+
327
+    def get_ploenlein(self):
328
+        return self.__pf__.get_ploenlein()
329
+
330
+    def set_bakery(self, state):
331
+        if self.sm_mode.this_state_is(self.sm_mode.STATE_MANUAL):
332
+            self.__pf__.set_bakery(state)
333
+            return True
334
+        else:
335
+            return False
336
+
337
+    def get_bakery(self):
338
+        return self.__pf__.get_bakery()
339
+
340
+    def set_bake_house(self, state):
341
+        if self.sm_mode.this_state_is(self.sm_mode.STATE_MANUAL):
342
+            self.__pf__.set_bake_house(state)
343
+            return True
344
+        else:
345
+            return False
346
+
347
+    def get_bake_house(self):
348
+        return self.__pf__.get_bake_house()
349
+
350
+    def set_mill(self, state):
351
+        if self.sm_mode.this_state_is(self.sm_mode.STATE_MANUAL):
352
+            self.__pf__.set_mill(state)
353
+            return True
354
+        else:
355
+            return False
356
+
357
+    def get_mill(self):
358
+        return self.__pf__.get_mill()
359
+
360
+    def set_reese_house(self, state):
361
+        if self.sm_mode.this_state_is(self.sm_mode.STATE_MANUAL):
362
+            self.__pf__.set_reese_house(state)
363
+            return True
364
+        else:
365
+            return False
366
+
367
+    def get_reese_house(self):
368
+        return self.__pf__.get_reese_house()
369
+
370
+    def wake_time(self):
371
+        tm = time.localtime()
372
+        tm = list(tm)
373
+        tm[3] = 6   # tm_hour
374
+        tm[4] = 0   # tm_min
375
+        tm[5] = 0   # tm_sec=0
376
+        return time.mktime(time.struct_time(tm))
377
+
378
+    def sunrise_time(self):
379
+        return time.mktime(geo.sun.sunrise(geo_position)) + 30 * 60
380
+
381
+    def sunset_time(self):
382
+        return time.mktime(geo.sun.sunset(geo_position)) - 30 * 60
383
+
384
+    def sleep_time(self):
385
+        tm = time.localtime()
386
+        tm = list(tm)
387
+        tm[3] = 21  # tm_hour
388
+        tm[4] = 15  # tm_min
389
+        tm[5] = 0   # tm_sec=0
390
+        return time.mktime(time.struct_time(tm))
391
+
392
+    def identify_current_state(self):
393
+        now = time.mktime(time.localtime())
394
+        if now > self.sleep_time():
395
+            return self.ST_SLEEP
396
+        elif now > self.sunset_time():
397
+            return self.ST_SUNSET
398
+        elif now > self.sunrise_time():
399
+            return self.ST_SUNRISE
400
+        elif now > self.wake_time():
401
+            return self.ST_WAKE
402
+        else:
403
+            return self.ST_SLEEP
404
+
405
+    def wait(self, queue_inst, delay):
406
+        if delay > 0:
407
+            logger.debug('%s Wait for %d seconds initiated. %d elements left in queue.', self.LOG_PREFIX, delay, queue_inst.qsize())
408
+            cnt = 0
409
+            while cnt < delay * 5:
410
+                if queue_inst.qsize() == 0:
411
+                    logger.debug('%s Quit wait for %d seconds. %d elements left in queue.', self.LOG_PREFIX, delay, queue_inst.qsize())
412
+                    break
413
+                time.sleep(0.2)
414
+                cnt += 1
415
+
416
+    def fill_wake_queue(self):
417
+        no_delay = self.sm_day_state.previous_state_was(self.sm_day_state.STATE_IDLE)
418
+        # WAKE
419
+        self._queue.enqueue(1, self.__queue_wrapper__, self.__pf__.set_bakery, True)
420
+        self._queue.enqueue(2, self.wait, 0 if no_delay else 5 * 60)
421
+        self._queue.enqueue(3, self.__queue_wrapper__, self.__pf__.set_bake_house, True)
422
+        self._queue.enqueue(4, self.wait, 0 if no_delay else 10 * 60)
423
+        self._queue.enqueue(5, self.__queue_wrapper__, self.__pf__.set_reese_house, True)
424
+        self._queue.enqueue(6, self.wait, 0 if no_delay else 7 * 60)
425
+        self._queue.enqueue(7, self.__queue_wrapper__, self.__pf__.set_ploenlein, True)
426
+        self._queue.enqueue(8, self.wait, 0 if no_delay else 8 * 60)
427
+        self._queue.enqueue(9, self.__queue_wrapper__, self.__pf__.set_mill, True)
428
+
429
+    def fill_sunrise_queue(self):
430
+        no_delay = self.sm_day_state.previous_state_was(self.sm_day_state.STATE_IDLE)
431
+        # SUNRISE
432
+        self._queue.enqueue(1, self.__queue_wrapper__, self.__pf__.set_bake_house, True)
433
+        self._queue.enqueue(2, self.__queue_wrapper__, self.__pf__.set_ploenlein, False)
434
+        self._queue.enqueue(3, self.wait, 0 if no_delay else 5 * 60)
435
+        self._queue.enqueue(4, self.__queue_wrapper__, self.__pf__.set_mill, False)
436
+        self._queue.enqueue(5, self.wait, 0 if no_delay else 8 * 60)
437
+        self._queue.enqueue(6, self.__queue_wrapper__, self.__pf__.set_bakery, False)
438
+        self._queue.enqueue(7, self.wait, 0 if no_delay else 12 * 60)
439
+        self._queue.enqueue(8, self.__queue_wrapper__, self.__pf__.set_reese_house, False)
440
+
441
+    def fill_sunset_queue(self):
442
+        no_delay = self.sm_day_state.previous_state_was(self.sm_day_state.STATE_IDLE)
443
+        # SUNSET
444
+        self._queue.enqueue(1, self.__queue_wrapper__, self.__pf__.set_bake_house, True)
445
+        self._queue.enqueue(2, self.__queue_wrapper__, self.__pf__.set_bakery, True)
446
+        self._queue.enqueue(3, self.wait, 0 if no_delay else 10 * 60)
447
+        self._queue.enqueue(4, self.__queue_wrapper__, self.__pf__.set_reese_house, True)
448
+        self._queue.enqueue(5, self.wait, 0 if no_delay else 8 * 60)
449
+        self._queue.enqueue(6, self.__queue_wrapper__, self.__pf__.set_mill, True)
450
+        self._queue.enqueue(7, self.wait, 0 if no_delay else 7 * 60)
451
+        self._queue.enqueue(8, self.__queue_wrapper__, self.__pf__.set_ploenlein, True)
452
+
453
+    def fill_sleep_queue(self):
454
+        no_delay = self.sm_day_state.previous_state_was(self.sm_day_state.STATE_IDLE)
455
+        # SLEEP
456
+        self._queue.enqueue(1, self.__queue_wrapper__, self.__pf__.set_bake_house, False)
457
+        self._queue.enqueue(2, self.wait, 0 if no_delay else 5 * 60)
458
+        self._queue.enqueue(3, self.__queue_wrapper__, self.__pf__.set_bakery, False)
459
+        self._queue.enqueue(4, self.wait, 0 if no_delay else 9 * 60)
460
+        self._queue.enqueue(5, self.__queue_wrapper__, self.__pf__.set_ploenlein, False)
461
+        self._queue.enqueue(6, self.wait, 0 if no_delay else 9 * 60)
462
+        self._queue.enqueue(7, self.__queue_wrapper__, self.__pf__.set_mill, False)
463
+        self._queue.enqueue(8, self.wait, 0 if no_delay else 6 * 60)
464
+        self._queue.enqueue(9, self.__queue_wrapper__, self.__pf__.set_reese_house, False)
465
+
466
+    def task_1s(self, task_inst):
467
+        self.sm_mode.work()
468
+        self.sm_day_state.work()
469
+
470
+    def join(self):
471
+        self._task_1s.join()
472
+        self._queue.join()
473
+
474
+    def stop(self):
475
+        self._task_1s.stop()
476
+        self._queue.stop()
477
+
478
+    def __del__(self):
479
+        self.stop()

+ 1
- 0
protocol

1
+Subproject commit 6ac1b584fb53aeb58c1b9809fff306a56dd32400

+ 1
- 0
report

1
+Subproject commit 98fea3a4d45ba906d08a8ebc90525fb3592f06be

+ 2
- 0
requirements.txt Ver fichero

1
+pifacecommon
2
+pifacedigitalio

+ 1
- 0
socket_protocol

1
+Subproject commit 127ef51719f2fdbc6699f7e0751f9d92f6773f0f

+ 1
- 0
state_machine

1
+Subproject commit ddf94e9881ae211dcbab9d6e1ba1ac3d7b8919c4

+ 1
- 0
stringtools

1
+Subproject commit b86248c1a3a3456e489e007857271106e1ecf090

+ 1
- 0
task

1
+Subproject commit d3fbc5934051ab165723ca37e8f02596329f174a

+ 1
- 0
tcp_socket

1
+Subproject commit 62f13e2d878b09b81d094c2f77dbd830d72be2c7

Loading…
Cancelar
Guardar