Modify build and archive Argon40 scripts.

Move all of the argon40 scripts to archive... these scripts should not
be used as is, they are simply copies of the originals so I can track
changes to see if there is anything important that needs to be
transfered to the battery driver.
This commit is contained in:
Jeff Curless
2026-02-02 13:02:10 -05:00
parent c79f02f489
commit 2b178485ae
652 changed files with 19 additions and 6 deletions

View File

@@ -0,0 +1,180 @@
#!/bin/bash
echo "----------------------"
echo " Argon Uninstall Tool"
echo "----------------------"
echo -n "Press Y to continue:"
read -n 1 confirm
echo
if [ "$confirm" = "y" ]
then
confirm="Y"
fi
if [ "$confirm" != "Y" ]
then
echo "Cancelled"
exit
fi
destfoldername=$USERNAME
if [ -z "$destfoldername" ]
then
destfoldername=$USER
fi
if [ "$destfoldername" = "root" ]
then
destfoldername=""
fi
if [ -z "$destfoldername" ]
then
destfoldername="pi"
fi
shortcutfile="/home/$destfoldername/Desktop/argonone-config.desktop"
if [ -f "$shortcutfile" ]; then
sudo rm $shortcutfile
if [ -f "/usr/share/pixmaps/ar1config.png" ]; then
sudo rm /usr/share/pixmaps/ar1config.png
fi
if [ -f "/usr/share/pixmaps/argoneon.png" ]; then
sudo rm /usr/share/pixmaps/argoneon.png
fi
fi
shortcutfile="/home/$destfoldername/Desktop/argononeup.desktop"
if [ -f "$shortcutfile" ]; then
sudo rm $shortcutfile
fi
INSTALLATIONFOLDER=/etc/argon
argononefanscript=$INSTALLATIONFOLDER/argononed.py
if [ -f $argononefanscript ]; then
sudo systemctl stop argononed.service
sudo systemctl disable argononed.service
# Turn off the fan
/usr/bin/python3 $argononefanscript FANOFF
# Remove files
sudo rm /lib/systemd/system/argononed.service
fi
argononeupscript=$INSTALLATIONFOLDER/argononeupd.py
if [ -f $argononeupscript ]; then
sudo systemctl stop argononeupd.service
sudo systemctl disable argononeupd.service
# Remove files
sudo rm /lib/systemd/system/argononeupd.service
fi
# Remove RTC if any
argoneonrtcscript=$INSTALLATIONFOLDER/argoneond.py
if [ -f "$argoneonrtcscript" ]
then
# Disable Services
sudo systemctl stop argoneond.service
sudo systemctl disable argoneond.service
# No need for sudo
/usr/bin/python3 $argoneonrtcscript CLEAN
/usr/bin/python3 $argoneonrtcscript SHUTDOWN
# Remove files
sudo rm /lib/systemd/system/argoneond.service
fi
# Remove UPS daemon if any
argononeupsscript=$INSTALLATIONFOLDER/argononeupsd.py
if [ -f "$argononeupsscript" ]
then
#sudo rmmod argonbatteryicon
# Disable Services
sudo systemctl stop argononeupsd.service
sudo systemctl disable argononeupsd.service
sudo systemctl stop argonupsrtcd.service
sudo systemctl disable argonupsrtcd.service
# Remove files
sudo rm /lib/systemd/system/argononeupsd.service
sudo rm /lib/systemd/system/argonupsrtcd.service
find "/home" -maxdepth 1 -type d | while read line; do
shortcutfile="$line/Desktop/argonone-ups.desktop"
if [ -f "$shortcutfile" ]; then
sudo rm $shortcutfile
fi
done
fi
# Remove UPS daemon if any
argononeupsscript=$INSTALLATIONFOLDER/argononeupd.py
if [ -f "$argononeupsscript" ]
then
#sudo rmmod argonbatteryicon
# Disable Services
sudo systemctl stop argononeupd.service
sudo systemctl disable argononeupd.service
for tmpuser in `awk -F: '{ if ($3 >= 1000) print $1 }' /etc/passwd`
do
if [ "$tmpuser" != "nobody" ]
then
sudo -u "$tmpuser" systemctl --user stop argononeupduser.service
sudo -u "$tmpuser" systemctl --user disable argononeupduser.service
fi
done
systemctl --user stop argononeupduser.service
systemctl --user disable argononeupduser.service
# Remove files
sudo rm /lib/systemd/system/argononeupd.service
sudo rm /etc/systemd/user/argononeupduser.service
find "/home" -maxdepth 1 -type d | while read line; do
shortcutfile="$line/Desktop/argononeup.desktop"
if [ -f "$shortcutfile" ]; then
sudo rm $shortcutfile
fi
done
fi
if [ -f "/usr/bin/argon-config" ]
then
sudo rm /usr/bin/argon-config
fi
if [ -f "/usr/bin/argonone-config" ]
then
sudo rm /usr/bin/argonone-config
sudo rm /usr/bin/argonone-uninstall
fi
if [ -f "/usr/bin/argonone-ir" ]
then
sudo rm /usr/bin/argonone-ir
fi
# Delete config files
for configfile in argonunits argononed argononed-hdd argoneonrtc argoneonoled argonupsrtc argononeupd
do
if [ -f "/etc/${configfile}.conf" ]
then
sudo rm "/etc/${configfile}.conf"
fi
done
sudo rm /lib/systemd/system-shutdown/argon-shutdown.sh
sudo rm -R -f $INSTALLATIONFOLDER
echo "Removed Argon Services."
echo "Cleanup will complete after restarting the device."

View File

@@ -0,0 +1,14 @@
#!/bin/bash
VERSIONINFO="2601005"
echo "Version $VERSIONINFO"
if [ -z "$1" ]
then
echo
echo "We acknowledge the valuable feedback of the following:"
echo "ghalfacree, NHHiker"
echo
echo "Feel free to join the discussions at https://forum.argon40.com"
echo
fi

View File

@@ -0,0 +1,374 @@
#!/bin/python3
import time
import os
import sys
import signal
import curses
sys.path.append("/etc/argon/")
from argonsysinfo import *
from argonregister import *
############
# Constants
############
COLORPAIRID_DEFAULT=1
COLORPAIRID_LOGO=2
COLORPAIRID_DEFAULTINVERSE=3
COLORPAIRID_ALERT=4
COLORPAIRID_WARNING=5
COLORPAIRID_GOOD=6
INPUTREFRESHMS=100
DISPLAYREFRESHMS=5000
UPS_LOGFILE="/dev/shm/upslog.txt"
###################
# Display Elements
###################
def displaydatetime(stdscr):
try:
curtimenow = time.localtime()
stdscr.addstr(1, 1, time.strftime("%A", curtimenow), curses.color_pair(COLORPAIRID_DEFAULT))
stdscr.addstr(2, 1, time.strftime("%b %d,%Y", curtimenow), curses.color_pair(COLORPAIRID_DEFAULT))
stdscr.addstr(3, 1, time.strftime("%I:%M%p", curtimenow), curses.color_pair(COLORPAIRID_DEFAULT))
except:
pass
def displayipbattery(stdscr):
try:
displaytextright(stdscr,1, argonsysinfo_getip()+" ", COLORPAIRID_DEFAULT)
except:
pass
try:
status = ""
level = ""
outobj = {}
# Load status
fp = open(UPS_LOGFILE, "r")
logdata = fp.read()
alllines = logdata.split("\n")
ctr = 0
while ctr < len(alllines):
tmpval = alllines[ctr].strip()
curinfo = tmpval.split(":")
if len(curinfo) > 1:
tmpattrib = curinfo[0].lower().split(" ")
# The rest are assumed to be value
outobj[tmpattrib[0]] = tmpval[(len(curinfo[0])+1):].strip()
ctr = ctr + 1
# Map to data
try:
statuslist = outobj["power"].lower().split(" ")
if statuslist[0] == "battery":
tmp_charging = 0
else:
tmp_charging = 1
tmp_battery = int(statuslist[1].replace("%",""))
colorpairidx = COLORPAIRID_DEFAULT
if tmp_charging:
if tmp_battery > 99:
status="Plugged"
level=""
else:
status="Charging"
level=str(tmp_battery)+"%"
else:
status="Battery"
level=str(tmp_battery)+"%"
if tmp_battery <= 20:
colorpairidx = COLORPAIRID_ALERT
elif tmp_battery <= 50:
colorpairidx = COLORPAIRID_WARNING
else:
colorpairidx = COLORPAIRID_GOOD
displaytextright(stdscr,2, status+" ", colorpairidx)
displaytextright(stdscr,3, level+" ", colorpairidx)
except:
pass
except:
pass
def displayramcpu(stdscr, refcpu, rowstart, colstart):
curusage_b = argonsysinfo_getcpuusagesnapshot()
try:
outputlist = []
tmpraminfo = argonsysinfo_getram()
outputlist.append({"title": "ram ", "value": tmpraminfo[1]+" "+tmpraminfo[0]+" Free"})
for cpuname in refcpu:
if cpuname == "cpu":
continue
if refcpu[cpuname]["total"] == curusage_b[cpuname]["total"]:
outputlist.append({"title": cpuname, "value": "Loading"})
else:
total = curusage_b[cpuname]["total"]-refcpu[cpuname]["total"]
idle = curusage_b[cpuname]["idle"]-refcpu[cpuname]["idle"]
outputlist.append({"title": cpuname, "value": str(int(100*(total-idle)/(total)))+"% Used"})
displaytitlevaluelist(stdscr, rowstart, colstart, outputlist)
except:
pass
return curusage_b
def displaytempfan(stdscr, rowstart, colstart):
try:
outputlist = []
try:
if busobj is not None:
fanspeed = argonregister_getfanspeed(busobj)
fanspeedstr = "Off"
if fanspeed > 0:
fanspeedstr = str(fanspeed)+"%"
outputlist.append({"title": "Fan ", "value": fanspeedstr})
except:
pass
# Todo load from config
temperature = "C"
hddtempctr = 0
maxcval = 0
mincval = 200
# Get min/max of hdd temp
hddtempobj = argonsysinfo_gethddtemp()
for curdev in hddtempobj:
if hddtempobj[curdev] < mincval:
mincval = hddtempobj[curdev]
if hddtempobj[curdev] > maxcval:
maxcval = hddtempobj[curdev]
hddtempctr = hddtempctr + 1
cpucval = argonsysinfo_getcputemp()
if hddtempctr > 0:
alltempobj = {"cpu": cpucval,"hdd min": mincval, "hdd max": maxcval}
# Update max C val to CPU Temp if necessary
if maxcval < cpucval:
maxcval = cpucval
displayrowht = 8
displayrow = 8
for curdev in alltempobj:
if temperature == "C":
# Celsius
tmpstr = str(alltempobj[curdev])
if len(tmpstr) > 4:
tmpstr = tmpstr[0:4]
else:
# Fahrenheit
tmpstr = str(32+9*(alltempobj[curdev])/5)
if len(tmpstr) > 5:
tmpstr = tmpstr[0:5]
if len(curdev) <= 3:
outputlist.append({"title": curdev.upper(), "value": tmpstr +temperature})
else:
outputlist.append({"title": curdev.upper(), "value": tmpstr +temperature})
else:
maxcval = cpucval
if temperature == "C":
# Celsius
tmpstr = str(cpucval)
if len(tmpstr) > 4:
tmpstr = tmpstr[0:4]
else:
# Fahrenheit
tmpstr = str(32+9*(cpucval)/5)
if len(tmpstr) > 5:
tmpstr = tmpstr[0:5]
outputlist.append({"title": "Temp", "value": tmpstr +temperature})
displaytitlevaluelist(stdscr, rowstart, colstart, outputlist)
except:
pass
def displaystorage(stdscr, rowstart, colstart):
try:
outputlist = []
tmpobj = argonsysinfo_listhddusage()
for curdev in tmpobj:
outputlist.append({"title": curdev, "value": argonsysinfo_kbstr(tmpobj[curdev]['total'])+ " "+ str(int(100*tmpobj[curdev]['used']/tmpobj[curdev]['total']))+"% Used" })
displaytitlevaluelist(stdscr, rowstart, colstart, outputlist)
except:
pass
##################
# Helpers
##################
# Initialize I2C Bus
bus = argonregister_initializebusobj()
def handle_resize(signum, frame):
# TODO: Not working?
curses.update_lines_cols()
# Ideally redraw here
def displaytitlevaluelist(stdscr, rowstart, leftoffset, curlist):
rowidx = rowstart
while rowidx < curses.LINES and len(curlist) > 0:
curline = ""
tmpitem = curlist.pop(0)
curline = tmpitem["title"]+": "+str(tmpitem["value"])
stdscr.addstr(rowidx, leftoffset, curline)
rowidx = rowidx + 1
def displaytextcentered(stdscr, rownum, strval, colorpairidx = COLORPAIRID_DEFAULT):
leftoffset = 0
numchars = len(strval)
if numchars < 1:
return
elif (numchars > curses.COLS):
leftoffset = 0
strval = strval[0:curses.COLS]
else:
leftoffset = (curses.COLS - numchars)>>1
stdscr.addstr(rownum, leftoffset, strval, curses.color_pair(colorpairidx))
def displaytextright(stdscr, rownum, strval, colorpairidx = COLORPAIRID_DEFAULT):
leftoffset = 0
numchars = len(strval)
if numchars < 1:
return
elif (numchars > curses.COLS):
leftoffset = 0
strval = strval[0:curses.COLS]
else:
leftoffset = curses.COLS - numchars
stdscr.addstr(rownum, leftoffset, strval, curses.color_pair(colorpairidx))
def displaylinebreak(stdscr, rownum, colorpairidx = COLORPAIRID_DEFAULTINVERSE):
strval = " "
while len(strval) < curses.COLS:
strval = strval + " "
stdscr.addstr(rownum, 0, strval, curses.color_pair(colorpairidx))
##################
# Main Loop
##################
def mainloop(stdscr):
try:
# Set up signal handler
signal.signal(signal.SIGWINCH, handle_resize)
maxloopctr = int(DISPLAYREFRESHMS/INPUTREFRESHMS)
sleepsecs = INPUTREFRESHMS/1000
loopctr = maxloopctr
loopmode = True
stdscr = curses.initscr()
# Turn off echoing of keys, and enter cbreak mode,
# where no buffering is performed on keyboard input
curses.noecho()
curses.cbreak()
curses.curs_set(0)
curses.start_color()
#curses.COLOR_BLACK
#curses.COLOR_BLUE
#curses.COLOR_CYAN
#curses.COLOR_GREEN
#curses.COLOR_MAGENTA
#curses.COLOR_RED
#curses.COLOR_WHITE
#curses.COLOR_YELLOW
curses.init_pair(COLORPAIRID_DEFAULT, curses.COLOR_WHITE, curses.COLOR_BLACK)
curses.init_pair(COLORPAIRID_LOGO, curses.COLOR_WHITE, curses.COLOR_RED)
curses.init_pair(COLORPAIRID_DEFAULTINVERSE, curses.COLOR_BLACK, curses.COLOR_WHITE)
curses.init_pair(COLORPAIRID_ALERT, curses.COLOR_RED, curses.COLOR_BLACK)
curses.init_pair(COLORPAIRID_WARNING, curses.COLOR_YELLOW, curses.COLOR_BLACK)
curses.init_pair(COLORPAIRID_GOOD, curses.COLOR_GREEN, curses.COLOR_BLACK)
stdscr.nodelay(True)
refcpu = argonsysinfo_getcpuusagesnapshot()
while True:
try:
key = stdscr.getch()
# if key == ord('x') or key == ord('X'):
# Any key
if key > 0:
break
except curses.error:
# No key was pressed
pass
loopctr = loopctr + 1
if loopctr >= maxloopctr:
loopctr = 0
# Screen refresh loop
# Clear screen
stdscr.clear()
displaytextcentered(stdscr, 0, " ", COLORPAIRID_LOGO)
displaytextcentered(stdscr, 1, " Argon40 Dashboard ", COLORPAIRID_LOGO)
displaytextcentered(stdscr, 2, " ", COLORPAIRID_LOGO)
displaytextcentered(stdscr, 3, "Press any key to close")
displaylinebreak(stdscr, 5)
# Display Elements
displaydatetime(stdscr)
displayipbattery(stdscr)
# Data Columns
rowstart = 7
colstart = 20
refcpu = displayramcpu(stdscr, refcpu, rowstart, colstart)
displaystorage(stdscr, rowstart, colstart+30)
displaytempfan(stdscr, rowstart, colstart+60)
# Main refresh even
stdscr.refresh()
time.sleep(sleepsecs)
except Exception as initerr:
pass
##########
# Cleanup
##########
try:
curses.curs_set(1)
curses.echo()
curses.nocbreak()
curses.endwin()
except Exception as closeerr:
pass
try:
curses.wrapper(mainloop)
except Exception as wrapperr:
pass

View File

@@ -0,0 +1,817 @@
#!/usr/bin/python3
#
# This script monitor battery via ic2 and keyboard events.
#
# Additional comments are found in each function below
#
#
from evdev import InputDevice, categorize, ecodes, list_devices
from select import select
import subprocess
import sys
import os
import time
from threading import Thread
from queue import Queue
UPS_LOGFILE="/dev/shm/upslog.txt"
KEYBOARD_LOCKFILE="/dev/shm/argononeupkeyboardlock.txt"
KEYCODE_BRIGHTNESSUP = "KEY_BRIGHTNESSUP"
KEYCODE_BRIGHTNESSDOWN = "KEY_BRIGHTNESSDOWN"
KEYCODE_VOLUMEUP = "KEY_VOLUMEUP"
KEYCODE_VOLUMEDOWN = "KEY_VOLUMEDOWN"
KEYCODE_PAUSE = "KEY_PAUSE"
KEYCODE_MUTE = "KEY_MUTE"
###################
# Utilty Functions
###################
# Debug Logger
def debuglog(typestr, logstr):
return
# try:
# DEBUGFILE="/dev/shm/argononeupkeyboarddebuglog.txt"
# tmpstrpadding = " "
# with open(DEBUGFILE, "a") as txt_file:
# txt_file.write("["+time.asctime(time.localtime(time.time()))+"] "+typestr.upper()+" "+logstr.strip().replace("\n","\n"+tmpstrpadding)+"\n")
# except:
# pass
def runcmdlist(key, cmdlist):
try:
cmdresult = subprocess.run(cmdlist,
capture_output=True,
text=True,
check=True
)
#debuglog(key+"-result-output",str(cmdresult.stdout))
if cmdresult.stderr:
debuglog(key+"-result-error",str(cmdresult.stderr))
#debuglog(key+"-result-code",str(cmdresult.returncode))
except subprocess.CalledProcessError as e:
debuglog(key+"-error-output",str(e.stdout))
if e.stderr:
debuglog(key+"-error-error",str(e.stderr))
debuglog(key+"-error-code",str(e.returncode))
except FileNotFoundError:
debuglog(key+"-error-filenotfound","Command Not Found")
except Exception as othererr:
try:
debuglog(key+"-error-other", str(othererr))
except:
debuglog(key+"-error-other", "Other Error")
def createlockfile(fname):
# try:
# if os.path.isfile(fname):
# return True
# except Exception as checklockerror:
# try:
# debuglog("keyboard-lock-error", str(checklockerror))
# except:
# debuglog("keyboard-lock-error", "Error Checking Lock File")
# try:
# with open(fname, "w") as txt_file:
# txt_file.write(time.asctime(time.localtime(time.time()))+"\n")
# except Exception as lockerror:
# try:
# debuglog("keyboard-lock-error", str(lockerror))
# except:
# debuglog("keyboard-lock-error", "Error Creating Lock File")
return False
def deletelockfile(fname):
# try:
# os.remove(fname)
# except Exception as lockerror:
# try:
# debuglog("keyboard-lock-error", str(lockerror))
# except:
# debuglog("keyboard-lock-error", "Error Removing Lock File")
return True
# System Notifcation
def notifymessage(message, iscritical):
if not isinstance(message, str) or len(message.strip()) == 0:
return
wftype="notify"
if iscritical:
wftype="critical"
os.system("export SUDO_UID=1000; wfpanelctl "+wftype+" \""+message+"\"")
os.system("export DISPLAY=:0.0; lxpanelctl notify \""+message+"\"")
#############
# Battery (copied)
#############
def battery_loadlogdata():
# status, version, time, schedule
outobj = {}
try:
fp = open(UPS_LOGFILE, "r")
logdata = fp.read()
alllines = logdata.split("\n")
ctr = 0
while ctr < len(alllines):
tmpval = alllines[ctr].strip()
curinfo = tmpval.split(":")
if len(curinfo) > 1:
tmpattrib = curinfo[0].lower().split(" ")
# The rest are assumed to be value
outobj[tmpattrib[0]] = tmpval[(len(curinfo[0])+1):].strip()
ctr = ctr + 1
except Exception as einit:
try:
debuglog("keyboard-battery-error", str(einit))
except:
debuglog("keyboard-battery-error", "Error getting battery status")
#pass
return outobj
def keyboardevent_getdevicepaths():
outlist = []
try:
for path in list_devices():
try:
tmpdevice = InputDevice(path)
keyeventlist = tmpdevice.capabilities().get(ecodes.EV_KEY, [])
# Keyboard has EV_KEY (key) and EV_REP (autorepeat)
if ecodes.KEY_BRIGHTNESSDOWN in keyeventlist and ecodes.KEY_BRIGHTNESSDOWN in keyeventlist:
outlist.append(path)
#debuglog("keyboard-device-keys", path)
#debuglog("keyboard-device-keys", str(keyeventlist))
elif ecodes.KEY_F2 in keyeventlist and ecodes.KEY_F3 in keyeventlist:
# Keyboards with FN key sometimes do not include KEY_BRIGHTNESS in declaration
outlist.append(path)
#debuglog("keyboard-device-keys", path)
#debuglog("keyboard-device-keys", str(keyeventlist))
tmpdevice.close()
except:
pass
except:
pass
return outlist
def keyboardevent_devicechanged(curlist, newlist):
try:
for curpath in curlist:
if curpath not in newlist:
return True
for newpath in newlist:
if newpath not in curlist:
return True
except:
pass
return False
def keyboardevent_getbrigthnesstoolid():
toolid = 0
try:
output = subprocess.check_output(["ddcutil", "--version"], text=True, stderr=subprocess.DEVNULL)
lines = output.splitlines()
if len(lines) > 0:
tmpline = lines[0].strip()
toolid = int(tmpline.split(" ")[1].split(".")[0])
except Exception as einit:
try:
debuglog("keyboard-brightness-tool-error", str(einit))
except:
debuglog("keyboard-brightness-tool-error", "Error getting tool id value")
debuglog("keyboard-brightness-tool", toolid)
return toolid
def keyboardevent_getbrigthnessinfo(toolid, defaultlevel=50):
level = defaultlevel
try:
# VCP code x10(Brightness ): current value = 90, max value = 100
if toolid > 1:
# Disabled dynamic sleep "--disable-dynamic-sleep", "--sleep-multiplier", "0.1"
output = subprocess.check_output(["ddcutil", "--disable-dynamic-sleep", "--sleep-multiplier", "0.1", "getvcp", "10"], text=True, stderr=subprocess.DEVNULL)
else:
output = subprocess.check_output(["ddcutil", "--sleep-multiplier", "0.1", "getvcp", "10"], text=True, stderr=subprocess.DEVNULL)
debuglog("keyboard-brightness-info", output)
level = int(output.split(":")[-1].split(",")[0].split("=")[-1].strip())
except Exception as einit:
try:
debuglog("keyboard-brightness-error", str(einit))
except:
debuglog("keyboard-brightness-error", "Error getting base value")
return {
"level": level
}
def keyboardevent_adjustbrigthness(toolid, baselevel, adjustval=5):
curlevel = baselevel
if adjustval == 0:
return {
"level": baselevel
}
# Moved reading because ddcutil has delay
# try:
# tmpobj = keyboardevent_getbrigthnessinfo(toolid, curlevel)
# curlevel = tmpobj["level"]
# except Exception:
# pass
tmpval = max(10, min(100, curlevel + adjustval))
if tmpval != curlevel:
try:
debuglog("keyboard-brightness", str(curlevel)+"% to "+str(tmpval)+"%")
if toolid > 1:
# Disabled dynamic sleep "--disable-dynamic-sleep", "--sleep-multiplier", "0.1"
runcmdlist("brightness", ["ddcutil", "--disable-dynamic-sleep", "--sleep-multiplier", "0.1", "setvcp", "10", str(tmpval)])
else:
runcmdlist("brightness", ["ddcutil", "--sleep-multiplier", "0.1", "setvcp", "10", str(tmpval)])
notifymessage("Brightness: "+str(tmpval)+"%", False)
except Exception as adjusterr:
try:
debuglog("keyboard-brightness-error", str(adjusterr))
except:
debuglog("keyboard-brightness-error", "Error adjusting value")
return {
"level": curlevel
}
# DEBUG: Checking
#keyboardevent_getbrigthnessinfo(toolid, tmpval)
return {
"level": tmpval
}
def keyboardevent_getvolumesinkid(usedefault=True):
if usedefault == True:
return "@DEFAULT_SINK@"
cursinkid = 0
try:
output = subprocess.check_output(["wpctl", "status"], text=True, encoding='utf-8', stderr=subprocess.DEVNULL)
# Find Audio section
tmpline = ""
foundidx = 0
lines = output.splitlines()
lineidx = 0
while lineidx < len(lines):
tmpline = lines[lineidx].strip()
if tmpline == "Audio":
foundidx = lineidx
break
lineidx = lineidx + 1
if foundidx < 1:
return 0
# Find Sinks section
foundidx = 0
lineidx = lineidx + 1
while lineidx < len(lines):
if "Sinks:" in lines[lineidx]:
foundidx = lineidx
break
elif len(lines[lineidx]) < 1:
break
lineidx = lineidx + 1
if foundidx < 1:
return 0
# Get find default id, or first id
lineidx = lineidx + 1
while lineidx < len(lines):
if "vol:" in lines[lineidx] and "." in lines[lineidx]:
tmpstr = lines[lineidx].split(".")[0]
tmplist = tmpstr.split()
if len(tmplist) > 1:
if tmplist[len(tmplist)-2] == "*":
return int(tmplist[len(tmplist)-1])
if len(tmplist) > 0 and cursinkid < 1:
cursinkid = int(tmplist[len(tmplist)-1])
elif len(lines[lineidx]) < 3:
break
lineidx = lineidx + 1
except Exception as einit:
try:
debuglog("keyboard-volume-error", str(einit))
except:
debuglog("keyboard-volume-error", "Error getting device ID")
return cursinkid
def keyboardevent_getvolumeinfo(deviceidstr="", defaultlevel=50, defaultmuted=0):
muted = defaultmuted
level = defaultlevel
try:
if deviceidstr == "":
audioidstr = str(keyboardevent_getvolumesinkid())
if audioidstr == "0":
debuglog("keyboard-volume-error", "Error getting device id")
return {
"level": defaultmuted,
"muted": defaultlevel
}
deviceidstr = audioidstr
output = subprocess.check_output(["wpctl", "get-volume", deviceidstr], text=True, stderr=subprocess.DEVNULL)
debuglog("keyboard-volume-info", output)
muted = 0
level = 0
# Parse output, examples
# Volume: 0.65
# Volume: 0.55 [MUTED]
outlist = output.split()
if len(outlist) > 0:
# Get last element
tmpstr = outlist[len(outlist)-1]
# Check if muted
if "MUTE" in tmpstr:
muted = 1
if len(outlist) > 1:
tmpstr = outlist[len(outlist)-2]
if tmpstr.endswith("%"):
# Level 100% to 0%
level = int(float(tmpstr[:-1]))
elif tmpstr.replace('.', '').isdigit():
# Level 1.00 to 0.00
level = int(float(tmpstr) * 100.0)
except Exception as einit:
try:
debuglog("keyboard-volume-error", str(einit))
except:
debuglog("keyboard-volume-error", "Error getting base value")
return {
"level": defaultmuted,
"muted": defaultlevel
}
#debuglog("keyboard-volume-get", str(level)+"% Mute:"+str(muted))
return {
"level": level,
"muted": muted
}
def keyboardevent_adjustvolume(baselevel, basemuted, adjustval=5):
curlevel = baselevel
curmuted = basemuted
needsnotification = False
deviceidstr = str(keyboardevent_getvolumesinkid())
if deviceidstr == "0":
debuglog("keyboard-volume-error", "Error getting device id")
return {
"level": baselevel,
"muted": basemuted
}
# try:
# tmpobj = keyboardevent_getvolumeinfo(deviceidstr, curlevel, curmuted)
# curlevel = tmpobj["level"]
# curmuted = tmpobj["muted"]
# except Exception:
# pass
tmpmuted = curmuted
if adjustval == 0:
# Toggle Mute
if curmuted == 0:
tmpmuted = 1
else:
tmpmuted = 0
tmpval = max(10, min(100, curlevel + adjustval))
if tmpval != curlevel:
try:
debuglog("keyboard-volume", str(curlevel)+"% to "+str(tmpval)+"%")
runcmdlist("volume", ["wpctl", "set-volume", deviceidstr, f"{tmpval}%"])
needsnotification = True
tmpmuted = 0
except Exception as adjusterr:
try:
debuglog("keyboard-volume-error", str(adjusterr))
except:
debuglog("keyboard-volume-error", "Error adjusting value")
return {
"level": curlevel,
"muted": curmuted
}
elif adjustval != 0:
# To unmute even if no volume level change
tmpmuted = 0
if tmpmuted != curmuted:
try:
debuglog("keyboard-mute", str(tmpmuted))
runcmdlist("mute", ["wpctl", "set-mute", deviceidstr, str(tmpmuted)])
needsnotification = True
except Exception as adjusterr:
try:
debuglog("keyboard-mute-error", str(adjusterr))
except:
debuglog("keyboard-mute-error", "Error adjusting value")
return {
"level": tmpval,
"muted": curmuted
}
#if tmpmuted == 1:
# notifymessage("Volume: Muted", False)
#else:
# notifymessage("Volume: "+str(tmpval)+"%", False)
# DEBUG: Checking
#keyboardevent_getvolumeinfo(deviceidstr, tmpval, tmpmuted)
return {
"level": tmpval,
"muted": tmpmuted
}
def keyboard_getlayoutfieldvalue(tmpval):
debuglog("keyboard-layout-lang", tmpval)
if "us" in tmpval:
debuglog("keyboard-layout-langout", "us")
return "us"
debuglog("keyboard-layout-langout", "gb")
return "gb" # uk, gb, etc
#return tmpval
def keyboard_getdevicefw(kbdevice):
# info: vendor 0x6080=24704, product 0x8062=32866
try:
if kbdevice.info.vendor == 24704 and kbdevice.info.product == 32866:
# Special HID
return "314"
except Exception as infoerr:
pass
return ""
def keyboardevemt_keyhandler(readq):
ADJUSTTYPE_NONE=0
ADJUSTTYPE_BRIGHTNESS=1
ADJUSTTYPE_VOLUME=2
ADJUSTTYPE_MUTE=3
ADJUSTTYPE_BATTERYINFO=4
DATAREFRESHINTERVALSEC = 10
PRESSWAITINTERVALSEC = 0.5
FIRSTHOLDINTERVALSEC = 0.5
HOLDWAITINTERVALSEC = 0.5
# Get current levels
volumetime = time.time()
curvolumemuted = 0
curvolume = 50
brightnesstime = volumetime
curbrightness = 50
brightnesstoolid = 0
try:
brightnesstoolid = keyboardevent_getbrigthnesstoolid()
except Exception:
brightnesstoolid = 0
pass
try:
tmpobj = keyboardevent_getbrigthnessinfo(brightnesstoolid)
curbrightness = tmpobj["level"]
except Exception:
pass
try:
tmpobj = keyboardevent_getvolumeinfo()
curvolumemuted = tmpobj["muted"]
curvolume = tmpobj["level"]
except Exception:
pass
while True:
try:
tmpkeymode = 0
tmpkeycode = ""
adjustval = 0
adjusttype = ADJUSTTYPE_NONE
tmpcode = readq.get() # Blocking
try:
codeelements = tmpcode.split("+")
if len(codeelements) == 2:
if codeelements[0] == "PRESS":
tmpkeymode = 1
else:
tmpkeymode = 2
tmpkeycode = codeelements[1]
elif tmpcode == "EXIT":
readq.task_done()
return
except Exception:
tmpkeycode = ""
tmpkeymode = 0
pass
tmptime = time.time()
if tmpkeycode in [KEYCODE_BRIGHTNESSDOWN, KEYCODE_BRIGHTNESSUP]:
if tmpkeymode == 1 and tmptime - brightnesstime > DATAREFRESHINTERVALSEC:
# Do not update value during hold
try:
tmpobj = keyboardevent_getbrigthnessinfo(brightnesstoolid)
curbrightness = tmpobj["level"]
except Exception:
pass
adjusttype = ADJUSTTYPE_BRIGHTNESS
if tmpkeycode == KEYCODE_BRIGHTNESSDOWN:
adjustval = -5*tmpkeymode
else:
adjustval = 5*tmpkeymode
brightnesstime = tmptime
elif tmpkeycode in [KEYCODE_MUTE, KEYCODE_VOLUMEDOWN, KEYCODE_VOLUMEUP]:
if tmpkeymode == 1 and tmptime - volumetime > DATAREFRESHINTERVALSEC and tmpkeymode == 1:
# Do not update value during hold
try:
tmpobj = keyboardevent_getvolumeinfo()
curvolumemuted = tmpobj["muted"]
curvolume = tmpobj["level"]
except Exception:
pass
if tmpkeycode == KEYCODE_MUTE:
adjusttype = ADJUSTTYPE_MUTE
adjustval = 0
else:
adjusttype = ADJUSTTYPE_VOLUME
if tmpkeycode == KEYCODE_VOLUMEDOWN:
adjustval = -5*tmpkeymode
else:
adjustval = 5*tmpkeymode
volumetime = tmptime
elif tmpkeycode == KEYCODE_PAUSE:
adjusttype = ADJUSTTYPE_BATTERYINFO
else:
readq.task_done()
continue
try:
tmplockfilea = KEYBOARD_LOCKFILE+".a"
if createlockfile(tmplockfilea) == False:
# Debug ONLY
# if tmpkeymode == 1:
# debuglog("keyboard-event", "Press Key Code: "+str(tmpkeycode))
# else:
# debuglog("keyboard-event", "Hold Key Code: "+str(tmpkeycode))
if adjusttype == ADJUSTTYPE_BRIGHTNESS:
try:
tmpobj = keyboardevent_adjustbrigthness(brightnesstoolid, curbrightness, adjustval)
curbrightness = tmpobj["level"]
except Exception as brightnesserr:
try:
debuglog("keyboard-brightnessother-error", str(brightnesserr))
except:
debuglog("keyboard-brightnessother-error", "Error adjusting value")
pass
elif adjusttype == ADJUSTTYPE_VOLUME or adjusttype == ADJUSTTYPE_MUTE:
try:
tmpobj = keyboardevent_adjustvolume(curvolume, curvolumemuted, adjustval)
curvolumemuted = tmpobj["muted"]
curvolume = tmpobj["level"]
except Exception as volumeerr:
try:
debuglog("keyboard-volumeother-error", str(volumeerr))
except:
debuglog("keyboard-volumeother-error", "Error adjusting value")
pass
elif adjusttype == ADJUSTTYPE_BATTERYINFO:
outobj = battery_loadlogdata()
try:
notifymessage(outobj["power"], False)
except:
pass
deletelockfile(tmplockfilea)
except Exception as keyhandlererr:
try:
debuglog("keyboard-handlererror", str(keyhandleerr))
except:
debuglog("keyboard-handlererror", "Error")
readq.task_done()
except Exception as mainerr:
time.sleep(10)
# While True
def keyboardevent_monitor(writeq):
READTIMEOUTSECS = 1.0
FIRSTHOLDINTERVALSEC = 0.5
HOLDWAITINTERVALSEC = 0.5
while True:
try:
keypresstimestamp = {}
keyholdtimestamp = {}
# Get Devices
devicelist = []
devicefdlist = []
devicepathlist = keyboardevent_getdevicepaths()
devicefwlist = []
deviceidx = 0
while deviceidx < len(devicepathlist):
try:
tmpdevice = InputDevice(devicepathlist[deviceidx])
devicelist.append(tmpdevice)
devicefdlist.append(tmpdevice.fd)
devicefwlist.append(keyboard_getdevicefw(tmpdevice))
#debuglog("keyboard-device-info", devicepathlist[deviceidx])
#debuglog("keyboard-device-info", str(tmpdevice.info))
except Exception as deverr:
try:
debuglog("keyboard-deviceerror", str(deverr)+ " "+ devicepathlist[deviceidx])
except:
debuglog("keyboard-deviceerror", "Error "+devicepathlist[deviceidx])
deviceidx = deviceidx + 1
try:
debuglog("keyboard-update", str(len(devicefdlist))+" Devices")
while len(devicefdlist) > 0:
# Exception when one of the devices gets removed
# Wait for events on any registered device
r, w, x = select(devicefdlist, [], [], READTIMEOUTSECS)
for fd in r:
found = False
curdevicefw = ""
deviceidx = 0
while deviceidx < len(devicefdlist):
if devicefdlist[deviceidx] == fd:
curdevicefw = devicefwlist[deviceidx]
found = True
break
deviceidx = deviceidx + 1
if found:
for event in devicelist[deviceidx].read():
try:
# Process the event
#print("Device: "+devicelist[deviceidx].path+", Event: ", event)
#debuglog("keyboard-event", "Device: "+devicelist[deviceidx].path+", Event: "+str(event))
if event.type == ecodes.EV_KEY:
key_event = categorize(event)
keycodelist = []
# 2 hold, 0 release, 1 press
if event.value == 2 or event.value == 1:
#debuglog("keyboard-event", "Mode:"+str(event.value)+" Key Code: "+str(key_event.keycode))
if isinstance(key_event.keycode, str):
keycodelist = [key_event.keycode]
else:
keycodelist = key_event.keycode
else:
continue
keycodelistidx = 0
while keycodelistidx < len(keycodelist):
tmpkeycode = keycodelist[keycodelistidx]
if curdevicefw == "314":
# Remap printscreen event as pause and vice versa for special handling
if tmpkeycode == "KEY_PRINTSCREEN":
tmpkeycode = KEYCODE_PAUSE
elif tmpkeycode == "KEY_SYSRQ":
# This gets fired for some devices
tmpkeycode = KEYCODE_PAUSE
elif tmpkeycode == KEYCODE_PAUSE:
# Some other key so it will not fire
tmpkeycode = "KEY_PRINTSCREEN"
#debuglog("keyboard-event", "FW:" + curdevicefw+ " Key Code: "+tmpkeycode + " Press:"+keycodelist[keycodelistidx])
keycodelistidx = keycodelistidx + 1
if tmpkeycode not in [KEYCODE_BRIGHTNESSDOWN, KEYCODE_BRIGHTNESSUP, KEYCODE_VOLUMEDOWN, KEYCODE_VOLUMEUP]:
if event.value == 2:
# Skip hold for unhandled keys
continue
elif tmpkeycode not in [KEYCODE_PAUSE, KEYCODE_MUTE]:
# Skip press for unhandled keys
continue
tmptime = time.time()
finalmode = event.value
if event.value == 2:
# Hold needs checking
if tmpkeycode in keypresstimestamp:
# Guard time before first for hold
if (tmptime - keypresstimestamp[tmpkeycode]) >= FIRSTHOLDINTERVALSEC:
# Guard time for hold
if tmpkeycode in keyholdtimestamp:
if (tmptime - keyholdtimestamp[tmpkeycode]) < HOLDWAITINTERVALSEC:
#debuglog("keyboard-event", "Hold Key Code: "+str(tmpkeycode)+" - Skip")
continue
else:
#debuglog("keyboard-event", "Hold Key Code: "+str(tmpkeycode)+" - Skip")
continue
else:
# Should not happen, but treat as if first press
finalmode = 1
#debuglog("keyboard-event", "Mode:"+str(event.value) + " Final:"+str(finalmode)+" " +str(tmpkeycode))
if finalmode == 1:
keypresstimestamp[tmpkeycode] = tmptime
writeq.put("PRESS+"+tmpkeycode)
else:
keyholdtimestamp[tmpkeycode] = tmptime
writeq.put("HOLD+"+tmpkeycode)
except Exception as keyhandleerr:
try:
debuglog("keyboard-keyerror", str(keyhandleerr))
except:
debuglog("keyboard-keyerror", "Error")
newpathlist = keyboardevent_getdevicepaths()
if keyboardevent_devicechanged(devicepathlist, newpathlist):
debuglog("keyboard-update", "Device list changed")
break
except Exception as e:
try:
debuglog("keyboard-mainerror", str(e))
except:
debuglog("keyboard-mainerror", "Error")
# Close devices
while len(devicelist) > 0:
tmpdevice = devicelist.pop(0)
try:
tmpdevice.close()
except:
pass
except Exception as mainerr:
time.sleep(10)
# While True
try:
writeq.put("EXIT")
except Exception:
pass
if len(sys.argv) > 1:
cmd = sys.argv[1].upper()
if cmd == "SERVICE":
if createlockfile(KEYBOARD_LOCKFILE) == True:
debuglog("keyboard-service", "Already running")
else:
try:
debuglog("keyboard-service", "Service Starting")
ipcq = Queue()
t1 = Thread(target = keyboardevemt_keyhandler, args =(ipcq, ))
t2 = Thread(target = keyboardevent_monitor, args =(ipcq, ))
t1.start()
t2.start()
ipcq.join()
except Exception as einit:
try:
debuglog("keyboard-service-error", str(einit))
except:
debuglog("keyboard-service-error", "Error")
debuglog("keyboard-service", "Service Stopped")
deletelockfile(KEYBOARD_LOCKFILE)

View File

@@ -0,0 +1,568 @@
#!/usr/bin/env python3
# Based on /usr/bin/rpi-eeprom-config of bookworm
"""
rpi-eeprom-config
"""
import argparse
import atexit
import os
import subprocess
import string
import struct
import sys
import tempfile
import time
VALID_IMAGE_SIZES = [512 * 1024, 2 * 1024 * 1024]
BOOTCONF_TXT = 'bootconf.txt'
BOOTCONF_SIG = 'bootconf.sig'
PUBKEY_BIN = 'pubkey.bin'
# Each section starts with a magic number followed by a 32 bit offset to the
# next section (big-endian).
# The number, order and size of the sections depends on the bootloader version
# but the following mask can be used to test for section headers and skip
# unknown data.
#
# The last 4KB of the EEPROM image is reserved for internal use by the
# bootloader and may be overwritten during the update process.
MAGIC = 0x55aaf00f
PAD_MAGIC = 0x55aafeef
MAGIC_MASK = 0xfffff00f
FILE_MAGIC = 0x55aaf11f # id for modifiable files
FILE_HDR_LEN = 20
FILENAME_LEN = 12
TEMP_DIR = None
# Modifiable files are stored in a single 4K erasable sector.
# The max content 4076 bytes because of the file header.
ERASE_ALIGN_SIZE = 4096
MAX_FILE_SIZE = ERASE_ALIGN_SIZE - FILE_HDR_LEN
DEBUG = False
# BEGIN: Argon40 added methods
def argon_rpisupported():
# bcm2711 = pi4, bcm2712 = pi5
return rpi5()
def argon_edit_config():
# modified/stripped version of edit_config
config_src = ''
# If there is a pending update then use the configuration from
# that in order to support incremental updates. Otherwise,
# use the current EEPROM configuration.
bootfs = shell_cmd(['rpi-eeprom-update', '-b']).rstrip()
pending = os.path.join(bootfs, 'pieeprom.upd')
if os.path.exists(pending):
config_src = pending
image = BootloaderImage(pending)
current_config = image.get_file(BOOTCONF_TXT).decode('utf-8')
else:
current_config, config_src = read_current_config()
# Add PSU Mas Current etc if not yet set
foundnewsetting = 0
addsetting="\nPSU_MAX_CURRENT=5000"
current_config_lines = current_config.splitlines()
new_config = current_config
lineidx = 0
while lineidx < len(current_config_lines):
current_config_pair = current_config_lines[lineidx].split("=")
newsetting = ""
if current_config_pair[0] == "PSU_MAX_CURRENT":
newsetting = "PSU_MAX_CURRENT=5000"
if newsetting != "":
addsetting = addsetting.replace("\n"+newsetting,"",1)
if current_config_lines[lineidx] != newsetting:
foundnewsetting = foundnewsetting + 1
new_config = new_config.replace(current_config_lines[lineidx], newsetting, 1)
lineidx = lineidx + 1
if addsetting != "":
# Append additional settings after [all]
new_config = new_config.replace("[all]", "[all]"+addsetting, 1)
foundnewsetting = foundnewsetting + 1
if foundnewsetting == 0:
# Already configured
print("EEPROM settings up to date")
sys.exit(0)
# Skipped editor and write new config to temp file
create_tempdir()
tmp_conf = os.path.join(TEMP_DIR, 'boot.conf')
out = open(tmp_conf, 'w')
out.write(new_config)
out.close()
# Apply updates
apply_update(tmp_conf, None, config_src)
# END: Argon40 added methods
def debug(s):
if DEBUG:
sys.stderr.write(s + '\n')
def rpi4():
compatible_path = "/sys/firmware/devicetree/base/compatible"
if os.path.exists(compatible_path):
with open(compatible_path, "rb") as f:
compatible = f.read().decode('utf-8')
if "bcm2711" in compatible:
return True
return False
def rpi5():
compatible_path = "/sys/firmware/devicetree/base/compatible"
if os.path.exists(compatible_path):
with open(compatible_path, "rb") as f:
compatible = f.read().decode('utf-8')
if "bcm2712" in compatible:
return True
return False
def exit_handler():
"""
Delete any temporary files.
"""
if TEMP_DIR is not None and os.path.exists(TEMP_DIR):
tmp_image = os.path.join(TEMP_DIR, 'pieeprom.upd')
if os.path.exists(tmp_image):
os.remove(tmp_image)
tmp_conf = os.path.join(TEMP_DIR, 'boot.conf')
if os.path.exists(tmp_conf):
os.remove(tmp_conf)
os.rmdir(TEMP_DIR)
def create_tempdir():
global TEMP_DIR
if TEMP_DIR is None:
TEMP_DIR = tempfile.mkdtemp()
def pemtobin(infile):
"""
Converts an RSA public key into the format expected by the bootloader.
"""
# Import the package here to make this a weak dependency.
from Cryptodome.PublicKey import RSA
arr = bytearray()
f = open(infile,'r')
key = RSA.importKey(f.read())
if key.size_in_bits() != 2048:
raise Exception("RSA key size must be 2048")
# Export N and E in little endian format
arr.extend(key.n.to_bytes(256, byteorder='little'))
arr.extend(key.e.to_bytes(8, byteorder='little'))
return arr
def exit_error(msg):
"""
Trapped a fatal error, output message to stderr and exit with non-zero
return code.
"""
sys.stderr.write("ERROR: %s\n" % msg)
sys.exit(1)
def shell_cmd(args):
"""
Executes a shell command waits for completion returning STDOUT. If an
error occurs then exit and output the subprocess stdout, stderr messages
for debug.
"""
start = time.time()
arg_str = ' '.join(args)
result = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while time.time() - start < 5:
if result.poll() is not None:
break
if result.poll() is None:
exit_error("%s timeout" % arg_str)
if result.returncode != 0:
exit_error("%s failed: %d\n %s\n %s\n" %
(arg_str, result.returncode, result.stdout.read(), result.stderr.read()))
else:
return result.stdout.read().decode('utf-8')
def get_latest_eeprom():
"""
Returns the path of the latest EEPROM image file if it exists.
"""
latest = shell_cmd(['rpi-eeprom-update', '-l']).rstrip()
if not os.path.exists(latest):
exit_error("EEPROM image '%s' not found" % latest)
return latest
def apply_update(config, eeprom=None, config_src=None):
"""
Applies the config file to the latest available EEPROM image and spawns
rpi-eeprom-update to schedule the update at the next reboot.
"""
if eeprom is not None:
eeprom_image = eeprom
else:
eeprom_image = get_latest_eeprom()
create_tempdir()
# Replace the contents of bootconf.txt with the contents of the config file
tmp_update = os.path.join(TEMP_DIR, 'pieeprom.upd')
image = BootloaderImage(eeprom_image, tmp_update)
image.update_file(config, BOOTCONF_TXT)
image.write()
config_str = open(config).read()
if config_src is None:
config_src = ''
sys.stdout.write("Updating bootloader EEPROM\n image: %s\nconfig_src: %s\nconfig: %s\n%s\n%s\n%s\n" %
(eeprom_image, config_src, config, '#' * 80, config_str, '#' * 80))
sys.stdout.write("\n*** To cancel this update run 'sudo rpi-eeprom-update -r' ***\n\n")
# Ignore APT package checksums so that this doesn't fail when used
# with EEPROMs with configs delivered outside of APT.
# The checksums are really just a safety check for automatic updates.
args = ['rpi-eeprom-update', '-d', '-i', '-f', tmp_update]
resp = shell_cmd(args)
sys.stdout.write(resp)
def edit_config(eeprom=None):
"""
Implements something like 'git commit' for editing EEPROM configs.
"""
# Default to nano if $EDITOR is not defined.
editor = 'nano'
if 'EDITOR' in os.environ:
editor = os.environ['EDITOR']
config_src = ''
# If there is a pending update then use the configuration from
# that in order to support incremental updates. Otherwise,
# use the current EEPROM configuration.
bootfs = shell_cmd(['rpi-eeprom-update', '-b']).rstrip()
pending = os.path.join(bootfs, 'pieeprom.upd')
if os.path.exists(pending):
config_src = pending
image = BootloaderImage(pending)
current_config = image.get_file(BOOTCONF_TXT).decode('utf-8')
else:
current_config, config_src = read_current_config()
create_tempdir()
tmp_conf = os.path.join(TEMP_DIR, 'boot.conf')
out = open(tmp_conf, 'w')
out.write(current_config)
out.close()
cmd = "\'%s\' \'%s\'" % (editor, tmp_conf)
result = os.system(cmd)
if result != 0:
exit_error("Aborting update because \'%s\' exited with code %d." % (cmd, result))
new_config = open(tmp_conf, 'r').read()
if len(new_config.splitlines()) < 2:
exit_error("Aborting update because \'%s\' appears to be empty." % tmp_conf)
apply_update(tmp_conf, eeprom, config_src)
def read_current_config():
"""
Reads the configuration used by the current bootloader.
"""
fw_base = "/sys/firmware/devicetree/base/"
nvmem_base = "/sys/bus/nvmem/devices/"
if os.path.exists(fw_base + "/aliases/blconfig"):
with open(fw_base + "/aliases/blconfig", "rb") as f:
nvmem_ofnode_path = fw_base + f.read().decode('utf-8')
for d in os.listdir(nvmem_base):
if os.path.realpath(nvmem_base + d + "/of_node") in os.path.normpath(nvmem_ofnode_path):
return (open(nvmem_base + d + "/nvmem", "rb").read().decode('utf-8'), "blconfig device")
return (shell_cmd(['vcgencmd', 'bootloader_config']), "vcgencmd bootloader_config")
class ImageSection:
def __init__(self, magic, offset, length, filename=''):
self.magic = magic
self.offset = offset
self.length = length
self.filename = filename
debug("ImageSection %x offset %d length %d %s" % (magic, offset, length, filename))
class BootloaderImage(object):
def __init__(self, filename, output=None):
"""
Instantiates a Bootloader image writer with a source eeprom (filename)
and optionally an output filename.
"""
self._filename = filename
self._sections = []
self._image_size = 0
try:
self._bytes = bytearray(open(filename, 'rb').read())
except IOError as err:
exit_error("Failed to read \'%s\'\n%s\n" % (filename, str(err)))
self._out = None
if output is not None:
self._out = open(output, 'wb')
self._image_size = len(self._bytes)
if self._image_size not in VALID_IMAGE_SIZES:
exit_error("%s: Expected size %d bytes actual size %d bytes" %
(filename, self._image_size, len(self._bytes)))
self.parse()
def parse(self):
"""
Builds a table of offsets to the different sections in the EEPROM.
"""
offset = 0
magic = 0
while offset < self._image_size:
magic, length = struct.unpack_from('>LL', self._bytes, offset)
if magic == 0x0 or magic == 0xffffffff:
break # EOF
elif (magic & MAGIC_MASK) != MAGIC:
raise Exception('EEPROM is corrupted %x %x %x' % (magic, magic & MAGIC_MASK, MAGIC))
filename = ''
if magic == FILE_MAGIC: # Found a file
# Discard trailing null characters used to pad filename
filename = self._bytes[offset + 8: offset + FILE_HDR_LEN].decode('utf-8').replace('\0', '')
debug("section at %d length %d magic %08x %s" % (offset, length, magic, filename))
self._sections.append(ImageSection(magic, offset, length, filename))
offset += 8 + length # length + type
offset = (offset + 7) & ~7
def find_file(self, filename):
"""
Returns the offset, length and whether this is the last section in the
EEPROM for a modifiable file within the image.
"""
offset = -1
length = -1
is_last = False
next_offset = self._image_size - ERASE_ALIGN_SIZE # Don't create padding inside the bootloader scratch page
for i in range(0, len(self._sections)):
s = self._sections[i]
if s.magic == FILE_MAGIC and s.filename == filename:
is_last = (i == len(self._sections) - 1)
offset = s.offset
length = s.length
break
# Find the start of the next non padding section
i += 1
while i < len(self._sections):
if self._sections[i].magic == PAD_MAGIC:
i += 1
else:
next_offset = self._sections[i].offset
break
ret = (offset, length, is_last, next_offset)
debug('%s offset %d length %d is-last %d next %d' % (filename, ret[0], ret[1], ret[2], ret[3]))
return ret
def update(self, src_bytes, dst_filename):
"""
Replaces a modifiable file with specified byte array.
"""
hdr_offset, length, is_last, next_offset = self.find_file(dst_filename)
update_len = len(src_bytes) + FILE_HDR_LEN
if hdr_offset + update_len > self._image_size - ERASE_ALIGN_SIZE:
raise Exception('No space available - image past EOF.')
if hdr_offset < 0:
raise Exception('Update target %s not found' % dst_filename)
if hdr_offset + update_len > next_offset:
raise Exception('Update %d bytes is larger than section size %d' % (update_len, next_offset - hdr_offset))
new_len = len(src_bytes) + FILENAME_LEN + 4
struct.pack_into('>L', self._bytes, hdr_offset + 4, new_len)
struct.pack_into(("%ds" % len(src_bytes)), self._bytes,
hdr_offset + 4 + FILE_HDR_LEN, src_bytes)
# If the new file is smaller than the old file then set any old
# data which is now unused to all ones (erase value)
pad_start = hdr_offset + 4 + FILE_HDR_LEN + len(src_bytes)
# Add padding up to 8-byte boundary
while pad_start % 8 != 0:
struct.pack_into('B', self._bytes, pad_start, 0xff)
pad_start += 1
# Create a padding section unless the padding size is smaller than the
# size of a section head. Padding is allowed in the last section but
# by convention bootconf.txt is the last section and there's no need to
# pad to the end of the sector. This also ensures that the loopback
# config read/write tests produce identical binaries.
pad_bytes = next_offset - pad_start
if pad_bytes > 8 and not is_last:
pad_bytes -= 8
struct.pack_into('>i', self._bytes, pad_start, PAD_MAGIC)
pad_start += 4
struct.pack_into('>i', self._bytes, pad_start, pad_bytes)
pad_start += 4
debug("pad %d" % pad_bytes)
pad = 0
while pad < pad_bytes:
struct.pack_into('B', self._bytes, pad_start + pad, 0xff)
pad = pad + 1
def update_key(self, src_pem, dst_filename):
"""
Replaces the specified public key entry with the public key values extracted
from the source PEM file.
"""
pubkey_bytes = pemtobin(src_pem)
self.update(pubkey_bytes, dst_filename)
def update_file(self, src_filename, dst_filename):
"""
Replaces the contents of dst_filename in the EEPROM with the contents of src_file.
"""
src_bytes = open(src_filename, 'rb').read()
if len(src_bytes) > MAX_FILE_SIZE:
raise Exception("src file %s is too large (%d bytes). The maximum size is %d bytes."
% (src_filename, len(src_bytes), MAX_FILE_SIZE))
self.update(src_bytes, dst_filename)
def write(self):
"""
Writes the updated EEPROM image to stdout or the specified output file.
"""
if self._out is not None:
self._out.write(self._bytes)
self._out.close()
else:
if hasattr(sys.stdout, 'buffer'):
sys.stdout.buffer.write(self._bytes)
else:
sys.stdout.write(self._bytes)
def get_file(self, filename):
hdr_offset, length, is_last, next_offset = self.find_file(filename)
offset = hdr_offset + 4 + FILE_HDR_LEN
file_bytes = self._bytes[offset:offset+length-FILENAME_LEN-4]
return file_bytes
def extract_files(self):
for i in range(0, len(self._sections)):
s = self._sections[i]
if s.magic == FILE_MAGIC:
file_bytes = self.get_file(s.filename)
open(s.filename, 'wb').write(file_bytes)
def read(self):
config_bytes = self.get_file('bootconf.txt')
if self._out is not None:
self._out.write(config_bytes)
self._out.close()
else:
if hasattr(sys.stdout, 'buffer'):
sys.stdout.buffer.write(config_bytes)
else:
sys.stdout.write(config_bytes)
def main():
"""
Utility for reading and writing the configuration file in the
Raspberry Pi bootloader EEPROM image.
"""
description = """\
Bootloader EEPROM configuration tool for the Raspberry Pi 4 and Raspberry Pi 5.
Operating modes:
1. Outputs the current bootloader configuration to STDOUT if no arguments are
specified OR the given output file if --out is specified.
rpi-eeprom-config [--out boot.conf]
2. Extracts the configuration file from the given 'eeprom' file and outputs
the result to STDOUT or the output file if --output is specified.
rpi-eeprom-config pieeprom.bin [--out boot.conf]
3. Writes a new EEPROM image replacing the configuration file with the contents
of the file specified by --config.
rpi-eeprom-config --config boot.conf --out newimage.bin pieeprom.bin
The new image file can be installed via rpi-eeprom-update
rpi-eeprom-update -d -f newimage.bin
4. Applies a given config file to an EEPROM image and invokes rpi-eeprom-update
to schedule an update of the bootloader when the system is rebooted.
Since this command launches rpi-eeprom-update to schedule the EEPROM update
it must be run as root.
sudo rpi-eeprom-config --apply boot.conf [pieeprom.bin]
If the 'eeprom' argument is not specified then the latest available image
is selected by calling 'rpi-eeprom-update -l'.
5. The '--edit' parameter behaves the same as '--apply' except that instead of
applying a predefined configuration file a text editor is launched with the
contents of the current EEPROM configuration.
Since this command launches rpi-eeprom-update to schedule the EEPROM update
it must be run as root.
The configuration file will be taken from:
* The blconfig reserved memory nvmem device
* The cached bootloader configuration 'vcgencmd bootloader_config'
* The current pending update - typically /boot/pieeprom.upd
sudo -E rpi-eeprom-config --edit [pieeprom.bin]
To cancel the pending update run 'sudo rpi-eeprom-update -r'
The default text editor is nano and may be overridden by setting the 'EDITOR'
environment variable and passing '-E' to 'sudo' to preserve the environment.
6. Signing the bootloader config file.
Updates an EEPROM binary with a signed config file (created by rpi-eeprom-digest) plus
the corresponding RSA public key.
Requires Python Cryptodomex libraries and OpenSSL. To install on Raspberry Pi OS run:-
sudo apt install openssl python-pip
sudo python3 -m pip install cryptodomex
rpi-eeprom-digest -k private.pem -i bootconf.txt -o bootconf.sig
rpi-eeprom-config --config bootconf.txt --digest bootconf.sig --pubkey public.pem --out pieeprom-signed.bin pieeprom.bin
Currently, the signing process is a separate step so can't be used with the --edit or --apply modes.
See 'rpi-eeprom-update -h' for more information about the available EEPROM images.
"""
if os.getuid() != 0:
exit_error("Please run as root")
elif not argon_rpisupported():
# Skip
sys.exit(0)
argon_edit_config()
if __name__ == '__main__':
atexit.register(exit_handler)
main()

View File

@@ -0,0 +1,114 @@
#!/bin/bash
tmpfile="/dev/shm/argontmpconf.txt"
daemonconfigfile="/etc/argononeupd.conf"
if [ -f "$daemonconfigfile" ]
then
. $daemonconfigfile
fi
if [ -z "$lidshutdownsecs" ]
then
lidshutdownsecs=0
fi
mainloopflag=1
newmode=0
get_number () {
read curnumber
if [ -z "$curnumber" ]
then
echo "-2"
return
elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]]
then
if [ $curnumber -lt 0 ]
then
echo "-1"
return
fi
echo $curnumber
return
fi
echo "-1"
return
}
while [ $mainloopflag -eq 1 ]
do
lidshutdownmins=$((lidshutdownsecs / 60))
echo "------------------------------------------"
echo " Argon One Up Lid Configuration Tool"
echo "------------------------------------------"
echo
echo "Lid Close Behavior:"
if [ $lidshutdownsecs -lt 1 ]
then
echo "(Do Nothing)"
else
echo "(Shut down after $lidshutdownmins minute(s))"
fi
echo " 1. Do Nothing"
echo " 2. Shutdown"
echo
echo " 0. Exit"
echo "NOTE: You can also edit $daemonconfigfile directly"
echo -n "Enter Number (0-2):"
newmode=$( get_number )
if [[ $newmode -eq 0 ]]
then
mainloopflag=0
elif [ $newmode -eq 1 ]
then
lidshutdownsecs=0
elif [ $newmode -eq 2 ]
then
maxmins=120
echo "Please provide number of minutes until shutdown:"
echo -n "Enter Number (1-$maxmins):"
curval=$( get_number )
if [ $curval -gt $maxmins ]
then
newmode=0
echo "Invalid input"
elif [ $curval -lt 1 ]
then
newmode=0
echo "Invalid input"
else
lidshutdownsecs=$((curval * 60))
fi
fi
if [ $newmode -eq 1 ] || [ $newmode -eq 2 ]
then
if [ -f "$daemonconfigfile" ]
then
grep -v 'lidshutdownsecs' "$daemonconfigfile" > $tmpfile
else
echo '#' > $tmpfile
echo '# Argon One Up Configuration' >> $tmpfile
echo '#' >> $tmpfile
fi
echo '# lidshutdownsecs number of seconds till shutdown when lid is closed 0 if do nothing' >> $tmpfile
echo "lidshutdownsecs=$lidshutdownsecs" >> $tmpfile
sudo cp $tmpfile $daemonconfigfile
sudo chmod 666 $daemonconfigfile
echo "Configuration updated."
fi
done
echo

View File

@@ -0,0 +1,471 @@
#!/bin/bash
echo "*************"
echo " Argon Setup "
echo "*************"
# Check time if need to 'fix'
NEEDSTIMESYNC=0
LOCALTIME=$(date -u +%s%N | cut -b1-10)
GLOBALTIME=$(curl -s 'http://worldtimeapi.org/api/ip.txt' | grep unixtime | cut -b11-20)
TIMEDIFF=$((GLOBALTIME-LOCALTIME))
# about 26hrs, max timezone difference
if [ $TIMEDIFF -gt 100000 ]
then
NEEDSTIMESYNC=1
fi
argon_time_error() {
echo "**********************************************"
echo "* WARNING: Device time seems to be incorrect *"
echo "* This may cause problems during setup. *"
echo "**********************************************"
echo "Possible Network Time Protocol Server issue"
echo "Try running the following to correct:"
echo " curl -k https://download.argon40.com/tools/setntpserver.sh | bash"
}
if [ $NEEDSTIMESYNC -eq 1 ]
then
argon_time_error
fi
# Helper variables
ARGONDOWNLOADSERVER=https://download.argon40.com
INSTALLATIONFOLDER=/etc/argon
pythonbin="sudo /usr/bin/python3"
versioninfoscript=$INSTALLATIONFOLDER/argon-versioninfo.sh
uninstallscript=$INSTALLATIONFOLDER/argon-uninstall.sh
configscript=$INSTALLATIONFOLDER/argon-config
argondashboardscript=$INSTALLATIONFOLDER/argondashboard.py
setupmode="Setup"
if [ -f $configscript ]
then
setupmode="Update"
echo "Updating files"
else
sudo mkdir $INSTALLATIONFOLDER
sudo chmod 755 $INSTALLATIONFOLDER
fi
##########
# Start code lifted from raspi-config
# set_config_var based on raspi-config
if [ -e /boot/firmware/config.txt ] ; then
FIRMWARE=/firmware
else
FIRMWARE=
fi
CONFIG=/boot${FIRMWARE}/config.txt
set_config_var() {
if ! grep -q -E "$1=$2" $3 ; then
echo "$1=$2" | sudo tee -a $3 > /dev/null
fi
}
# End code lifted from raspi-config
##########
# Reuse set_config_var
set_nvme_default() {
set_config_var dtparam nvme $CONFIG
set_config_var dtparam=pciex1_gen 3 $CONFIG
}
set_external_antenna() {
set_config_var dtparam ant2 $CONFIG
}
argon_check_pkg() {
RESULT=$(dpkg-query -W -f='${Status}\n' "$1" 2> /dev/null | grep "installed")
if [ "" == "$RESULT" ]; then
echo "NG"
else
echo "OK"
fi
}
CHECKDEVICE="oneup" # Hardcoded for argononeup
CHECKGPIOMODE="libgpiod" # libgpiod or rpigpio
# Check if Raspbian, Ubuntu, others
CHECKPLATFORM="Others"
CHECKPLATFORMVERSION=""
CHECKPLATFORMVERSIONNUM=""
if [ -f "/etc/os-release" ]
then
source /etc/os-release
if [ "$ID" = "raspbian" ]
then
CHECKPLATFORM="Raspbian"
CHECKPLATFORMVERSION=$VERSION_ID
elif [ "$ID" = "debian" ]
then
# For backwards compatibility, continue using raspbian
CHECKPLATFORM="Raspbian"
CHECKPLATFORMVERSION=$VERSION_ID
elif [ "$ID" = "ubuntu" ]
then
CHECKPLATFORM="Ubuntu"
CHECKPLATFORMVERSION=$VERSION_ID
fi
echo ${CHECKPLATFORMVERSION} | grep -e "\." > /dev/null
if [ $? -eq 0 ]
then
CHECKPLATFORMVERSIONNUM=`cut -d "." -f2 <<< $CHECKPLATFORMVERSION `
CHECKPLATFORMVERSION=`cut -d "." -f1 <<< $CHECKPLATFORMVERSION `
fi
fi
gpiopkg="python3-libgpiod"
if [ "$CHECKGPIOMODE" = "rpigpio" ]
then
if [ "$CHECKPLATFORM" = "Raspbian" ]
then
gpiopkg="raspi-gpio python3-rpi.gpio"
else
gpiopkg="python3-rpi.gpio"
fi
fi
pkglist=($gpiopkg python3-smbus i2c-tools python3-evdev ddcutil)
echo "Installing/updating dependencies..."
for curpkg in ${pkglist[@]}; do
sudo apt-get install -y $curpkg
RESULT=$(argon_check_pkg "$curpkg")
if [ "NG" == "$RESULT" ]
then
echo "********************************************************************"
echo "Please also connect device to the internet and restart installation."
echo "********************************************************************"
exit
fi
done
echo "Updating configuration ..."
# Ubuntu Mate for RPi has raspi-config too
command -v raspi-config &> /dev/null
if [ $? -eq 0 ]
then
# Enable i2c
sudo raspi-config nonint do_i2c 0
fi
# Added to enabled NVMe for pi5
set_nvme_default
# Fan Setup
basename="argononeup"
daemonname=$basename"d"
eepromrpiscript="/usr/bin/rpi-eeprom-config"
eepromconfigscript=$INSTALLATIONFOLDER/${basename}-eepromconfig.py
daemonscript=$INSTALLATIONFOLDER/$daemonname.py
daemonservice=/lib/systemd/system/$daemonname.service
userdaemonservice=/etc/systemd/user/${daemonname}user.service
daemonconfigfile=/etc/$daemonname.conf
lidconfigscript=$INSTALLATIONFOLDER/${basename}-lidconfig.sh
for TMPDIRECTORY in "/lib/systemd/system"
do
sudo mkdir -p "$TMPDIRECTORY"
sudo chmod 755 $TMPDIRECTORY
sudo chown root:root "$TMPDIRECTORY"
done
echo "Installing/Updating scripts and services ..."
if [ ! -f $daemonconfigfile ]; then
# Generate config file for fan speed
sudo touch $daemonconfigfile
sudo chmod 666 $daemonconfigfile
echo '#' >> $daemonconfigfile
echo '# Argon One Up Configuration' >> $daemonconfigfile
echo '#' >> $daemonconfigfile
echo '# lidshutdownsecs number of seconds till shutdown when lid is closed 0 if do nothing' >> $daemonconfigfile
echo 'lidshutdownsecs=300' >> $daemonconfigfile
fi
# Lid Config Script
sudo wget $ARGONDOWNLOADSERVER/scripts/argononeup-lidconfig.sh -O $lidconfigscript --quiet
sudo chmod 755 $lidconfigscript
if [ -f "$eepromrpiscript" ]
then
# EEPROM Config Script
sudo wget $ARGONDOWNLOADSERVER/scripts/argon-rpi-eeprom-config-psu.py -O $eepromconfigscript --quiet
sudo chmod 755 $eepromconfigscript
fi
# Daemon/Service Files
sudo wget $ARGONDOWNLOADSERVER/scripts/${daemonname}.py -O $daemonscript --quiet
sudo wget $ARGONDOWNLOADSERVER/scripts/${daemonname}.service -O $daemonservice --quiet
sudo chmod 644 $daemonservice
sudo wget $ARGONDOWNLOADSERVER/scripts/${daemonname}user.service -O $userdaemonservice --quiet
sudo chmod 644 $userdaemonservice
# Battery Images
if [ ! -d "$INSTALLATIONFOLDER/ups" ]
then
sudo mkdir $INSTALLATIONFOLDER/ups
fi
sudo wget $ARGONDOWNLOADSERVER/ups/upsimg.tar.gz -O $INSTALLATIONFOLDER/ups/upsimg.tar.gz --quiet
sudo tar xfz $INSTALLATIONFOLDER/ups/upsimg.tar.gz -C $INSTALLATIONFOLDER/ups/
sudo rm -Rf $INSTALLATIONFOLDER/ups/upsimg.tar.gz
sudo wget "$ARGONDOWNLOADSERVER/scripts/argonpowerbutton-${CHECKGPIOMODE}.py" -O $INSTALLATIONFOLDER/argonpowerbutton.py --quiet
sudo wget $ARGONDOWNLOADSERVER/scripts/argonkeyboard.py -O $INSTALLATIONFOLDER/argonkeyboard.py --quiet
# Other utility scripts
sudo wget $ARGONDOWNLOADSERVER/scripts/argondashboard.py -O $INSTALLATIONFOLDER/argondashboard.py --quiet
sudo wget $ARGONDOWNLOADSERVER/scripts/argon-versioninfo.sh -O $versioninfoscript --quiet
sudo chmod 755 $versioninfoscript
sudo wget $ARGONDOWNLOADSERVER/scripts/argonsysinfo.py -O $INSTALLATIONFOLDER/argonsysinfo.py --quiet
sudo wget $ARGONDOWNLOADSERVER/scripts/argonregister-v1.py -O $INSTALLATIONFOLDER/argonregister.py --quiet
# Argon Uninstall Script
sudo wget $ARGONDOWNLOADSERVER/scripts/argon-uninstall.sh -O $uninstallscript --quiet
sudo chmod 755 $uninstallscript
# Argon Config Script
if [ -f $configscript ]; then
sudo rm $configscript
fi
sudo touch $configscript
# To ensure we can write the following lines
sudo chmod 666 $configscript
echo '#!/bin/bash' >> $configscript
echo 'echo "--------------------------"' >> $configscript
echo 'echo "Argon Configuration Tool"' >> $configscript
echo "$versioninfoscript simple" >> $configscript
echo 'echo "--------------------------"' >> $configscript
echo 'get_number () {' >> $configscript
echo ' read curnumber' >> $configscript
echo ' if [ -z "$curnumber" ]' >> $configscript
echo ' then' >> $configscript
echo ' echo "-2"' >> $configscript
echo ' return' >> $configscript
echo ' elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]]' >> $configscript
echo ' then' >> $configscript
echo ' if [ $curnumber -lt 0 ]' >> $configscript
echo ' then' >> $configscript
echo ' echo "-1"' >> $configscript
echo ' return' >> $configscript
echo ' elif [ $curnumber -gt 100 ]' >> $configscript
echo ' then' >> $configscript
echo ' echo "-1"' >> $configscript
echo ' return' >> $configscript
echo ' fi ' >> $configscript
echo ' echo $curnumber' >> $configscript
echo ' return' >> $configscript
echo ' fi' >> $configscript
echo ' echo "-1"' >> $configscript
echo ' return' >> $configscript
echo '}' >> $configscript
echo '' >> $configscript
echo 'mainloopflag=1' >> $configscript
echo 'while [ $mainloopflag -eq 1 ]' >> $configscript
echo 'do' >> $configscript
echo ' echo' >> $configscript
echo ' echo "Choose Option:"' >> $configscript
echo ' echo " 1. Get Battery Status"' >> $configscript
echo ' echo " 2. Configure Lid Behavior"' >> $configscript
uninstalloption="4"
statusoption=$(($uninstalloption-1))
echo " echo \" $statusoption. Dashboard\"" >> $configscript
echo " echo \" $uninstalloption. Uninstall\"" >> $configscript
echo ' echo ""' >> $configscript
echo ' echo " 0. Exit"' >> $configscript
echo " echo -n \"Enter Number (0-$uninstalloption):\"" >> $configscript
echo ' newmode=$( get_number )' >> $configscript
echo ' if [ $newmode -eq 0 ]' >> $configscript
echo ' then' >> $configscript
echo ' echo "Thank you."' >> $configscript
echo ' mainloopflag=0' >> $configscript
echo ' elif [ $newmode -eq 1 ]' >> $configscript
echo ' then' >> $configscript
# Option 1
echo " $pythonbin $daemonscript GETBATTERY" >> $configscript
echo ' elif [ $newmode -eq 2 ]' >> $configscript
echo ' then' >> $configscript
# Option 2
echo " $lidconfigscript" >> $configscript
# Standard options
echo " elif [ \$newmode -eq $statusoption ]" >> $configscript
echo ' then' >> $configscript
echo " $pythonbin $argondashboardscript" >> $configscript
echo " elif [ \$newmode -eq $uninstalloption ]" >> $configscript
echo ' then' >> $configscript
echo " $uninstallscript" >> $configscript
echo ' mainloopflag=0' >> $configscript
echo ' fi' >> $configscript
echo 'done' >> $configscript
sudo chmod 755 $configscript
# Desktop Icon
destfoldername=$USERNAME
if [ -z "$destfoldername" ]
then
destfoldername=$USER
fi
if [ -z "$destfoldername" ]
then
destfoldername="pi"
fi
shortcutfile="/home/$destfoldername/Desktop/argononeup.desktop"
if [ -d "/home/$destfoldername/Desktop" ]
then
echo "Creating/Updating Desktop Elements ..."
terminalcmd="lxterminal --working-directory=/home/$destfoldername/ -t"
if [ -f "/home/$destfoldername/.twisteros.twid" ]
then
terminalcmd="xfce4-terminal --default-working-directory=/home/$destfoldername/ -T"
fi
imagefile=argon40.png
sudo wget https://download.argon40.com/$imagefile -O /etc/argon/$imagefile --quiet
if [ -f $shortcutfile ]; then
sudo rm $shortcutfile
fi
# Create Shortcuts
echo "[Desktop Entry]" > $shortcutfile
echo "Name=Argon Configuration" >> $shortcutfile
echo "Comment=Argon Configuration" >> $shortcutfile
echo "Icon=/etc/argon/$imagefile" >> $shortcutfile
echo 'Exec='$terminalcmd' "Argon Configuration" -e '$configscript >> $shortcutfile
echo "Type=Application" >> $shortcutfile
echo "Encoding=UTF-8" >> $shortcutfile
echo "Terminal=false" >> $shortcutfile
echo "Categories=None;" >> $shortcutfile
chmod 755 $shortcutfile
fi
configcmd="$(basename -- $configscript)"
echo "Initializing Services ..."
# Force remove lock files
sudo rm -f /dev/shm/argononeupkeyboardlock.txt
sudo rm -f /dev/shm/argononeupkeyboardlock.txt.a
if [ "$setupmode" = "Setup" ]
then
if [ -f "/usr/bin/$configcmd" ]
then
sudo rm /usr/bin/$configcmd
fi
sudo ln -s $configscript /usr/bin/$configcmd
# Enable and Start Service(s)
sudo systemctl daemon-reload
sudo systemctl enable argononeupd.service
sudo systemctl start argononeupd.service
else
sudo systemctl daemon-reload
sudo systemctl restart argononeupd.service
fi
# Enable and Start User Service(s)
for tmpuser in `awk -F: '{ if ($3 >= 1000) print $1 }' /etc/passwd`
do
if [ "$tmpuser" != "nobody" ]
then
if [ "$setupmode" = "Setup" ]
then
sudo -u "$tmpuser" systemctl --user enable argononeupduser.service
sudo -u "$tmpuser" systemctl --user start argononeupduser.service
else
sudo -u "$tmpuser" systemctl --user restart argononeupduser.service
fi
fi
done
# Current user / fallback
if [ "$setupmode" = "Setup" ]
then
systemctl --user enable argononeupduser.service
systemctl --user start argononeupduser.service
else
systemctl --user restart argononeupduser.service
fi
if [ "$CHECKPLATFORM" = "Raspbian" ]
then
if [ -f "$eepromrpiscript" ]
then
echo "Checking EEPROM ..."
sudo apt-get update && sudo apt-get upgrade -y
sudo rpi-eeprom-update
# EEPROM Config Script
sudo $eepromconfigscript
fi
else
echo "WARNING: EEPROM not updated. Please run this under Raspberry Pi OS"
fi
echo "*********************"
echo " $setupmode Completed "
echo "*********************"
$versioninfoscript
echo
echo "Use '$configcmd' to configure device"
echo
if [ $NEEDSTIMESYNC -eq 1 ]
then
argon_time_error
fi

View File

@@ -0,0 +1,466 @@
#!/usr/bin/python3
#
# This script monitor battery via ic2 and keyboard events.
#
# Additional comments are found in each function below
#
#
import sys
import os
import time
from threading import Thread
from queue import Queue
sys.path.append("/etc/argon/")
from argonregister import *
from argonpowerbutton import *
# Initialize I2C Bus
bus = argonregister_initializebusobj()
# Constants
ADDR_BATTERY = 0x64
UPS_LOGFILE="/dev/shm/upslog.txt"
###################
# Utilty Functions
###################
# Debug Logger
def debuglog(typestr, logstr):
try:
DEBUGFILE="/dev/shm/argononeupdebuglog.txt"
tmpstrpadding = " "
with open(DEBUGFILE, "a") as txt_file:
txt_file.write("["+time.asctime(time.localtime(time.time()))+"] "+typestr.upper()+" "+logstr.strip().replace("\n","\n"+tmpstrpadding)+"\n")
except:
pass
# System Notifcation
def notifymessage(message, iscritical):
if not isinstance(message, str) or len(message.strip()) == 0:
return
wftype="notify"
if iscritical:
wftype="critical"
os.system("export SUDO_UID=1000; wfpanelctl "+wftype+" \""+message+"\"")
os.system("export DISPLAY=:0.0; lxpanelctl notify \""+message+"\"")
#############
# Battery
#############
REG_CONTROL = 0x08
REG_SOCALERT = 0x0b
REG_PROFILE = 0x10
REG_ICSTATE = 0xA7
def battery_restart():
# Set to active mode
try:
maxretry = 3
while maxretry > 0:
maxretry = maxretry - 1
# Restart
bus.write_byte_data(ADDR_BATTERY, REG_CONTROL, 0x30)
time.sleep(0.5)
# Activate
bus.write_byte_data(ADDR_BATTERY, REG_CONTROL, 0x00)
time.sleep(0.5)
# Wait for Ready Status
maxwaitsecs = 5
while maxwaitsecs > 0:
tmpval = bus.read_byte_data(ADDR_BATTERY, REG_ICSTATE)
if (tmpval&0x0C) != 0:
debuglog("battery-activate", "Activated Successfully")
return 0
time.sleep(1)
maxwaitsecs = maxwaitsecs - 1
debuglog("battery-activate", "Failed to activate")
return 2
except Exception as e:
try:
debuglog("battery-activateerror", str(e))
except:
debuglog("battery-activateerror", "Activation Failed")
return 1
def battery_getstatus(restartifnotactive):
try:
tmpval = bus.read_byte_data(ADDR_BATTERY, REG_CONTROL)
if tmpval != 0:
if restartifnotactive == True:
tmpval = battery_restart()
if tmpval != 0:
debuglog("battery-status", "Inactive "+str(tmpval))
return 2
tmpval = bus.read_byte_data(ADDR_BATTERY, REG_SOCALERT)
if (tmpval&0x80) == 0:
debuglog("battery-status", "Profile not ready "+str(tmpval))
return 3
# OK
#debuglog("battery-status", "OK")
return 0
except Exception as e:
try:
debuglog("battery-status-error", str(e))
except:
debuglog("battery-status-error", "Battery Status Failed")
return 1
def battery_checkupdateprofile():
try:
REG_GPIOCONFIG = 0x0A
PROFILE_DATALIST = [0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA8,0xAA,0xBE,0xC6,0xB8,0xAE,0xC2,0x98,0x82,0xFF,0xFF,0xCA,0x98,0x75,0x63,0x55,0x4E,0x4C,0x49,0x98,0x88,0xDC,0x34,0xDB,0xD3,0xD4,0xD3,0xD0,0xCE,0xCB,0xBB,0xE7,0xA2,0xC2,0xC4,0xAE,0x96,0x89,0x80,0x74,0x67,0x63,0x71,0x8E,0x9F,0x85,0x6F,0x3B,0x20,0x00,0xAB,0x10,0xFF,0xB0,0x73,0x00,0x00,0x00,0x64,0x08,0xD3,0x77,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFA]
PROFILE_LEN = len(PROFILE_DATALIST)
# Try to compare profile if battery is active
tmpidx = 0
tmpval = battery_getstatus(True)
if tmpval == 0:
# Status OK, check profile
tmpidx = 0
while tmpidx < PROFILE_LEN:
tmpval = bus.read_byte_data(ADDR_BATTERY, REG_PROFILE+tmpidx)
if tmpval != PROFILE_DATALIST[tmpidx]:
debuglog("battery-profile-error", "Mismatch")
break
tmpidx = tmpidx + 1
if tmpidx == PROFILE_LEN:
# Matched
return 0
else:
debuglog("battery-profile", "Status Error "+str(tmpval)+", will attempt to update")
# needs update
debuglog("battery-profile", "Updating...")
# Device Sleep state
# Restart
bus.write_byte_data(ADDR_BATTERY, REG_CONTROL, 0x30)
time.sleep(0.5)
# Sleep
bus.write_byte_data(ADDR_BATTERY, REG_CONTROL, 0xF0)
time.sleep(0.5)
# Write Profile
tmpidx = 0
while tmpidx < PROFILE_LEN:
bus.write_byte_data(ADDR_BATTERY, REG_PROFILE+tmpidx, PROFILE_DATALIST[tmpidx])
tmpidx = tmpidx + 1
debuglog("battery-profile", "Profile Updated,Restarting...")
# Set Update Flag
bus.write_byte_data(ADDR_BATTERY, REG_SOCALERT, 0x80)
time.sleep(0.5)
# Close Interrupts
bus.write_byte_data(ADDR_BATTERY, REG_GPIOCONFIG, 0)
time.sleep(0.5)
# Restart Battery
tmpval = battery_restart()
if tmpval == 0:
debuglog("battery-profile", "Update Completed")
return 0
debuglog("battery-profile", "Unable to restart")
return 3
except Exception as e:
try:
debuglog("battery-profile-error", str(e))
except:
debuglog("battery-profile-error", "Battery Profile Check/Update Failed")
return 1
def battery_getpercent():
# State of Charge (SOC)
try:
SOC_HIGH_REG = 0x04
socpercent = bus.read_byte_data(ADDR_BATTERY, SOC_HIGH_REG)
if socpercent > 100:
return 100
elif socpercent > 0:
return socpercent
# Support Fraction percent
#SOC_LOW_REG = 0x05
#soc_low = bus.read_byte_data(ADDR_BATTERY, SOC_LOW_REG)
#socpercentfloat = socpercent + (soc_low / 256.0)
#if socpercentfloat > 100.0:
# return 100.0
#elif socpercentfloat > 0.0:
# return socpercentfloat
except Exception as e:
try:
debuglog("battery-percenterror", str(e))
except:
debuglog("battery-percenterror", "Read Battery Failed")
return 0
def battery_isplugged():
# State of Charge (SOC)
try:
CURRENT_HIGH_REG = 0x0E
current_high = bus.read_byte_data(ADDR_BATTERY, CURRENT_HIGH_REG)
if (current_high & 0x80) > 0:
return 1
#CURRENT_LOW_REG = 0x0F
#R_SENSE = 10.0
#current_low = bus.read_byte_data(ADDR_BATTERY, CURRENT_LOW_REG)
#raw_current = int.from_bytes([current_high, current_low], byteorder='big', signed=True)
#current = (52.4 * raw_current) / (32768 * R_SENSE)
except Exception as e:
try:
debuglog("battery-chargingerror", str(e))
except:
debuglog("battery-chargingerror", "Read Charging Failed")
return 0
def battery_loadlogdata():
# status, version, time, schedule
outobj = {}
try:
fp = open(UPS_LOGFILE, "r")
logdata = fp.read()
alllines = logdata.split("\n")
ctr = 0
while ctr < len(alllines):
tmpval = alllines[ctr].strip()
curinfo = tmpval.split(":")
if len(curinfo) > 1:
tmpattrib = curinfo[0].lower().split(" ")
# The rest are assumed to be value
outobj[tmpattrib[0]] = tmpval[(len(curinfo[0])+1):].strip()
ctr = ctr + 1
except OSError:
pass
return outobj
def battery_check(readq):
CMDSTARTBYTE=0xfe
CMDCONTROLBYTECOUNT=3
CHECKSTATUSLOOPFREQ=50
CMDsendrequest = [ 0xfe, 0, 0, 0xfe, 0xfe, 0, 0, 0xfe, 0, 0, 0]
lastcmdtime=""
loopCtr = CHECKSTATUSLOOPFREQ
sendcmdid = -1
debuglog("battery", "Starting")
updatedesktopicon("Argon ONE UP", "/etc/argon/argon40.png")
maxretry = 5
while maxretry > 0:
try:
if battery_checkupdateprofile() == 0:
break
except Exception as mainerr:
try:
debuglog("battery-mainerror", str(mainerr))
except:
debuglog("battery-mainerror", "Error")
# Give time before retry
time.sleep(10)
maxretry = maxretry - 1
while maxretry > 0: # Outer loop; maxretry never decrements so infinite
qdata = ""
if readq.empty() == False:
qdata = readq.get()
if battery_getstatus(True) != 0:
# Give time before retry
time.sleep(3)
continue
prevnotifymsg = ""
previconfile = ""
statusstr = ""
needsupdate=False
device_battery=0
device_charging=0
while True: # Command loop
try:
if sendcmdid < 0:
cmddatastr = ""
if cmddatastr == "":
if loopCtr >= CHECKSTATUSLOOPFREQ:
# Check Battery Status
sendcmdid = 0
loopCtr = 0
else:
loopCtr = loopCtr + 1
if (loopCtr&1) == 0:
sendcmdid = 0 # Check Battery Status
if sendcmdid == 0:
tmp_battery = battery_getpercent()
tmp_charging = battery_isplugged()
if tmp_charging != device_charging or tmp_battery!=device_battery:
device_battery=tmp_battery
device_charging=tmp_charging
tmpiconfile = "/etc/argon/ups/"
needsupdate=True
curnotifymsg = ""
curnotifycritical = False
if device_charging == 0:
if "Shutting Down" in prevnotifymsg:
os.system("shutdown -c ""Charging, shutdown cancelled.""")
debuglog("battery-shutdown", "Abort")
if device_battery>99:
# Prevents switching issue
statusstr = "Charged"
curnotifymsg = statusstr
tmpiconfile = tmpiconfile+"charge_"+str(device_battery)
elif device_charging == 0:
statusstr = "Charging"
curnotifymsg = statusstr
tmpiconfile = tmpiconfile+"charge_"+str(device_battery)
else:
statusstr = "Battery"
tmpiconfile = tmpiconfile+"discharge_"+str(device_battery)
if device_battery > 50:
curnotifymsg="Battery Mode"
elif device_battery > 20:
curnotifymsg="50%% Battery"
elif device_battery > 10:
curnotifymsg="20%% Battery"
elif device_battery > 5:
#curnotifymsg="Low Battery"
curnotifymsg="Low Battery: The device may power off automatically soon."
curnotifycritical=True
else:
curnotifymsg="CRITICAL BATTERY: Shutting Down in 1 minute"
curnotifycritical=True
tmpiconfile = tmpiconfile + ".png"
statusstr = statusstr + " " + str(device_battery)+"%"
# Add/update desktop icons too; add check to minimize write
if previconfile != tmpiconfile:
updatedesktopicon(statusstr, tmpiconfile)
previconfile = tmpiconfile
# Send notification if necessary
if prevnotifymsg != curnotifymsg:
notifymessage(curnotifymsg, curnotifycritical)
if device_battery <= 5 and device_charging != 0:
os.system("shutdown +1 """+curnotifymsg+".""")
debuglog("battery-shutdown", "Shutdown in 1 minute")
prevnotifymsg = curnotifymsg
sendcmdid=-1
if needsupdate==True:
# Log File
otherstr = ""
with open(UPS_LOGFILE, "w") as txt_file:
txt_file.write("Status as of: "+time.asctime(time.localtime(time.time()))+"\n Power:"+statusstr+"\n"+otherstr)
needsupdate=False
except Exception as e:
try:
debuglog("battery-error", str(e))
except:
debuglog("battery-error", "Error")
break
time.sleep(3)
def updatedesktopicon(statusstr, tmpiconfile):
try:
icontitle = "Argon ONE UP"
tmp = os.popen("find /home -maxdepth 1 -type d").read()
alllines = tmp.split("\n")
for curfolder in alllines:
if curfolder == "/home" or curfolder == "":
continue
#debuglog("desktop-update-path", curfolder)
#debuglog("desktop-update-text", statusstr)
#debuglog("desktop-update-icon", tmpiconfile)
with open(curfolder+"/Desktop/argononeup.desktop", "w") as txt_file:
txt_file.write("[Desktop Entry]\nName="+icontitle+"\nComment="+statusstr+"\nIcon="+tmpiconfile+"\nExec=lxterminal --working-directory="+curfolder+"/ -t \"Argon ONE UP\" -e \"/etc/argon/argon-config\"\nType=Application\nEncoding=UTF-8\nTerminal=false\nCategories=None;\n")
except Exception as desktope:
#pass
try:
debuglog("desktop-update-error", str(desktope))
except:
debuglog("desktop-update-error", "Error")
if len(sys.argv) > 1:
cmd = sys.argv[1].upper()
if cmd == "GETBATTERY":
outobj = battery_loadlogdata()
try:
print(outobj["power"])
except:
print("Error retrieving battery status")
elif cmd == "RESETBATTERY":
battery_checkupdateprofile()
elif cmd == "SERVICE":
# Starts sudo level services
try:
ipcq = Queue()
if len(sys.argv) > 2:
cmd = sys.argv[2].upper()
t1 = Thread(target = battery_check, args =(ipcq, ))
t2 = Thread(target = argonpowerbutton_monitorlid, args =(ipcq, ))
t1.start()
t2.start()
ipcq.join()
except Exception:
sys.exit(1)

View File

@@ -0,0 +1,10 @@
[Unit]
Description=Argon ONE UP Service
After=multi-user.target
[Service]
Type=simple
Restart=always
RemainAfterExit=true
ExecStart=/usr/bin/python3 /etc/argon/argononeupd.py SERVICE
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,10 @@
[Unit]
Description=Argon ONE UP Service
After=multi-user.target
[Service]
Type=simple
Restart=always
RemainAfterExit=true
ExecStart=/usr/bin/python3 /etc/argon/argonkeyboard.py SERVICE
[Install]
WantedBy=default.target

View File

@@ -0,0 +1,206 @@
# For Libreelec/Lakka, note that we need to add system paths
# import sys
# sys.path.append('/storage/.kodi/addons/virtual.rpi-tools/lib')
import gpiod
import os
import time
# Debug Logger
def argonpowerbutton_debuglog(typestr, logstr):
try:
DEBUGFILE="/dev/shm/argononegpiodebuglog.txt"
tmpstrpadding = " "
with open(DEBUGFILE, "a") as txt_file:
txt_file.write("["+time.asctime(time.localtime(time.time()))+"] "+typestr.upper()+" "+logstr.strip().replace("\n","\n"+tmpstrpadding)+"\n")
except:
pass
def argonpowerbutton_getvalue(lineobj,lineid):
if lineid is not None:
tmpval = lineobj.get_value(lineid) != gpiod.line.Value.INACTIVE
if tmpval == False:
return 0
return 1
return lineobj.get_value()
def argonpowerbutton_watchline(debugname, dataq, lineid, callback):
monitormode = True
argonpowerbutton_debuglog(debugname, "Starting")
# Pi5 mapping, 0 for older
chippath = '/dev/gpiochip4'
try:
chip = gpiod.Chip(chippath)
except Exception as gpioerr:
try:
# Old mapping
chippath = '/dev/gpiochip0'
chip = gpiod.Chip(chippath)
except Exception as gpioolderr:
chippath = ""
if len(chippath) == 0:
argonpowerbutton_debuglog(debugname+"-error", "Unable to initialize GPIO")
try:
dataq.put("ERROR")
except:
pass
return
# Monitoring starts
try:
try:
# Reference https://github.com/brgl/libgpiod/blob/master/bindings/python/examples/gpiomon.py
lineobj = chip.get_line(lineid)
if lineid == 27:
lineobj.request(consumer="argon", type=gpiod.LINE_REQ_EV_BOTH_EDGES, flags=gpiod.LINE_REQ_FLAG_BIAS_PULL_UP)
else:
lineobj.request(consumer="argon", type=gpiod.LINE_REQ_EV_BOTH_EDGES)
while monitormode == True:
hasevent = lineobj.event_wait(10)
if hasevent:
eventdata = lineobj.event_read()
monitormode = callback(eventdata.type == gpiod.LineEvent.RISING_EDGE, lineobj, dataq, None)
lineobj.release()
chip.close()
except Exception:
# https://github.com/brgl/libgpiod/blob/master/bindings/python/examples/watch_line_rising.py
configobj = {lineid: gpiod.LineSettings(direction=gpiod.line.Direction.INPUT, edge_detection=gpiod.line.Edge.BOTH)}
if lineid == 27:
configobj = {lineid: gpiod.LineSettings(direction=gpiod.line.Direction.INPUT, edge_detection=gpiod.line.Edge.BOTH, bias=gpiod.line.Bias.PULL_UP )}
with gpiod.request_lines(
chippath,
consumer="argon",
config=configobj,
) as request:
while monitormode:
# Blocks until at least one event is available
for event in request.read_edge_events():
monitormode = callback(event.event_type == event.Type.RISING_EDGE, request, dataq, event.line_offset)
except Exception as monitorerror:
try:
argonpowerbutton_debuglog(debugname+"-error", str(monitorerror))
except:
argonpowerbutton_debuglog(debugname+"-error", "Error aborting")
try:
dataq.put("ERROR")
except:
pass
# This function is the thread that monitors activity in our shutdown pin
# The pulse width is measured, and the corresponding shell command will be issued
def argonpowerbutton_getconfigval(keyname, datatype="int"):
keyname = keyname.lower()
fname = "/etc/argononeupd.conf"
try:
with open(fname, "r") as fp:
for curline in fp:
if not curline:
continue
tmpline = curline.replace(" ", "").replace("\t", "")
if not tmpline:
continue
if tmpline[0] == "#":
continue
tmppair = tmpline.split("=")
if len(tmppair) != 2:
continue
tmpvar = tmppair[0].lower()
if tmpvar != keyname:
continue
try:
if datatype == "int":
return int(tmppair[1])
elif datatype == "float":
return float(tmppair[1])
return tmppair[1]
except:
continue
except:
pass
if datatype == "int":
return -1
elif datatype == "float":
return -1
return ""
def argonpowerbutton_monitorlidevent(isrising, lineobj, writeq, lineid):
if isrising == False:
targetsecs = argonpowerbutton_getconfigval("lidshutdownsecs")
if targetsecs > 0:
argonpowerbutton_debuglog("lid-monitor", "Close Detect; Wait for :"+str(targetsecs))
else:
argonpowerbutton_debuglog("lid-monitor", "Close Detected; Do nothing")
# Time pulse data
time.sleep(1)
pulsetimesec = 1
# 0 - Lid is closed, 1 - Lid is open
while argonpowerbutton_getvalue(lineobj, lineid) == 0:
if targetsecs > 0:
if pulsetimesec >= targetsecs:
argonpowerbutton_debuglog("lid-monitor", "Target Reached, shutting down")
monitormode = False
os.system("shutdown now -h")
return False
time.sleep(1)
pulsetimesec += 1
argonpowerbutton_debuglog("lid-monitor", "Open Detected")
return True
def argonpowerbutton_monitorlid(writeq):
LINE_LIDMONITOR=27
argonpowerbutton_watchline("lid-monitor", writeq, LINE_LIDMONITOR, argonpowerbutton_monitorlidevent)
def argonpowerbutton_monitorevent(isrising, lineobj, writeq, lineid):
pulsetime = 0
if isrising == True:
# Time pulse data
while argonpowerbutton_getvalue(lineobj, lineid) == 1:
time.sleep(0.01)
pulsetime += 1
if pulsetime >=2 and pulsetime <=3:
# Testing
#writeq.put("OLEDSWITCH")
writeq.put("OLEDSTOP")
os.system("reboot")
return False
elif pulsetime >=4 and pulsetime <=5:
writeq.put("OLEDSTOP")
os.system("shutdown now -h")
return False
elif pulsetime >=6 and pulsetime <=7:
writeq.put("OLEDSWITCH")
return True
def argonpowerbutton_monitor(writeq):
LINE_SHUTDOWN=4
argonpowerbutton_watchline("button", writeq, LINE_SHUTDOWN, argonpowerbutton_monitorevent)
def argonpowerbutton_monitorswitchevent(isrising, lineobj, writeq, lineid):
pulsetime = 0
if isrising == True:
# Time pulse data
while argonpowerbutton_getvalue(lineobj, lineid) == 1:
time.sleep(0.01)
pulsetime += 1
if pulsetime >= 10:
writeq.put("OLEDSWITCH")
return True
def argonpowerbutton_monitorswitch(writeq):
LINE_SHUTDOWN=4
argonpowerbutton_watchline("button-switch", writeq, LINE_SHUTDOWN, argonpowerbutton_monitorswitchevent)
# Testing
#argonpowerbutton_monitor(None)

View File

@@ -0,0 +1,156 @@
# For Libreelec/Lakka, note that we need to add system paths
# import sys
# sys.path.append('/storage/.kodi/addons/virtual.rpi-tools/lib')
import RPi.GPIO as GPIO
import os
import time
# Debug Logger
def argonpowerbutton_debuglog(typestr, logstr):
try:
DEBUGFILE="/dev/shm/argononegpiodebuglog.txt"
tmpstrpadding = " "
with open(DEBUGFILE, "a") as txt_file:
txt_file.write("["+time.asctime(time.localtime(time.time()))+"] "+typestr.upper()+" "+logstr.strip().replace("\n","\n"+tmpstrpadding)+"\n")
except:
pass
# This function is the thread that monitors activity in our shutdown pin
# The pulse width is measured, and the corresponding shell command will be issued
def argonpowerbutton_getconfigval(keyname, datatype="int"):
keyname = keyname.lower()
fname = "/etc/argononeupd.conf"
try:
with open(fname, "r") as fp:
for curline in fp:
if not curline:
continue
tmpline = curline.replace(" ", "").replace("\t", "")
if not tmpline:
continue
if tmpline[0] == "#":
continue
tmppair = tmpline.split("=")
if len(tmppair) != 2:
continue
tmpvar = tmppair[0].lower()
if tmpvar != keyname:
continue
try:
if datatype == "int":
return int(tmppair[1])
elif datatype == "float":
return float(tmppair[1])
return tmppair[1]
except:
continue
except:
pass
if datatype == "int":
return -1
elif datatype == "float":
return -1
return ""
def argonpowerbutton_monitorlid(writeq):
try:
argonpowerbutton_debuglog("lid-monitor", "Starting")
monitormode = True
# 0 - Lid is closed, 1 - Lid is open
# Pin Assignments
PIN_LIDMONITOR=27
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIN_LIDMONITOR, GPIO.IN, pull_up_down=GPIO.PUD_UP)
while monitormode == True:
pulsetimesec = 1
GPIO.wait_for_edge(PIN_LIDMONITOR, GPIO.FALLING)
targetsecs = argonpowerbutton_getconfigval("lidshutdownsecs")
if targetsecs > 0:
argonpowerbutton_debuglog("lid-monitor", "Close Detect; Wait for :"+str(targetsecs))
else:
argonpowerbutton_debuglog("lid-monitor", "Close Detected; Do nothing")
# Time pulse data
time.sleep(1)
while GPIO.input(PIN_LIDMONITOR) == GPIO.LOW:
if targetsecs > 0:
if pulsetimesec >= targetsecs:
argonpowerbutton_debuglog("lid-monitor", "Target Reached, shutting down")
monitormode = False
os.system("shutdown now -h")
break
time.sleep(1)
pulsetimesec += 1
argonpowerbutton_debuglog("lid-monitor", "Open Detected")
except Exception as liderror:
try:
argonpowerbutton_debuglog("lid-monitor-error", str(liderror))
except:
argonpowerbutton_debuglog("lid-monitor-error", "Error aborting")
#pass
GPIO.cleanup()
def argonpowerbutton_monitor(writeq):
try:
# Pin Assignments
PIN_SHUTDOWN=4
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIN_SHUTDOWN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
while True:
pulsetime = 1
GPIO.wait_for_edge(PIN_SHUTDOWN, GPIO.RISING)
time.sleep(0.01)
while GPIO.input(PIN_SHUTDOWN) == GPIO.HIGH:
time.sleep(0.01)
pulsetime += 1
if pulsetime >=2 and pulsetime <=3:
# Testing
#writeq.put("OLEDSWITCH")
writeq.put("OLEDSTOP")
os.system("reboot")
break
elif pulsetime >=4 and pulsetime <=5:
writeq.put("OLEDSTOP")
os.system("shutdown now -h")
break
elif pulsetime >=6 and pulsetime <=7:
writeq.put("OLEDSWITCH")
except Exception:
writeq.put("ERROR")
GPIO.cleanup()
def argonpowerbutton_monitorswitch(writeq):
try:
# Pin Assignments
PIN_SHUTDOWN=4
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIN_SHUTDOWN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
while True:
pulsetime = 1
GPIO.wait_for_edge(PIN_SHUTDOWN, GPIO.RISING)
time.sleep(0.01)
while GPIO.input(PIN_SHUTDOWN) == GPIO.HIGH:
time.sleep(0.01)
pulsetime += 1
if pulsetime >= 10:
writeq.put("OLEDSWITCH")
except Exception:
writeq.put("ERROR")
GPIO.cleanup()

View File

@@ -0,0 +1,74 @@
#!/usr/bin/python3
#
# Argon Register Helper methods
# Same as argonregister, but no support for new register commands
#
import time
import smbus
# I2C Addresses
ADDR_ARGONONEFAN=0x1a
ADDR_ARGONONEREG=ADDR_ARGONONEFAN
# ARGONONEREG Addresses
ADDR_ARGONONEREG_DUTYCYCLE=0x80
ADDR_ARGONONEREG_FW=0x81
ADDR_ARGONONEREG_IR=0x82
ADDR_ARGONONEREG_CTRL=0x86
# Initialize bus
def argonregister_initializebusobj():
try:
return smbus.SMBus(1)
except Exception:
try:
# Older version
return smbus.SMBus(0)
except Exception:
print("Unable to detect i2c")
return None
# Checks if the FW supports control registers
def argonregister_checksupport(busobj):
return False
def argonregister_getbyte(busobj, address):
if busobj is None:
return 0
return busobj.read_byte_data(ADDR_ARGONONEREG, address)
def argonregister_setbyte(busobj, address, bytevalue):
if busobj is None:
return
busobj.write_byte_data(ADDR_ARGONONEREG,address,bytevalue)
time.sleep(1)
def argonregister_getfanspeed(busobj, regsupport=None):
return 0
def argonregister_setfanspeed(busobj, newspeed, regsupport=None):
if busobj is None:
return
if newspeed > 100:
newspeed = 100
elif newspeed < 0:
newspeed = 0
busobj.write_byte(ADDR_ARGONONEFAN,newspeed)
time.sleep(1)
def argonregister_signalpoweroff(busobj):
if busobj is None:
return
busobj.write_byte(ADDR_ARGONONEFAN,0xFF)
def argonregister_setircode(busobj, vallist):
if busobj is None:
return
busobj.write_i2c_block_data(ADDR_ARGONONEREG, ADDR_ARGONONEREG_IR, vallist)

View File

@@ -0,0 +1,394 @@
#!/usr/bin/python3
#
# Misc methods to retrieve system information.
#
import os
import time
import socket
def argonsysinfo_listcpuusage(sleepsec = 1):
outputlist = []
curusage_a = argonsysinfo_getcpuusagesnapshot()
time.sleep(sleepsec)
curusage_b = argonsysinfo_getcpuusagesnapshot()
for cpuname in curusage_a:
if cpuname == "cpu":
continue
if curusage_a[cpuname]["total"] == curusage_b[cpuname]["total"]:
outputlist.append({"title": cpuname, "value": "0%"})
else:
total = curusage_b[cpuname]["total"]-curusage_a[cpuname]["total"]
idle = curusage_b[cpuname]["idle"]-curusage_a[cpuname]["idle"]
outputlist.append({"title": cpuname, "value": int(100*(total-idle)/(total))})
return outputlist
def argonsysinfo_getcpuusagesnapshot():
cpupercent = {}
errorflag = False
try:
cpuctr = 0
# user, nice, system, idle, iowait, irc, softirq, steal, guest, guest nice
tempfp = open("/proc/stat", "r")
alllines = tempfp.readlines()
for temp in alllines:
temp = temp.replace('\t', ' ')
temp = temp.strip()
while temp.find(" ") >= 0:
temp = temp.replace(" ", " ")
if len(temp) < 3:
cpuctr = cpuctr +1
continue
checkname = temp[0:3]
if checkname == "cpu":
infolist = temp.split(" ")
idle = 0
total = 0
colctr = 1
while colctr < len(infolist):
curval = int(infolist[colctr])
if colctr == 4 or colctr == 5:
idle = idle + curval
total = total + curval
colctr = colctr + 1
if total > 0:
cpupercent[infolist[0]] = {"total": total, "idle": idle}
cpuctr = cpuctr +1
tempfp.close()
except IOError:
errorflag = True
return cpupercent
def argonsysinfo_liststoragetotal():
outputlist = []
ramtotal = 0
errorflag = False
try:
hddctr = 0
tempfp = open("/proc/partitions", "r")
alllines = tempfp.readlines()
for temp in alllines:
temp = temp.replace('\t', ' ')
temp = temp.strip()
while temp.find(" ") >= 0:
temp = temp.replace(" ", " ")
infolist = temp.split(" ")
if len(infolist) >= 4:
# Check if header
if infolist[3] != "name":
parttype = infolist[3][0:3]
if parttype == "ram":
ramtotal = ramtotal + int(infolist[2])
elif parttype[0:2] == "sd" or parttype[0:2] == "hd":
lastchar = infolist[3][-1]
if lastchar.isdigit() == False:
outputlist.append({"title": infolist[3], "value": argonsysinfo_kbstr(int(infolist[2]))})
else:
# SD Cards
lastchar = infolist[3][-2]
if lastchar[0] != "p":
outputlist.append({"title": infolist[3], "value": argonsysinfo_kbstr(int(infolist[2]))})
tempfp.close()
#outputlist.append({"title": "ram", "value": argonsysinfo_kbstr(ramtotal)})
except IOError:
errorflag = True
return outputlist
def argonsysinfo_getram():
totalram = 0
totalfree = 0
tempfp = open("/proc/meminfo", "r")
alllines = tempfp.readlines()
for temp in alllines:
temp = temp.replace('\t', ' ')
temp = temp.strip()
while temp.find(" ") >= 0:
temp = temp.replace(" ", " ")
infolist = temp.split(" ")
if len(infolist) >= 2:
if infolist[0] == "MemTotal:":
totalram = int(infolist[1])
elif infolist[0] == "MemFree:":
totalfree = totalfree + int(infolist[1])
elif infolist[0] == "Buffers:":
totalfree = totalfree + int(infolist[1])
elif infolist[0] == "Cached:":
totalfree = totalfree + int(infolist[1])
if totalram == 0:
return "0%"
return [str(int(100*totalfree/totalram))+"%", str((totalram+512*1024)>>20)+"GB"]
def argonsysinfo_getcputemp():
try:
tempfp = open("/sys/class/thermal/thermal_zone0/temp", "r")
temp = tempfp.readline()
tempfp.close()
#cval = temp/1000
#fval = 32+9*temp/5000
return float(int(temp)/1000)
except IOError:
return 0
def argonsysinfo_getmaxhddtemp():
maxtempval = 0
try:
hddtempobj = argonsysinfo_gethddtemp()
for curdev in hddtempobj:
if hddtempobj[curdev] > maxtempval:
maxtempval = hddtempobj[curdev]
return maxtempval
except:
return maxtempval
def argonsysinfo_gethddtemp():
# May 2022: Used smartctl, hddtemp is not available on some platforms
hddtempcmd = "/usr/sbin/smartctl"
if os.path.exists(hddtempcmd) == False:
# Fallback for now
hddtempcmd = "/usr/sbin/hddtemp"
outputobj = {}
if os.path.exists(hddtempcmd):
try:
tmp = os.popen("lsblk | grep -e '0 disk' | awk '{print $1}'").read()
alllines = tmp.split("\n")
for curdev in alllines:
if curdev[0:2] == "sd" or curdev[0:2] == "hd":
tempval = argonsysinfo_getdevhddtemp(hddtempcmd,curdev)
if tempval > 0:
outputobj[curdev] = tempval
return outputobj
except:
return outputobj
return outputobj
def argonsysinfo_getdevhddtemp(hddtempcmd, curdev):
cmdstr = ""
if hddtempcmd == "/usr/sbin/hddtemp":
cmdstr = "/usr/sbin/hddtemp -n sata:/dev/"+curdev
elif hddtempcmd == "/usr/sbin/smartctl":
cmdstr = "/usr/sbin/smartctl -d sat -A /dev/"+curdev+" | grep Temperature_Celsius | awk '{print $10}'"
tempval = 0
if len(cmdstr) > 0:
try:
temperaturestr = os.popen(cmdstr+" 2>&1").read()
tempval = float(temperaturestr)
except:
tempval = -1
return tempval
def argonsysinfo_getip():
ipaddr = ""
st = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
# Connect to nonexistent device
st.connect(('254.255.255.255', 1))
ipaddr = st.getsockname()[0]
except Exception:
ipaddr = 'N/A'
finally:
st.close()
return ipaddr
def argonsysinfo_getrootdev():
tmp = os.popen('mount').read()
alllines = tmp.split("\n")
for temp in alllines:
temp = temp.replace('\t', ' ')
temp = temp.strip()
while temp.find(" ") >= 0:
temp = temp.replace(" ", " ")
infolist = temp.split(" ")
if len(infolist) >= 3:
if infolist[2] == "/":
return infolist[0]
return ""
def argonsysinfo_listhddusage():
outputobj = {}
raidlist = argonsysinfo_listraid()
raiddevlist = []
raidctr = 0
while raidctr < len(raidlist['raidlist']):
raiddevlist.append(raidlist['raidlist'][raidctr]['title'])
# TODO: May need to use different method for each raid type (i.e. check raidlist['raidlist'][raidctr]['value'])
#outputobj[raidlist['raidlist'][raidctr]['title']] = {"used":int(raidlist['raidlist'][raidctr]['info']['used']), "total":int(raidlist['raidlist'][raidctr]['info']['size'])}
raidctr = raidctr + 1
rootdev = argonsysinfo_getrootdev()
tmp = os.popen('df').read()
alllines = tmp.split("\n")
for temp in alllines:
temp = temp.replace('\t', ' ')
temp = temp.strip()
while temp.find(" ") >= 0:
temp = temp.replace(" ", " ")
infolist = temp.split(" ")
if len(infolist) >= 6:
if infolist[1] == "Size":
continue
if len(infolist[0]) < 5:
continue
elif infolist[0][0:5] != "/dev/":
continue
curdev = infolist[0]
if curdev == "/dev/root" and rootdev != "":
curdev = rootdev
tmpidx = curdev.rfind("/")
if tmpidx >= 0:
curdev = curdev[tmpidx+1:]
if curdev in raidlist['hddlist']:
# Skip devices that are part of a RAID setup
continue
elif curdev in raiddevlist:
# Skip RAID ID that already have size data
# (use df information otherwise)
if curdev in outputobj:
continue
elif curdev[0:2] == "sd" or curdev[0:2] == "hd":
curdev = curdev[0:-1]
else:
curdev = curdev[0:-2]
# Aggregate values (i.e. sda1, sda2 to sda)
if curdev in outputobj:
outputobj[curdev] = {"used":outputobj[curdev]['used']+int(infolist[2]), "total":outputobj[curdev]['total']+int(infolist[1])}
else:
outputobj[curdev] = {"used":int(infolist[2]), "total":int(infolist[1])}
return outputobj
def argonsysinfo_kbstr(kbval, wholenumbers = True):
remainder = 0
suffixidx = 0
suffixlist = ["KB", "MB", "GB", "TB"]
while kbval > 1023 and suffixidx < len(suffixlist):
remainder = kbval & 1023
kbval = kbval >> 10
suffixidx = suffixidx + 1
#return str(kbval)+"."+str(remainder) + suffixlist[suffixidx]
remainderstr = ""
if kbval < 100 and wholenumbers == False:
remainder = int((remainder+50)/100)
if remainder > 0:
remainderstr = "."+str(remainder)
elif remainder >= 500:
kbval = kbval + 1
return str(kbval)+remainderstr + suffixlist[suffixidx]
def argonsysinfo_listraid():
hddlist = []
outputlist = []
# cat /proc/mdstat
# multiple mdxx from mdstat
# mdadm -D /dev/md1
ramtotal = 0
errorflag = False
try:
hddctr = 0
tempfp = open("/proc/mdstat", "r")
alllines = tempfp.readlines()
for temp in alllines:
temp = temp.replace('\t', ' ')
temp = temp.strip()
while temp.find(" ") >= 0:
temp = temp.replace(" ", " ")
infolist = temp.split(" ")
if len(infolist) >= 4:
# Check if raid info
if infolist[0] != "Personalities" and infolist[1] == ":":
devname = infolist[0]
raidtype = infolist[3]
#raidstatus = infolist[2]
hddctr = 4
while hddctr < len(infolist):
tmpdevname = infolist[hddctr]
tmpidx = tmpdevname.find("[")
if tmpidx >= 0:
tmpdevname = tmpdevname[0:tmpidx]
hddlist.append(tmpdevname)
hddctr = hddctr + 1
devdetail = argonsysinfo_getraiddetail(devname)
outputlist.append({"title": devname, "value": raidtype, "info": devdetail})
tempfp.close()
except IOError:
# No raid
errorflag = True
return {"raidlist": outputlist, "hddlist": hddlist}
def argonsysinfo_getraiddetail(devname):
state = ""
raidtype = ""
size = 0
used = 0
total = 0
working = 0
active = 0
failed = 0
spare = 0
rebuildstat = ""
tmp = os.popen('mdadm -D /dev/'+devname).read()
alllines = tmp.split("\n")
for temp in alllines:
temp = temp.replace('\t', ' ')
temp = temp.strip()
while temp.find(" ") >= 0:
temp = temp.replace(" ", " ")
infolist = temp.split(" : ")
if len(infolist) == 2:
if infolist[0].lower() == "raid level":
raidtype = infolist[1]
elif infolist[0].lower() == "array size":
tmpidx = infolist[1].find(" ")
if tmpidx > 0:
size = (infolist[1][0:tmpidx])
elif infolist[0].lower() == "used dev size":
tmpidx = infolist[1].find(" ")
if tmpidx > 0:
used = (infolist[1][0:tmpidx])
elif infolist[0].lower() == "state":
tmpidx = infolist[1].rfind(" ")
if tmpidx > 0:
state = (infolist[1][tmpidx+1:])
else:
state = infolist[1]
elif infolist[0].lower() == "total devices":
total = infolist[1]
elif infolist[0].lower() == "active devices":
active = infolist[1]
elif infolist[0].lower() == "working devices":
working = infolist[1]
elif infolist[0].lower() == "failed devices":
failed = infolist[1]
elif infolist[0].lower() == "spare devices":
spare = infolist[1]
elif infolist[0].lower() == "rebuild status":
tmpidx = infolist[1].find("%")
if tmpidx > 0:
rebuildstat = (infolist[1][0:tmpidx])+"%"
return {"state": state, "raidtype": raidtype, "size": int(size), "used": int(used), "devices": int(total), "active": int(active), "working": int(working), "failed": int(failed), "spare": int(spare), "rebuildstat": rebuildstat}

40
archive/kickstarter/getfiles.sh Executable file
View File

@@ -0,0 +1,40 @@
#!/bin/bash
ARGONDOWNLOADSERVER=https://download.argon40.com
INSTALLATIONFOLDER=.
versioninfoscript=$INSTALLATIONFOLDER/argon-verioninfo.sh
uninstallscript=$INSTALLATIONFOLDER/argon-uninstall.sh
configscript=$INSTALLATIONFOLDER/argon-config
argondashboardscript=$INSTALLATIONFOLDER/argondashboard.py
basename="argononeup"
daemonname=$basename"d"
eepromrpiscript="/usr/bin/rpi-eeprom-config"
eepromconfigscript=$INSTALLATIONFOLDER/${basename}-eepromconfig.py
daemonscript=$INSTALLATIONFOLDER/$daemonname.py
daemonservice=$INSTALLATIONFOLDER/$daemonname.service
userdaemonservice=$INSTALLATIONFOLDER/${daemonname}user.service
daemonconfigfile=$INSTALLATIONFOLDER/$daemonname.conf
lidconfigscript=$INSTALLATIONFOLDER/${basename}-lidconfig.sh
imagefile=argon40.png
wget $ARGONDOWNLOADSERVER/scripts/argononeup-lidconfig.sh -O $lidconfigscript --quiet
wget $ARGONDOWNLOADSERVER/scripts/argon-rpi-eeprom-config-psu.py -O $eepromconfigscript --quiet
wget $ARGONDOWNLOADSERVER/scripts/${daemonname}.py -O $daemonscript --quiet
wget $ARGONDOWNLOADSERVER/scripts/${daemonname}.service -O $daemonservice --quiet
wget $ARGONDOWNLOADSERVER/scripts/${daemonname}user.service -O $userdaemonservice --quiet
wget $ARGONDOWNLOADSERVER/ups/upsimg.tar.gz -O $INSTALLATIONFOLDER/ups/upsimg.tar.gz --quiet
tar xfz $INSTALLATIONFOLDER/ups/upsimg.tar.gz -C $INSTALLATIONFOLDER/ups/
rm -Rf $INSTALLATIONFOLDER/ups/upsimg.tar.gz
wget "$ARGONDOWNLOADSERVER/scripts/argonpowerbutton-rpigpio.py" -O $INSTALLATIONFOLDER/argonpowerbutton_rpigpio.py --quiet
wget "$ARGONDOWNLOADSERVER/scripts/argonpowerbutton-libgpiod.py" -O $INSTALLATIONFOLDER/argonpowerbutton_libgpiod.py --quiet
wget $ARGONDOWNLOADSERVER/scripts/argonkeyboard.py -O $INSTALLATIONFOLDER/argonkeyboard.py --quiet
wget $ARGONDOWNLOADSERVER/scripts/argondashboard.py -O $INSTALLATIONFOLDER/argondashboard.py --quiet
wget $ARGONDOWNLOADSERVER/scripts/argon-versioninfo.sh -O $versioninfoscript --quiet
wget $ARGONDOWNLOADSERVER/scripts/argonsysinfo.py -O $INSTALLATIONFOLDER/argonsysinfo.py --quiet
wget $ARGONDOWNLOADSERVER/scripts/argonregister-v1.py -O $INSTALLATIONFOLDER/argonregister.py --quiet
wget $ARGONDOWNLOADSERVER/scripts/argon-uninstall.sh -O $uninstallscript --quiet
wget https://download.argon40.com/$imagefile -O /etc/argon/$imagefile --quiet

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Some files were not shown because too many files have changed in this diff Show More