led.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. led: Control the LED on a single-board computer
  20. """
  21. import sys,time
  22. from mmgen.common import *
  23. import threading
  24. class LEDControl:
  25. binfo = namedtuple('board_info',['name','status','trigger','trigger_states'])
  26. boards = {
  27. 'raspi_pi': binfo(
  28. name = 'Raspberry Pi',
  29. status = '/sys/class/leds/led0/brightness',
  30. trigger = '/sys/class/leds/led0/trigger',
  31. trigger_states = ('none','mmc0') ),
  32. 'orange_pi': binfo(
  33. name = 'Orange Pi (Armbian)',
  34. status = '/sys/class/leds/orangepi:red:status/brightness',
  35. trigger = None,
  36. trigger_states = None ),
  37. 'rock_pi': binfo(
  38. name = 'Rock Pi (Armbian)',
  39. status = '/sys/class/leds/status/brightness',
  40. trigger = '/sys/class/leds/status/trigger',
  41. trigger_states = ('none','heartbeat') ),
  42. 'dummy': binfo(
  43. name = 'Fake',
  44. status = '/tmp/led_status',
  45. trigger = '/tmp/led_trigger',
  46. trigger_states = ('none','original_value') ),
  47. }
  48. def __init__(self,enabled,simulate=False,debug=False):
  49. self.enabled = enabled
  50. self.debug = debug
  51. if not enabled:
  52. self.set = self.stop = self.noop
  53. return
  54. self.ev = threading.Event()
  55. self.led_thread = None
  56. if simulate:
  57. type(self).create_dummy_control_files()
  58. self.debug = True
  59. for board_id,board in self.boards.items():
  60. try: os.stat(board.status)
  61. except: pass
  62. else: break
  63. else:
  64. from mmgen.exception import NoLEDSupport
  65. raise NoLEDSupport('Control files not found! LED control not supported on this system')
  66. msg(f'{board.name} board detected')
  67. if self.debug:
  68. msg(fmt(f"""
  69. Status file: {board.status}
  70. Trigger file: {board.trigger}
  71. """,indent=' ',strip_char='\t'))
  72. if board_id == 'dummy' and not simulate:
  73. if g.test_suite:
  74. msg('Warning: no simulation requested but dummy LED control files detected')
  75. self.debug = True
  76. else:
  77. die(1,fmt(f"""
  78. No simulation requested but dummy LED control files detected:
  79. {board.status}
  80. {board.trigger}
  81. You may wish to remove them and restart
  82. """,indent=' ',strip_char='\t'))
  83. def check_access(fn,desc,init_val=None):
  84. try:
  85. if not init_val:
  86. with open(fn) as fp:
  87. init_val = fp.read().strip()
  88. with open(fn,'w') as fp:
  89. fp.write(f'{init_val}\n')
  90. return True
  91. except PermissionError:
  92. ydie(1,'\n'+fmt(f"""
  93. You do not have access to the {desc} file
  94. To allow access, run the following command:
  95. sudo chmod 0666 {fn}
  96. """,indent=' ',strip_char='\t'))
  97. check_access(board.status,desc='status LED control')
  98. if board.trigger:
  99. check_access(board.trigger,desc='LED trigger',init_val=board.trigger_states[0])
  100. self.board = board
  101. @classmethod
  102. def create_dummy_control_files(cls):
  103. db = cls.boards['dummy']
  104. with open(db.status,'w') as fp:
  105. fp.write('0\n')
  106. with open(db.trigger,'w') as fp:
  107. fp.write(db.trigger_states[1]+'\n')
  108. def noop(self,*args,**kwargs): pass
  109. def ev_sleep(self,secs):
  110. self.ev.wait(secs)
  111. return self.ev.isSet()
  112. def led_loop(self,on_secs,off_secs):
  113. if self.debug:
  114. msg(f'led_loop({on_secs},{off_secs})')
  115. if not on_secs:
  116. with open(self.board.status,'w') as fp:
  117. fp.write('0\n')
  118. while True:
  119. if self.ev_sleep(3600):
  120. return
  121. while True:
  122. for s_time,val in ((on_secs,255),(off_secs,0)):
  123. if self.debug:
  124. msg_r(('^','+')[bool(val)])
  125. with open(self.board.status,'w') as fp:
  126. fp.write(f'{val}\n')
  127. if self.ev_sleep(s_time):
  128. if self.debug:
  129. msg('\n')
  130. return
  131. def set(self,state):
  132. lt = namedtuple('led_timings',['on_secs','off_secs'])
  133. timings = {
  134. 'off': lt( 0, 0 ),
  135. 'standby': lt( 2.2, 0.2 ),
  136. 'busy': lt( 0.06, 0.06 ),
  137. 'error': lt( 0.5, 0.5 ) }
  138. if self.led_thread:
  139. self.ev.set()
  140. self.led_thread.join()
  141. self.ev.clear()
  142. if self.debug:
  143. msg(f'Setting LED state to {state!r}')
  144. self.led_thread = threading.Thread(target=self.led_loop,name='LED loop',args=timings[state])
  145. self.led_thread.start()
  146. def stop(self):
  147. self.set('off')
  148. self.ev.set()
  149. self.led_thread.join()
  150. if self.debug:
  151. msg('Stopping LED')
  152. if self.board.trigger:
  153. with open(self.board.trigger,'w') as fp:
  154. fp.write(self.board.trigger_states[1]+'\n')