led.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2023 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 or simulate
  51. if not enabled:
  52. self.set = self.stop = self.noop
  53. return
  54. self.ev = threading.Event()
  55. self.led_thread = None
  56. for board_id,board in self.boards.items():
  57. if board_id == 'dummy' and not simulate:
  58. continue
  59. try: os.stat(board.status)
  60. except: pass
  61. else: break
  62. else:
  63. from .util import die
  64. die( 'NoLEDSupport', 'Control files not found! LED control not supported on this system' )
  65. msg(f'{board.name} board detected')
  66. if self.debug:
  67. msg(fmt(f"""
  68. Status file: {board.status}
  69. Trigger file: {board.trigger}
  70. """,indent=' ',strip_char='\t'))
  71. def check_access(fn,desc,init_val=None):
  72. try:
  73. if not init_val:
  74. with open(fn) as fp:
  75. init_val = fp.read().strip()
  76. with open(fn,'w') as fp:
  77. fp.write(f'{init_val}\n')
  78. return True
  79. except PermissionError:
  80. from .util import die
  81. die(2,'\n'+fmt(f"""
  82. You do not have access to the {desc} file
  83. To allow access, run the following command:
  84. sudo chmod 0666 {fn}
  85. """,indent=' ',strip_char='\t'))
  86. check_access(board.status,desc='status LED control')
  87. if board.trigger:
  88. check_access(board.trigger,desc='LED trigger',init_val=board.trigger_states[0])
  89. self.board = board
  90. @classmethod
  91. def create_dummy_control_files(cls):
  92. db = cls.boards['dummy']
  93. with open(db.status,'w') as fp:
  94. fp.write('0\n')
  95. with open(db.trigger,'w') as fp:
  96. fp.write(db.trigger_states[1]+'\n')
  97. @classmethod
  98. def delete_dummy_control_files(cls):
  99. db = cls.boards['dummy']
  100. os.unlink(db.status)
  101. os.unlink(db.trigger)
  102. def noop(self,*args,**kwargs): pass
  103. def ev_sleep(self,secs):
  104. self.ev.wait(secs)
  105. return self.ev.is_set()
  106. def led_loop(self,on_secs,off_secs):
  107. if self.debug:
  108. msg(f'led_loop({on_secs},{off_secs})')
  109. if not on_secs:
  110. with open(self.board.status,'w') as fp:
  111. fp.write('0\n')
  112. while True:
  113. if self.ev_sleep(3600):
  114. return
  115. while True:
  116. for s_time,val in ((on_secs,255),(off_secs,0)):
  117. if self.debug:
  118. msg_r(('^','+')[bool(val)])
  119. with open(self.board.status,'w') as fp:
  120. fp.write(f'{val}\n')
  121. if self.ev_sleep(s_time):
  122. if self.debug:
  123. msg('\n')
  124. return
  125. def set(self,state):
  126. lt = namedtuple('led_timings',['on_secs','off_secs'])
  127. timings = {
  128. 'off': lt( 0, 0 ),
  129. 'standby': lt( 2.2, 0.2 ),
  130. 'busy': lt( 0.06, 0.06 ),
  131. 'error': lt( 0.5, 0.5 ) }
  132. if self.led_thread:
  133. self.ev.set()
  134. self.led_thread.join()
  135. self.ev.clear()
  136. if self.debug:
  137. msg(f'Setting LED state to {state!r}')
  138. self.led_thread = threading.Thread(target=self.led_loop,name='LED loop',args=timings[state])
  139. self.led_thread.start()
  140. def stop(self):
  141. self.set('off')
  142. self.ev.set()
  143. self.led_thread.join()
  144. if self.debug:
  145. msg('Stopping LED')
  146. if self.board.trigger:
  147. with open(self.board.trigger,'w') as fp:
  148. fp.write(self.board.trigger_states[1]+'\n')