1#!/usr/bin/env python3

2

3 import sys

4 import re

5 import time

6 import logging

7 import argparse

8 from pathlib import Path

9 from datetime import datetime

10

11

12 class SysFsLEDController:

13

14 SysFsLEDPath = "/sys/class/leds"

15

16 def __init__(self, name, on_value="0", off_value="0"):

17 self.led_name = name

18 self.led_node = Path(self.SysFsLEDPath).joinpath(name)

19 self.brightness_node = self.led_node.joinpath("brightness")

20 self.max_brightness_node = self.led_node.joinpath("max_brightness")

21 self.trigger_node = self.led_node.joinpath("trigger")

22 self.initial_state = {"trigger": None, "brightness": None}

23 self._on_value = on_value

24 self._off_value = off_value

25

26 def __enter__(self):

27 self.setup()

28 return self

29

30 def __exit__(self, type, value, traceback):

31 self.teardown()

32

33 def setup(self):

34 logging.debug("setup action for LED testing")

35

36 if int(self._on_value) > int(self.max_brightness):

37 raise ValueError("brightness value greater than max brightness")

38 if self._on_value == "0":

39 self._on_value = self.max_brightness

40

41 # Get the initial value of trigger and brightness

42 self._get_initial_state()

43 # Set the trigger type to none

44 self.trigger = "none"

45 self.off()

46

47 def teardown(self):

48 logging.debug("teardown action for LED testing")

49 initial_state = self.initial_state

50 self.brightness = initial_state["brightness"]

51 self.trigger = initial_state["trigger"]

52

53 def _node_exists(self, node):

54 if node.exists() is False:

55 raise FileNotFoundError("{} file not exists".format(str(node)))

56

57 def _read_node(self, node):

58 self._node_exists(node)

59 return node.read_text().strip("\n")

60

61 def _write_node(self, node, value, check=True):

62

63 self._node_exists(node)

64 node.write_text(value)

65 if check and self._read_node(node) != value:

66 raise ValueError(

67 "Unable to change the value of {} file".format(str(node))

68 )

69

70 @property

71 def brightness(self):

72 return self._read_node(self.brightness_node)

73

74 @brightness.setter

75 def brightness(self, value):

76 logging.debug("set brightness to %s for %s LED", value, self.led_name)

77 self._write_node(self.brightness_node, value)

78

79 @property

80 def max_brightness(self):

81 return self._read_node(self.max_brightness_node)

82

83 @property

84 def trigger(self):

85 return self._read_node(self.trigger_node)

86

87 @trigger.setter

88 def trigger(self, value):

89 logging.debug(

90 "set trigger action to {} for {} LED".format(value, self.led_name)

91 )

92 # The read value from trigger node is all supported trigger type

93 # So skip the check

94 self._write_node(self.trigger_node, value, False)

95

96 def _get_initial_state(self):

97 match = re.search(r"\[([\w-]+)\]", self.trigger)

98 if match:

99 self.initial_state["trigger"] = match.groups()[0]

100

101 self.initial_state["brightness"] = self.brightness

102

103 def on(self):

104 logging.debug("turn on {} LED".format(self.led_name))

105 self.brightness = self._on_value

106

107 def off(self):

108 logging.debug("turn off {} LED".format(self.led_name))

109 self.brightness = self._off_value

110

111 def blinking(self, duration=10, interval=1):

112 logging.debug("set {} LED blinking".format(self.led_name))

113 start_time = datetime.now()

114 while (datetime.now() - start_time).total_seconds() <= duration:

115 self.on()

116 time.sleep(interval)

117 self.off()

118 time.sleep(interval)

119

120

121 def register_arguments():

122 parser = argparse.ArgumentParser(

123 formatter_class=argparse.RawDescriptionHelpFormatter,

124 description="LED Tests",

125 )

126 parser.add_argument("-n", "--name", required=True, type=str)

127 parser.add_argument("-d", "--duration", type=int, default=5)

128 parser.add_argument("-i", "--interval", type=int, default=0.5)

129 parser.add_argument("--on-value", type=int, default="0")

130 parser.add_argument("--off-value", type=int, default="0")

131 parser.add_argument(

132 "--debug",

133 action="store_true",

134 help="Turn on debug level output for extra info during test run.",

135 )

136

137 args = parser.parse_args()

138 return args

139

140

141 if __name__ == "__main__":

142

143 root_logger = logging.getLogger()

144 root_logger.setLevel(logging.INFO)

145 logger_format = "%(asctime)s %(levelname)-8s %(message)s"

146 date_format = "%Y-%m-%d %H:%M:%S"

147

148 # Log DEBUG and INFO to stdout, others to stderr

149 stdout_handler = logging.StreamHandler(sys.stdout)

150 stdout_handler.setFormatter(logging.Formatter(logger_format, date_format))

151

152 stderr_handler = logging.StreamHandler(sys.stderr)

153 stderr_handler.setFormatter(logging.Formatter(logger_format, date_format))

154

155 stdout_handler.setLevel(logging.DEBUG)

156 stderr_handler.setLevel(logging.WARNING)

157

158 # Add a filter to the stdout handler to limit log records to

159 # INFO level and below

160 stdout_handler.addFilter(lambda record: record.levelno <= logging.INFO)

161

162 root_logger.addHandler(stderr_handler)

163 root_logger.addHandler(stdout_handler)

164

165 args = register_arguments()

166 if args.debug:

167 root_logger.setLevel(logging.DEBUG)

168

169 logging.info("# Start LED testing")

170 logging.info(

171 (

172 "# Set the %s LED blinking around %d seconds"

173 "with %f seconds blink interval"

174 ),

175 args.name,

176 args.duration,

177 args.interval,

178 )

179

180 with SysFsLEDController(

181 args.name, str(args.on_value), str(args.off_value)

182 ) as led_controller:

183 led_controller.blinking(args.duration, args.interval)