Browse Source

new LEDControl class

The MMGen Project 4 years ago
parent
commit
5ba2f51e
1 changed files with 184 additions and 0 deletions
  1. 184 0
      mmgen/led.py

+ 184 - 0
mmgen/led.py

@@ -0,0 +1,184 @@
+#!/usr/bin/env python3
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2020 The MMGen Project <mmgen@tuta.io>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+led: Control the LED on a single-board computer
+"""
+
+import sys,time
+from mmgen.common import *
+import threading
+
+class LEDControl:
+
+	binfo = namedtuple('board_info',['name','status','trigger','trigger_states'])
+	boards = {
+		'raspi_pi': binfo(
+			name    = 'Raspberry Pi',
+			status  = '/sys/class/leds/led0/brightness',
+			trigger = '/sys/class/leds/led0/trigger',
+			trigger_states = ('none','mmc0') ),
+		'orange_pi': binfo(
+			name    = 'Orange Pi',
+			status  = '/sys/class/leds/orangepi:red:status/brightness',
+			trigger = None,
+			trigger_states = None ),
+#		'rock_pi': binfo( # TODO
+#			name    = 'Rock Pi',
+#			status  = '/sys/class/leds/user-led1/brightness',
+#			trigger = '/sys/class/leds/user-led1/trigger',
+#			trigger_states = ('none','heartbeat') ),
+		'dummy': binfo(
+			name    = 'Fake',
+			status  = '/tmp/led_status',
+			trigger = '/tmp/led_trigger',
+			trigger_states = ('none','original_value') ),
+	}
+
+	def __init__(self,enabled,simulate=False,debug=False):
+
+		self.enabled = enabled
+		self.debug = debug
+
+		if not enabled:
+			self.set = self.stop = self.noop
+			return
+
+		self.ev = threading.Event()
+		self.led_thread = None
+
+		if simulate:
+			type(self).create_dummy_control_files()
+			self.debug = True
+
+		for board_id,board in self.boards.items():
+			try: os.stat(board.status)
+			except: pass
+			else: break
+		else:
+			from mmgen.exception import NoLEDSupport
+			raise NoLEDSupport('Control files not found!  LED control not supported on this system')
+
+		msg(f'{board.name} board detected')
+
+		if self.debug:
+			msg(fmt(f"""
+			Status file:  {board.status}
+			Trigger file: {board.trigger}
+			""",indent='  ',strip_char='\t'))
+
+		if board_id == 'dummy' and not simulate:
+			if g.test_suite:
+				msg('Warning: no simulation requested but dummy LED control files detected')
+				self.debug = True
+			else:
+				die(1,fmt(f"""
+				No simulation requested but dummy LED control files detected:
+
+				  {board.status}
+				  {board.trigger}
+
+				You may wish to remove them and restart
+				""",indent='  ',strip_char='\t'))
+
+		def check_access(fn,desc,init_val=None):
+			try:
+				iv = init_val or open(fn).read().strip()
+				open(fn,'w').write(f'{iv}\n')
+				return True
+			except:
+				msg('\n'+fmt(f"""
+					You do not have access to the {desc} file
+					To allow access, run the following command:
+
+					    sudo chmod 0666 {fn}
+				""",indent='  ',strip_char='\t'))
+				return False
+
+		if not check_access(board.status,desc='status LED control'):
+			sys.exit(1)
+
+		if board.trigger:
+			if not check_access(board.trigger,desc='LED trigger',init_val=board.trigger_states[0]):
+				sys.exit(1)
+
+		self.board = board
+
+	@classmethod
+	def create_dummy_control_files(cls):
+		db = cls.boards['dummy']
+		open(db.status,'w').write('0\n')
+		open(db.trigger,'w').write(db.trigger_states[1]+'\n')
+
+	def noop(self,*args,**kwargs): pass
+
+	def ev_sleep(self,secs):
+		self.ev.wait(secs)
+		return self.ev.isSet()
+
+	def led_loop(self,on_secs,off_secs):
+
+		if self.debug:
+			msg(f'led_loop({on_secs},{off_secs})')
+
+		if not on_secs:
+			open(self.board.status,'w').write('0\n')
+			while True:
+				if self.ev_sleep(3600):
+					return
+
+		while True:
+			for s_time,val in ((on_secs,255),(off_secs,0)):
+				if self.debug:
+					msg_r(('^','+')[bool(val)])
+				open(self.board.status,'w').write(f'{val}\n')
+				if self.ev_sleep(s_time):
+					if self.debug:
+						msg('\n')
+					return
+
+	def set(self,state):
+		lt = namedtuple('led_timings',['on_secs','off_secs'])
+		timings = {
+			'off':     lt( 0, 0 ),
+			'standby': lt( 2.2, 0.2 ),
+			'busy':    lt( 0.06, 0.06 ),
+			'error':   lt( 0.5, 0.5 ) }
+
+		if self.led_thread:
+			self.ev.set()
+			self.led_thread.join()
+			self.ev.clear()
+
+		if self.debug:
+			msg(f'Setting LED state to {state!r}')
+
+		self.led_thread = threading.Thread(target=self.led_loop,name='LED loop',args=timings[state])
+		self.led_thread.start()
+
+	def stop(self):
+
+		self.set('off')
+		self.ev.set()
+		self.led_thread.join()
+
+		if self.debug:
+			msg('Stopping LED')
+
+		if self.board.trigger:
+			open(self.board.trigger,'w').write(self.board.trigger_states[1]+'\n')