Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 113 lines (90 sloc) 3.8 KB
#!/usr/bin/env python3
import itertools as it, operator as op, functools as ft
import os, sys, pathlib, time
class BlinkConfig:
leds_root = '/sys/class/leds'
duration_factor = 1.0 # multiplies values written to "duration"
bit_repr = 1300, 100, 700
interval = 1.5, 5.0
interval_mark = 100, 100, 5
def file_access_wrap(func, checks=12, timeout=1.0):
for n in range(checks, -1, -1):
try: return func()
except OSError: pass
if checks <= 0: break
if n: time.sleep(timeout / checks)
else: raise OSError('Access failed', func, timeout)
sysfs_node = lambda n: file_access_wrap(ft.partial(open, n, 'wb', buffering=0))
sysfs_write = lambda dst, data, end=b'\n': \
file_access_wrap(ft.partial(dst.write, data + (end or b'')))
def main(args=None, conf=None):
if not conf: conf = BlinkConfig()
import argparse
parser = argparse.ArgumentParser(
description='Blink passed port argument using linux led sysfs interface.',
epilog='Example: `led-blink-arg -f -l led0 -n 2/4-2200'
' -- some-arg 2213 yada yada` (will blink 13 in 4 bits on led0).')
parser.add_argument('args', nargs='+',
help='Any arguments to be used with e.g. -n/--arg-num option.')
parser.add_argument('-f', '--fork', action='store_true',
help='Fork and exit in main pid after start, i.e. daemonize.')
parser.add_argument('-l', '--led',
metavar='name', required=True,
help=f'Name of the led under {conf.leds_root} to use.')
parser.add_argument('-c', '--conf', metavar='json-object',
help='Overrides for configuration keys, as a single json object.'
' See BlinkConfig class in the script for the list of used keys/values.'
' Example: {"bit_repr": [2000, 200, 1400], "interval": [2.0, 10.0]}')
parser.add_argument('-n', '--arg-num', metavar='n/bits-dec',
help='Argument ("1" for first) to use as a number for blinking.'
' Can include "/bits" suffix to blink only specified number of last bits.'
' Can also include "-dec" suffix to decrement value by specified number first.')
parser.add_argument('-d', '--debug',
action='store_true', help='Verbose operation mode.')
opts = parser.parse_args(sys.argv[1:] if args is None else args)
if not opts.arg_num:
parser.error('No blink-control options (e.g. -n/--arg-num) specified')
if opts.conf:
import json
for k, v in json.loads(opts.conf).items():
if k.startswith('_'): continue
setattr(conf, k, v)
os.chdir(pathlib.Path(conf.leds_root) / opts.led)
if opts.fork and os.fork(): return
n = opts.arg_num
n, bits = n.split('/', 1) if '/' in n else (n, None)
bits, dec = bits.split('-', 1) if bits and '-' in bits else (bits, 0)
value = int(opts.args[int(n)-1])
bits = int(bits) if bits else value.bit_length()
if dec: value -= int(dec)
delay_pre, delay_post = conf.interval
bit_1, bit_0, bit_delay = conf.bit_repr
flash_on, flash_delay, flash_count = conf.interval_mark
sysfs_time_k = conf.duration_factor
with sysfs_node('trigger') as dst:
sysfs_write(dst, b'transient')
with sysfs_node('duration') as led_time, \
sysfs_node('state') as led_set, sysfs_node('activate') as led_run:
sysfs_write(led_set, b'1')
while True:
sysfs_write(led_time, str(int(flash_on * sysfs_time_k)).encode())
for n in range(flash_count):
sysfs_write(led_run, b'1')
time.sleep((flash_on + flash_delay) / 1000.0)
v = value
if opts.debug: print(f'Value: {v:b}')
for n in range(bits):
bit, v = v & 1, v >> 1
delay = bit_1 if bit else bit_0
if opts.debug: print(f' {v:b} {bit} [{delay}ms]')
sysfs_write(led_time, str(int(delay * sysfs_time_k)).encode())
sysfs_write(led_run, b'1')
time.sleep((delay + bit_delay) / 1000.0)
finally: sysfs_write(dst, b'none')
if __name__ == '__main__':
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)