diff --git a/pythonscript/kickstarter/argon-uninstall.sh b/pythonscript/kickstarter/argon-uninstall.sh new file mode 100644 index 0000000..94b782f --- /dev/null +++ b/pythonscript/kickstarter/argon-uninstall.sh @@ -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." diff --git a/pythonscript/kickstarter/argon-verioninfo.sh b/pythonscript/kickstarter/argon-verioninfo.sh new file mode 100644 index 0000000..e869da5 --- /dev/null +++ b/pythonscript/kickstarter/argon-verioninfo.sh @@ -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 diff --git a/pythonscript/kickstarter/argondashboard.py b/pythonscript/kickstarter/argondashboard.py new file mode 100644 index 0000000..78a1877 --- /dev/null +++ b/pythonscript/kickstarter/argondashboard.py @@ -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 diff --git a/pythonscript/kickstarter/argonkeyboard.py b/pythonscript/kickstarter/argonkeyboard.py new file mode 100644 index 0000000..8c1a960 --- /dev/null +++ b/pythonscript/kickstarter/argonkeyboard.py @@ -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) diff --git a/pythonscript/kickstarter/argononeup-eepromconfig.py b/pythonscript/kickstarter/argononeup-eepromconfig.py new file mode 100644 index 0000000..d7c486f --- /dev/null +++ b/pythonscript/kickstarter/argononeup-eepromconfig.py @@ -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() diff --git a/pythonscript/kickstarter/argononeup-lidconfig.sh b/pythonscript/kickstarter/argononeup-lidconfig.sh new file mode 100644 index 0000000..bcc756a --- /dev/null +++ b/pythonscript/kickstarter/argononeup-lidconfig.sh @@ -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 + diff --git a/pythonscript/kickstarter/argononeup.sh b/pythonscript/kickstarter/argononeup.sh new file mode 100644 index 0000000..8d667a3 --- /dev/null +++ b/pythonscript/kickstarter/argononeup.sh @@ -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 + diff --git a/pythonscript/kickstarter/argononeupd.py b/pythonscript/kickstarter/argononeupd.py new file mode 100644 index 0000000..8ea1f0e --- /dev/null +++ b/pythonscript/kickstarter/argononeupd.py @@ -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) diff --git a/pythonscript/kickstarter/argononeupd.service b/pythonscript/kickstarter/argononeupd.service new file mode 100644 index 0000000..6ba565b --- /dev/null +++ b/pythonscript/kickstarter/argononeupd.service @@ -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 diff --git a/pythonscript/kickstarter/argononeupduser.service b/pythonscript/kickstarter/argononeupduser.service new file mode 100644 index 0000000..62481a3 --- /dev/null +++ b/pythonscript/kickstarter/argononeupduser.service @@ -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 diff --git a/pythonscript/kickstarter/argonpowerbutton_libgpiod.py b/pythonscript/kickstarter/argonpowerbutton_libgpiod.py new file mode 100644 index 0000000..484e00c --- /dev/null +++ b/pythonscript/kickstarter/argonpowerbutton_libgpiod.py @@ -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) diff --git a/pythonscript/kickstarter/argonpowerbutton_rpigpio.py b/pythonscript/kickstarter/argonpowerbutton_rpigpio.py new file mode 100644 index 0000000..0d40c8c --- /dev/null +++ b/pythonscript/kickstarter/argonpowerbutton_rpigpio.py @@ -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() diff --git a/pythonscript/kickstarter/argonregister.py b/pythonscript/kickstarter/argonregister.py new file mode 100644 index 0000000..edd9c1d --- /dev/null +++ b/pythonscript/kickstarter/argonregister.py @@ -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) diff --git a/pythonscript/kickstarter/argonsysinfo.py b/pythonscript/kickstarter/argonsysinfo.py new file mode 100644 index 0000000..102f2e5 --- /dev/null +++ b/pythonscript/kickstarter/argonsysinfo.py @@ -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} \ No newline at end of file diff --git a/pythonscript/kickstarter/getfiles.sh b/pythonscript/kickstarter/getfiles.sh new file mode 100755 index 0000000..b1e275d --- /dev/null +++ b/pythonscript/kickstarter/getfiles.sh @@ -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 diff --git a/pythonscript/kickstarter/ups/charge_0.png b/pythonscript/kickstarter/ups/charge_0.png new file mode 100644 index 0000000..23a72a9 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_0.png differ diff --git a/pythonscript/kickstarter/ups/charge_1.png b/pythonscript/kickstarter/ups/charge_1.png new file mode 100644 index 0000000..f9a8827 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_1.png differ diff --git a/pythonscript/kickstarter/ups/charge_10.png b/pythonscript/kickstarter/ups/charge_10.png new file mode 100644 index 0000000..1bfdd1c Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_10.png differ diff --git a/pythonscript/kickstarter/ups/charge_100.png b/pythonscript/kickstarter/ups/charge_100.png new file mode 100644 index 0000000..13f8e7b Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_100.png differ diff --git a/pythonscript/kickstarter/ups/charge_11.png b/pythonscript/kickstarter/ups/charge_11.png new file mode 100644 index 0000000..1a77eea Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_11.png differ diff --git a/pythonscript/kickstarter/ups/charge_12.png b/pythonscript/kickstarter/ups/charge_12.png new file mode 100644 index 0000000..154d28f Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_12.png differ diff --git a/pythonscript/kickstarter/ups/charge_13.png b/pythonscript/kickstarter/ups/charge_13.png new file mode 100644 index 0000000..ad7efcc Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_13.png differ diff --git a/pythonscript/kickstarter/ups/charge_14.png b/pythonscript/kickstarter/ups/charge_14.png new file mode 100644 index 0000000..adbcad5 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_14.png differ diff --git a/pythonscript/kickstarter/ups/charge_15.png b/pythonscript/kickstarter/ups/charge_15.png new file mode 100644 index 0000000..edcad92 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_15.png differ diff --git a/pythonscript/kickstarter/ups/charge_16.png b/pythonscript/kickstarter/ups/charge_16.png new file mode 100644 index 0000000..689b9e0 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_16.png differ diff --git a/pythonscript/kickstarter/ups/charge_17.png b/pythonscript/kickstarter/ups/charge_17.png new file mode 100644 index 0000000..42cb749 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_17.png differ diff --git a/pythonscript/kickstarter/ups/charge_18.png b/pythonscript/kickstarter/ups/charge_18.png new file mode 100644 index 0000000..7a34bc2 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_18.png differ diff --git a/pythonscript/kickstarter/ups/charge_19.png b/pythonscript/kickstarter/ups/charge_19.png new file mode 100644 index 0000000..fc16475 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_19.png differ diff --git a/pythonscript/kickstarter/ups/charge_2.png b/pythonscript/kickstarter/ups/charge_2.png new file mode 100644 index 0000000..0f7b6d8 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_2.png differ diff --git a/pythonscript/kickstarter/ups/charge_20.png b/pythonscript/kickstarter/ups/charge_20.png new file mode 100644 index 0000000..6b2be49 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_20.png differ diff --git a/pythonscript/kickstarter/ups/charge_21.png b/pythonscript/kickstarter/ups/charge_21.png new file mode 100644 index 0000000..fcb4b7d Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_21.png differ diff --git a/pythonscript/kickstarter/ups/charge_22.png b/pythonscript/kickstarter/ups/charge_22.png new file mode 100644 index 0000000..d82869d Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_22.png differ diff --git a/pythonscript/kickstarter/ups/charge_23.png b/pythonscript/kickstarter/ups/charge_23.png new file mode 100644 index 0000000..d4f1759 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_23.png differ diff --git a/pythonscript/kickstarter/ups/charge_24.png b/pythonscript/kickstarter/ups/charge_24.png new file mode 100644 index 0000000..a7c6edc Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_24.png differ diff --git a/pythonscript/kickstarter/ups/charge_25.png b/pythonscript/kickstarter/ups/charge_25.png new file mode 100644 index 0000000..25306fe Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_25.png differ diff --git a/pythonscript/kickstarter/ups/charge_26.png b/pythonscript/kickstarter/ups/charge_26.png new file mode 100644 index 0000000..956ec9b Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_26.png differ diff --git a/pythonscript/kickstarter/ups/charge_27.png b/pythonscript/kickstarter/ups/charge_27.png new file mode 100644 index 0000000..f248199 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_27.png differ diff --git a/pythonscript/kickstarter/ups/charge_28.png b/pythonscript/kickstarter/ups/charge_28.png new file mode 100644 index 0000000..ac42a41 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_28.png differ diff --git a/pythonscript/kickstarter/ups/charge_29.png b/pythonscript/kickstarter/ups/charge_29.png new file mode 100644 index 0000000..c334547 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_29.png differ diff --git a/pythonscript/kickstarter/ups/charge_3.png b/pythonscript/kickstarter/ups/charge_3.png new file mode 100644 index 0000000..700783a Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_3.png differ diff --git a/pythonscript/kickstarter/ups/charge_30.png b/pythonscript/kickstarter/ups/charge_30.png new file mode 100644 index 0000000..fcc7c31 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_30.png differ diff --git a/pythonscript/kickstarter/ups/charge_31.png b/pythonscript/kickstarter/ups/charge_31.png new file mode 100644 index 0000000..696292d Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_31.png differ diff --git a/pythonscript/kickstarter/ups/charge_32.png b/pythonscript/kickstarter/ups/charge_32.png new file mode 100644 index 0000000..2a38059 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_32.png differ diff --git a/pythonscript/kickstarter/ups/charge_33.png b/pythonscript/kickstarter/ups/charge_33.png new file mode 100644 index 0000000..be993d3 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_33.png differ diff --git a/pythonscript/kickstarter/ups/charge_34.png b/pythonscript/kickstarter/ups/charge_34.png new file mode 100644 index 0000000..5156b38 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_34.png differ diff --git a/pythonscript/kickstarter/ups/charge_35.png b/pythonscript/kickstarter/ups/charge_35.png new file mode 100644 index 0000000..eab498c Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_35.png differ diff --git a/pythonscript/kickstarter/ups/charge_36.png b/pythonscript/kickstarter/ups/charge_36.png new file mode 100644 index 0000000..b8b5bc3 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_36.png differ diff --git a/pythonscript/kickstarter/ups/charge_37.png b/pythonscript/kickstarter/ups/charge_37.png new file mode 100644 index 0000000..0358b8a Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_37.png differ diff --git a/pythonscript/kickstarter/ups/charge_38.png b/pythonscript/kickstarter/ups/charge_38.png new file mode 100644 index 0000000..b24e946 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_38.png differ diff --git a/pythonscript/kickstarter/ups/charge_39.png b/pythonscript/kickstarter/ups/charge_39.png new file mode 100644 index 0000000..01ba4f5 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_39.png differ diff --git a/pythonscript/kickstarter/ups/charge_4.png b/pythonscript/kickstarter/ups/charge_4.png new file mode 100644 index 0000000..7b6d1c7 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_4.png differ diff --git a/pythonscript/kickstarter/ups/charge_40.png b/pythonscript/kickstarter/ups/charge_40.png new file mode 100644 index 0000000..bc0762b Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_40.png differ diff --git a/pythonscript/kickstarter/ups/charge_41.png b/pythonscript/kickstarter/ups/charge_41.png new file mode 100644 index 0000000..c94d76e Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_41.png differ diff --git a/pythonscript/kickstarter/ups/charge_42.png b/pythonscript/kickstarter/ups/charge_42.png new file mode 100644 index 0000000..7e54c8f Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_42.png differ diff --git a/pythonscript/kickstarter/ups/charge_43.png b/pythonscript/kickstarter/ups/charge_43.png new file mode 100644 index 0000000..e0cd9b3 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_43.png differ diff --git a/pythonscript/kickstarter/ups/charge_44.png b/pythonscript/kickstarter/ups/charge_44.png new file mode 100644 index 0000000..0379bf2 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_44.png differ diff --git a/pythonscript/kickstarter/ups/charge_45.png b/pythonscript/kickstarter/ups/charge_45.png new file mode 100644 index 0000000..dba5182 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_45.png differ diff --git a/pythonscript/kickstarter/ups/charge_46.png b/pythonscript/kickstarter/ups/charge_46.png new file mode 100644 index 0000000..1bb422c Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_46.png differ diff --git a/pythonscript/kickstarter/ups/charge_47.png b/pythonscript/kickstarter/ups/charge_47.png new file mode 100644 index 0000000..b9549bc Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_47.png differ diff --git a/pythonscript/kickstarter/ups/charge_48.png b/pythonscript/kickstarter/ups/charge_48.png new file mode 100644 index 0000000..d6ca870 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_48.png differ diff --git a/pythonscript/kickstarter/ups/charge_49.png b/pythonscript/kickstarter/ups/charge_49.png new file mode 100644 index 0000000..ba13975 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_49.png differ diff --git a/pythonscript/kickstarter/ups/charge_5.png b/pythonscript/kickstarter/ups/charge_5.png new file mode 100644 index 0000000..67a7337 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_5.png differ diff --git a/pythonscript/kickstarter/ups/charge_50.png b/pythonscript/kickstarter/ups/charge_50.png new file mode 100644 index 0000000..5763b85 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_50.png differ diff --git a/pythonscript/kickstarter/ups/charge_51.png b/pythonscript/kickstarter/ups/charge_51.png new file mode 100644 index 0000000..9216df2 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_51.png differ diff --git a/pythonscript/kickstarter/ups/charge_52.png b/pythonscript/kickstarter/ups/charge_52.png new file mode 100644 index 0000000..f18c40b Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_52.png differ diff --git a/pythonscript/kickstarter/ups/charge_53.png b/pythonscript/kickstarter/ups/charge_53.png new file mode 100644 index 0000000..d063dad Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_53.png differ diff --git a/pythonscript/kickstarter/ups/charge_54.png b/pythonscript/kickstarter/ups/charge_54.png new file mode 100644 index 0000000..9565378 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_54.png differ diff --git a/pythonscript/kickstarter/ups/charge_55.png b/pythonscript/kickstarter/ups/charge_55.png new file mode 100644 index 0000000..b38760e Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_55.png differ diff --git a/pythonscript/kickstarter/ups/charge_56.png b/pythonscript/kickstarter/ups/charge_56.png new file mode 100644 index 0000000..375db21 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_56.png differ diff --git a/pythonscript/kickstarter/ups/charge_57.png b/pythonscript/kickstarter/ups/charge_57.png new file mode 100644 index 0000000..768103c Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_57.png differ diff --git a/pythonscript/kickstarter/ups/charge_58.png b/pythonscript/kickstarter/ups/charge_58.png new file mode 100644 index 0000000..413c040 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_58.png differ diff --git a/pythonscript/kickstarter/ups/charge_59.png b/pythonscript/kickstarter/ups/charge_59.png new file mode 100644 index 0000000..176c8e5 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_59.png differ diff --git a/pythonscript/kickstarter/ups/charge_6.png b/pythonscript/kickstarter/ups/charge_6.png new file mode 100644 index 0000000..d3a4c1f Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_6.png differ diff --git a/pythonscript/kickstarter/ups/charge_60.png b/pythonscript/kickstarter/ups/charge_60.png new file mode 100644 index 0000000..ec4e5c2 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_60.png differ diff --git a/pythonscript/kickstarter/ups/charge_61.png b/pythonscript/kickstarter/ups/charge_61.png new file mode 100644 index 0000000..5b37ad7 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_61.png differ diff --git a/pythonscript/kickstarter/ups/charge_62.png b/pythonscript/kickstarter/ups/charge_62.png new file mode 100644 index 0000000..fe3f525 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_62.png differ diff --git a/pythonscript/kickstarter/ups/charge_63.png b/pythonscript/kickstarter/ups/charge_63.png new file mode 100644 index 0000000..979c57b Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_63.png differ diff --git a/pythonscript/kickstarter/ups/charge_64.png b/pythonscript/kickstarter/ups/charge_64.png new file mode 100644 index 0000000..4338286 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_64.png differ diff --git a/pythonscript/kickstarter/ups/charge_65.png b/pythonscript/kickstarter/ups/charge_65.png new file mode 100644 index 0000000..3b46b51 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_65.png differ diff --git a/pythonscript/kickstarter/ups/charge_66.png b/pythonscript/kickstarter/ups/charge_66.png new file mode 100644 index 0000000..7349984 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_66.png differ diff --git a/pythonscript/kickstarter/ups/charge_67.png b/pythonscript/kickstarter/ups/charge_67.png new file mode 100644 index 0000000..7ec7d82 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_67.png differ diff --git a/pythonscript/kickstarter/ups/charge_68.png b/pythonscript/kickstarter/ups/charge_68.png new file mode 100644 index 0000000..c9537ac Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_68.png differ diff --git a/pythonscript/kickstarter/ups/charge_69.png b/pythonscript/kickstarter/ups/charge_69.png new file mode 100644 index 0000000..8ef5fda Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_69.png differ diff --git a/pythonscript/kickstarter/ups/charge_7.png b/pythonscript/kickstarter/ups/charge_7.png new file mode 100644 index 0000000..74f291e Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_7.png differ diff --git a/pythonscript/kickstarter/ups/charge_70.png b/pythonscript/kickstarter/ups/charge_70.png new file mode 100644 index 0000000..c0a37ed Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_70.png differ diff --git a/pythonscript/kickstarter/ups/charge_71.png b/pythonscript/kickstarter/ups/charge_71.png new file mode 100644 index 0000000..ec81919 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_71.png differ diff --git a/pythonscript/kickstarter/ups/charge_72.png b/pythonscript/kickstarter/ups/charge_72.png new file mode 100644 index 0000000..ef15dc3 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_72.png differ diff --git a/pythonscript/kickstarter/ups/charge_73.png b/pythonscript/kickstarter/ups/charge_73.png new file mode 100644 index 0000000..ed8ce78 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_73.png differ diff --git a/pythonscript/kickstarter/ups/charge_74.png b/pythonscript/kickstarter/ups/charge_74.png new file mode 100644 index 0000000..8158b78 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_74.png differ diff --git a/pythonscript/kickstarter/ups/charge_75.png b/pythonscript/kickstarter/ups/charge_75.png new file mode 100644 index 0000000..52bfa33 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_75.png differ diff --git a/pythonscript/kickstarter/ups/charge_76.png b/pythonscript/kickstarter/ups/charge_76.png new file mode 100644 index 0000000..5936d04 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_76.png differ diff --git a/pythonscript/kickstarter/ups/charge_77.png b/pythonscript/kickstarter/ups/charge_77.png new file mode 100644 index 0000000..c1bddac Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_77.png differ diff --git a/pythonscript/kickstarter/ups/charge_78.png b/pythonscript/kickstarter/ups/charge_78.png new file mode 100644 index 0000000..cc7a791 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_78.png differ diff --git a/pythonscript/kickstarter/ups/charge_79.png b/pythonscript/kickstarter/ups/charge_79.png new file mode 100644 index 0000000..bddca01 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_79.png differ diff --git a/pythonscript/kickstarter/ups/charge_8.png b/pythonscript/kickstarter/ups/charge_8.png new file mode 100644 index 0000000..cdcea70 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_8.png differ diff --git a/pythonscript/kickstarter/ups/charge_80.png b/pythonscript/kickstarter/ups/charge_80.png new file mode 100644 index 0000000..0ae3eab Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_80.png differ diff --git a/pythonscript/kickstarter/ups/charge_81.png b/pythonscript/kickstarter/ups/charge_81.png new file mode 100644 index 0000000..373c905 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_81.png differ diff --git a/pythonscript/kickstarter/ups/charge_82.png b/pythonscript/kickstarter/ups/charge_82.png new file mode 100644 index 0000000..523b7c3 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_82.png differ diff --git a/pythonscript/kickstarter/ups/charge_83.png b/pythonscript/kickstarter/ups/charge_83.png new file mode 100644 index 0000000..d082067 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_83.png differ diff --git a/pythonscript/kickstarter/ups/charge_84.png b/pythonscript/kickstarter/ups/charge_84.png new file mode 100644 index 0000000..ac590a1 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_84.png differ diff --git a/pythonscript/kickstarter/ups/charge_85.png b/pythonscript/kickstarter/ups/charge_85.png new file mode 100644 index 0000000..da3ec15 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_85.png differ diff --git a/pythonscript/kickstarter/ups/charge_86.png b/pythonscript/kickstarter/ups/charge_86.png new file mode 100644 index 0000000..a188d2d Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_86.png differ diff --git a/pythonscript/kickstarter/ups/charge_87.png b/pythonscript/kickstarter/ups/charge_87.png new file mode 100644 index 0000000..ca0a478 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_87.png differ diff --git a/pythonscript/kickstarter/ups/charge_88.png b/pythonscript/kickstarter/ups/charge_88.png new file mode 100644 index 0000000..2065265 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_88.png differ diff --git a/pythonscript/kickstarter/ups/charge_89.png b/pythonscript/kickstarter/ups/charge_89.png new file mode 100644 index 0000000..a63ed06 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_89.png differ diff --git a/pythonscript/kickstarter/ups/charge_9.png b/pythonscript/kickstarter/ups/charge_9.png new file mode 100644 index 0000000..1f8b895 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_9.png differ diff --git a/pythonscript/kickstarter/ups/charge_90.png b/pythonscript/kickstarter/ups/charge_90.png new file mode 100644 index 0000000..082c4b7 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_90.png differ diff --git a/pythonscript/kickstarter/ups/charge_91.png b/pythonscript/kickstarter/ups/charge_91.png new file mode 100644 index 0000000..8b30deb Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_91.png differ diff --git a/pythonscript/kickstarter/ups/charge_92.png b/pythonscript/kickstarter/ups/charge_92.png new file mode 100644 index 0000000..dbae546 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_92.png differ diff --git a/pythonscript/kickstarter/ups/charge_93.png b/pythonscript/kickstarter/ups/charge_93.png new file mode 100644 index 0000000..34c9ecc Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_93.png differ diff --git a/pythonscript/kickstarter/ups/charge_94.png b/pythonscript/kickstarter/ups/charge_94.png new file mode 100644 index 0000000..1995846 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_94.png differ diff --git a/pythonscript/kickstarter/ups/charge_95.png b/pythonscript/kickstarter/ups/charge_95.png new file mode 100644 index 0000000..1b8e1c2 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_95.png differ diff --git a/pythonscript/kickstarter/ups/charge_96.png b/pythonscript/kickstarter/ups/charge_96.png new file mode 100644 index 0000000..97504da Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_96.png differ diff --git a/pythonscript/kickstarter/ups/charge_97.png b/pythonscript/kickstarter/ups/charge_97.png new file mode 100644 index 0000000..25b2176 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_97.png differ diff --git a/pythonscript/kickstarter/ups/charge_98.png b/pythonscript/kickstarter/ups/charge_98.png new file mode 100644 index 0000000..12cf1c9 Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_98.png differ diff --git a/pythonscript/kickstarter/ups/charge_99.png b/pythonscript/kickstarter/ups/charge_99.png new file mode 100644 index 0000000..657554a Binary files /dev/null and b/pythonscript/kickstarter/ups/charge_99.png differ diff --git a/pythonscript/kickstarter/ups/discharge_0.png b/pythonscript/kickstarter/ups/discharge_0.png new file mode 100644 index 0000000..a30674c Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_0.png differ diff --git a/pythonscript/kickstarter/ups/discharge_1.png b/pythonscript/kickstarter/ups/discharge_1.png new file mode 100644 index 0000000..35f3f7a Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_1.png differ diff --git a/pythonscript/kickstarter/ups/discharge_10.png b/pythonscript/kickstarter/ups/discharge_10.png new file mode 100644 index 0000000..16448a5 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_10.png differ diff --git a/pythonscript/kickstarter/ups/discharge_100.png b/pythonscript/kickstarter/ups/discharge_100.png new file mode 100644 index 0000000..9496526 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_100.png differ diff --git a/pythonscript/kickstarter/ups/discharge_11.png b/pythonscript/kickstarter/ups/discharge_11.png new file mode 100644 index 0000000..8d84831 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_11.png differ diff --git a/pythonscript/kickstarter/ups/discharge_12.png b/pythonscript/kickstarter/ups/discharge_12.png new file mode 100644 index 0000000..8a51bbe Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_12.png differ diff --git a/pythonscript/kickstarter/ups/discharge_13.png b/pythonscript/kickstarter/ups/discharge_13.png new file mode 100644 index 0000000..d284ef9 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_13.png differ diff --git a/pythonscript/kickstarter/ups/discharge_14.png b/pythonscript/kickstarter/ups/discharge_14.png new file mode 100644 index 0000000..66f405f Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_14.png differ diff --git a/pythonscript/kickstarter/ups/discharge_15.png b/pythonscript/kickstarter/ups/discharge_15.png new file mode 100644 index 0000000..2a76ac8 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_15.png differ diff --git a/pythonscript/kickstarter/ups/discharge_16.png b/pythonscript/kickstarter/ups/discharge_16.png new file mode 100644 index 0000000..9ebea3c Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_16.png differ diff --git a/pythonscript/kickstarter/ups/discharge_17.png b/pythonscript/kickstarter/ups/discharge_17.png new file mode 100644 index 0000000..ac66a03 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_17.png differ diff --git a/pythonscript/kickstarter/ups/discharge_18.png b/pythonscript/kickstarter/ups/discharge_18.png new file mode 100644 index 0000000..d28a73d Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_18.png differ diff --git a/pythonscript/kickstarter/ups/discharge_19.png b/pythonscript/kickstarter/ups/discharge_19.png new file mode 100644 index 0000000..7764268 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_19.png differ diff --git a/pythonscript/kickstarter/ups/discharge_2.png b/pythonscript/kickstarter/ups/discharge_2.png new file mode 100644 index 0000000..e6812e3 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_2.png differ diff --git a/pythonscript/kickstarter/ups/discharge_20.png b/pythonscript/kickstarter/ups/discharge_20.png new file mode 100644 index 0000000..70b9823 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_20.png differ diff --git a/pythonscript/kickstarter/ups/discharge_21.png b/pythonscript/kickstarter/ups/discharge_21.png new file mode 100644 index 0000000..ae1e310 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_21.png differ diff --git a/pythonscript/kickstarter/ups/discharge_22.png b/pythonscript/kickstarter/ups/discharge_22.png new file mode 100644 index 0000000..12909cd Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_22.png differ diff --git a/pythonscript/kickstarter/ups/discharge_23.png b/pythonscript/kickstarter/ups/discharge_23.png new file mode 100644 index 0000000..7f1b416 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_23.png differ diff --git a/pythonscript/kickstarter/ups/discharge_24.png b/pythonscript/kickstarter/ups/discharge_24.png new file mode 100644 index 0000000..e2c1436 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_24.png differ diff --git a/pythonscript/kickstarter/ups/discharge_25.png b/pythonscript/kickstarter/ups/discharge_25.png new file mode 100644 index 0000000..3557203 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_25.png differ diff --git a/pythonscript/kickstarter/ups/discharge_26.png b/pythonscript/kickstarter/ups/discharge_26.png new file mode 100644 index 0000000..3a599c1 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_26.png differ diff --git a/pythonscript/kickstarter/ups/discharge_27.png b/pythonscript/kickstarter/ups/discharge_27.png new file mode 100644 index 0000000..7ac6725 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_27.png differ diff --git a/pythonscript/kickstarter/ups/discharge_28.png b/pythonscript/kickstarter/ups/discharge_28.png new file mode 100644 index 0000000..56096ed Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_28.png differ diff --git a/pythonscript/kickstarter/ups/discharge_29.png b/pythonscript/kickstarter/ups/discharge_29.png new file mode 100644 index 0000000..c277aa7 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_29.png differ diff --git a/pythonscript/kickstarter/ups/discharge_3.png b/pythonscript/kickstarter/ups/discharge_3.png new file mode 100644 index 0000000..647deea Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_3.png differ diff --git a/pythonscript/kickstarter/ups/discharge_30.png b/pythonscript/kickstarter/ups/discharge_30.png new file mode 100644 index 0000000..4af715b Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_30.png differ diff --git a/pythonscript/kickstarter/ups/discharge_31.png b/pythonscript/kickstarter/ups/discharge_31.png new file mode 100644 index 0000000..f115ac6 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_31.png differ diff --git a/pythonscript/kickstarter/ups/discharge_32.png b/pythonscript/kickstarter/ups/discharge_32.png new file mode 100644 index 0000000..190e30d Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_32.png differ diff --git a/pythonscript/kickstarter/ups/discharge_33.png b/pythonscript/kickstarter/ups/discharge_33.png new file mode 100644 index 0000000..f4ef185 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_33.png differ diff --git a/pythonscript/kickstarter/ups/discharge_34.png b/pythonscript/kickstarter/ups/discharge_34.png new file mode 100644 index 0000000..e407b27 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_34.png differ diff --git a/pythonscript/kickstarter/ups/discharge_35.png b/pythonscript/kickstarter/ups/discharge_35.png new file mode 100644 index 0000000..6448ac5 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_35.png differ diff --git a/pythonscript/kickstarter/ups/discharge_36.png b/pythonscript/kickstarter/ups/discharge_36.png new file mode 100644 index 0000000..4c2931e Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_36.png differ diff --git a/pythonscript/kickstarter/ups/discharge_37.png b/pythonscript/kickstarter/ups/discharge_37.png new file mode 100644 index 0000000..0de290c Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_37.png differ diff --git a/pythonscript/kickstarter/ups/discharge_38.png b/pythonscript/kickstarter/ups/discharge_38.png new file mode 100644 index 0000000..a571a77 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_38.png differ diff --git a/pythonscript/kickstarter/ups/discharge_39.png b/pythonscript/kickstarter/ups/discharge_39.png new file mode 100644 index 0000000..d47f6f1 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_39.png differ diff --git a/pythonscript/kickstarter/ups/discharge_4.png b/pythonscript/kickstarter/ups/discharge_4.png new file mode 100644 index 0000000..7a61781 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_4.png differ diff --git a/pythonscript/kickstarter/ups/discharge_40.png b/pythonscript/kickstarter/ups/discharge_40.png new file mode 100644 index 0000000..a01fc76 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_40.png differ diff --git a/pythonscript/kickstarter/ups/discharge_41.png b/pythonscript/kickstarter/ups/discharge_41.png new file mode 100644 index 0000000..fa80f42 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_41.png differ diff --git a/pythonscript/kickstarter/ups/discharge_42.png b/pythonscript/kickstarter/ups/discharge_42.png new file mode 100644 index 0000000..28b49ef Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_42.png differ diff --git a/pythonscript/kickstarter/ups/discharge_43.png b/pythonscript/kickstarter/ups/discharge_43.png new file mode 100644 index 0000000..842e248 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_43.png differ diff --git a/pythonscript/kickstarter/ups/discharge_44.png b/pythonscript/kickstarter/ups/discharge_44.png new file mode 100644 index 0000000..fb540c8 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_44.png differ diff --git a/pythonscript/kickstarter/ups/discharge_45.png b/pythonscript/kickstarter/ups/discharge_45.png new file mode 100644 index 0000000..898269e Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_45.png differ diff --git a/pythonscript/kickstarter/ups/discharge_46.png b/pythonscript/kickstarter/ups/discharge_46.png new file mode 100644 index 0000000..1fe7ce3 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_46.png differ diff --git a/pythonscript/kickstarter/ups/discharge_47.png b/pythonscript/kickstarter/ups/discharge_47.png new file mode 100644 index 0000000..2c5221c Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_47.png differ diff --git a/pythonscript/kickstarter/ups/discharge_48.png b/pythonscript/kickstarter/ups/discharge_48.png new file mode 100644 index 0000000..c628308 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_48.png differ diff --git a/pythonscript/kickstarter/ups/discharge_49.png b/pythonscript/kickstarter/ups/discharge_49.png new file mode 100644 index 0000000..054c91f Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_49.png differ diff --git a/pythonscript/kickstarter/ups/discharge_5.png b/pythonscript/kickstarter/ups/discharge_5.png new file mode 100644 index 0000000..27a7ea9 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_5.png differ diff --git a/pythonscript/kickstarter/ups/discharge_50.png b/pythonscript/kickstarter/ups/discharge_50.png new file mode 100644 index 0000000..2b9c6f8 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_50.png differ diff --git a/pythonscript/kickstarter/ups/discharge_51.png b/pythonscript/kickstarter/ups/discharge_51.png new file mode 100644 index 0000000..bc9a530 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_51.png differ diff --git a/pythonscript/kickstarter/ups/discharge_52.png b/pythonscript/kickstarter/ups/discharge_52.png new file mode 100644 index 0000000..fa05a5b Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_52.png differ diff --git a/pythonscript/kickstarter/ups/discharge_53.png b/pythonscript/kickstarter/ups/discharge_53.png new file mode 100644 index 0000000..0592bd4 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_53.png differ diff --git a/pythonscript/kickstarter/ups/discharge_54.png b/pythonscript/kickstarter/ups/discharge_54.png new file mode 100644 index 0000000..03a70b7 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_54.png differ diff --git a/pythonscript/kickstarter/ups/discharge_55.png b/pythonscript/kickstarter/ups/discharge_55.png new file mode 100644 index 0000000..a330038 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_55.png differ diff --git a/pythonscript/kickstarter/ups/discharge_56.png b/pythonscript/kickstarter/ups/discharge_56.png new file mode 100644 index 0000000..5fa95ee Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_56.png differ diff --git a/pythonscript/kickstarter/ups/discharge_57.png b/pythonscript/kickstarter/ups/discharge_57.png new file mode 100644 index 0000000..8e77a5f Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_57.png differ diff --git a/pythonscript/kickstarter/ups/discharge_58.png b/pythonscript/kickstarter/ups/discharge_58.png new file mode 100644 index 0000000..5bf2961 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_58.png differ diff --git a/pythonscript/kickstarter/ups/discharge_59.png b/pythonscript/kickstarter/ups/discharge_59.png new file mode 100644 index 0000000..f12df42 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_59.png differ diff --git a/pythonscript/kickstarter/ups/discharge_6.png b/pythonscript/kickstarter/ups/discharge_6.png new file mode 100644 index 0000000..cb0eeff Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_6.png differ diff --git a/pythonscript/kickstarter/ups/discharge_60.png b/pythonscript/kickstarter/ups/discharge_60.png new file mode 100644 index 0000000..7833f2d Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_60.png differ diff --git a/pythonscript/kickstarter/ups/discharge_61.png b/pythonscript/kickstarter/ups/discharge_61.png new file mode 100644 index 0000000..66e0381 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_61.png differ diff --git a/pythonscript/kickstarter/ups/discharge_62.png b/pythonscript/kickstarter/ups/discharge_62.png new file mode 100644 index 0000000..004695e Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_62.png differ diff --git a/pythonscript/kickstarter/ups/discharge_63.png b/pythonscript/kickstarter/ups/discharge_63.png new file mode 100644 index 0000000..9d8d02f Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_63.png differ diff --git a/pythonscript/kickstarter/ups/discharge_64.png b/pythonscript/kickstarter/ups/discharge_64.png new file mode 100644 index 0000000..fc688a1 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_64.png differ diff --git a/pythonscript/kickstarter/ups/discharge_65.png b/pythonscript/kickstarter/ups/discharge_65.png new file mode 100644 index 0000000..b9f5ff0 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_65.png differ diff --git a/pythonscript/kickstarter/ups/discharge_66.png b/pythonscript/kickstarter/ups/discharge_66.png new file mode 100644 index 0000000..7a59198 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_66.png differ diff --git a/pythonscript/kickstarter/ups/discharge_67.png b/pythonscript/kickstarter/ups/discharge_67.png new file mode 100644 index 0000000..25fbbe2 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_67.png differ diff --git a/pythonscript/kickstarter/ups/discharge_68.png b/pythonscript/kickstarter/ups/discharge_68.png new file mode 100644 index 0000000..acee426 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_68.png differ diff --git a/pythonscript/kickstarter/ups/discharge_69.png b/pythonscript/kickstarter/ups/discharge_69.png new file mode 100644 index 0000000..1b519b3 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_69.png differ diff --git a/pythonscript/kickstarter/ups/discharge_7.png b/pythonscript/kickstarter/ups/discharge_7.png new file mode 100644 index 0000000..286b310 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_7.png differ diff --git a/pythonscript/kickstarter/ups/discharge_70.png b/pythonscript/kickstarter/ups/discharge_70.png new file mode 100644 index 0000000..be480a7 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_70.png differ diff --git a/pythonscript/kickstarter/ups/discharge_71.png b/pythonscript/kickstarter/ups/discharge_71.png new file mode 100644 index 0000000..4fcc530 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_71.png differ diff --git a/pythonscript/kickstarter/ups/discharge_72.png b/pythonscript/kickstarter/ups/discharge_72.png new file mode 100644 index 0000000..ca8a765 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_72.png differ diff --git a/pythonscript/kickstarter/ups/discharge_73.png b/pythonscript/kickstarter/ups/discharge_73.png new file mode 100644 index 0000000..35ed1c6 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_73.png differ diff --git a/pythonscript/kickstarter/ups/discharge_74.png b/pythonscript/kickstarter/ups/discharge_74.png new file mode 100644 index 0000000..ace632d Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_74.png differ diff --git a/pythonscript/kickstarter/ups/discharge_75.png b/pythonscript/kickstarter/ups/discharge_75.png new file mode 100644 index 0000000..137dc80 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_75.png differ diff --git a/pythonscript/kickstarter/ups/discharge_76.png b/pythonscript/kickstarter/ups/discharge_76.png new file mode 100644 index 0000000..55b60df Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_76.png differ diff --git a/pythonscript/kickstarter/ups/discharge_77.png b/pythonscript/kickstarter/ups/discharge_77.png new file mode 100644 index 0000000..4b5a6cb Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_77.png differ diff --git a/pythonscript/kickstarter/ups/discharge_78.png b/pythonscript/kickstarter/ups/discharge_78.png new file mode 100644 index 0000000..a2b844a Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_78.png differ diff --git a/pythonscript/kickstarter/ups/discharge_79.png b/pythonscript/kickstarter/ups/discharge_79.png new file mode 100644 index 0000000..cc501e7 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_79.png differ diff --git a/pythonscript/kickstarter/ups/discharge_8.png b/pythonscript/kickstarter/ups/discharge_8.png new file mode 100644 index 0000000..1648101 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_8.png differ diff --git a/pythonscript/kickstarter/ups/discharge_80.png b/pythonscript/kickstarter/ups/discharge_80.png new file mode 100644 index 0000000..c456340 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_80.png differ diff --git a/pythonscript/kickstarter/ups/discharge_81.png b/pythonscript/kickstarter/ups/discharge_81.png new file mode 100644 index 0000000..5e957fe Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_81.png differ diff --git a/pythonscript/kickstarter/ups/discharge_82.png b/pythonscript/kickstarter/ups/discharge_82.png new file mode 100644 index 0000000..2bd31ef Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_82.png differ diff --git a/pythonscript/kickstarter/ups/discharge_83.png b/pythonscript/kickstarter/ups/discharge_83.png new file mode 100644 index 0000000..71bee1e Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_83.png differ diff --git a/pythonscript/kickstarter/ups/discharge_84.png b/pythonscript/kickstarter/ups/discharge_84.png new file mode 100644 index 0000000..6e36e1f Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_84.png differ diff --git a/pythonscript/kickstarter/ups/discharge_85.png b/pythonscript/kickstarter/ups/discharge_85.png new file mode 100644 index 0000000..2c2c014 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_85.png differ diff --git a/pythonscript/kickstarter/ups/discharge_86.png b/pythonscript/kickstarter/ups/discharge_86.png new file mode 100644 index 0000000..dc12d6d Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_86.png differ diff --git a/pythonscript/kickstarter/ups/discharge_87.png b/pythonscript/kickstarter/ups/discharge_87.png new file mode 100644 index 0000000..1895e9d Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_87.png differ diff --git a/pythonscript/kickstarter/ups/discharge_88.png b/pythonscript/kickstarter/ups/discharge_88.png new file mode 100644 index 0000000..0700c7f Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_88.png differ diff --git a/pythonscript/kickstarter/ups/discharge_89.png b/pythonscript/kickstarter/ups/discharge_89.png new file mode 100644 index 0000000..10ee6d3 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_89.png differ diff --git a/pythonscript/kickstarter/ups/discharge_9.png b/pythonscript/kickstarter/ups/discharge_9.png new file mode 100644 index 0000000..96692c1 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_9.png differ diff --git a/pythonscript/kickstarter/ups/discharge_90.png b/pythonscript/kickstarter/ups/discharge_90.png new file mode 100644 index 0000000..732bd28 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_90.png differ diff --git a/pythonscript/kickstarter/ups/discharge_91.png b/pythonscript/kickstarter/ups/discharge_91.png new file mode 100644 index 0000000..cab8194 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_91.png differ diff --git a/pythonscript/kickstarter/ups/discharge_92.png b/pythonscript/kickstarter/ups/discharge_92.png new file mode 100644 index 0000000..294a9c9 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_92.png differ diff --git a/pythonscript/kickstarter/ups/discharge_93.png b/pythonscript/kickstarter/ups/discharge_93.png new file mode 100644 index 0000000..3bcd7b7 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_93.png differ diff --git a/pythonscript/kickstarter/ups/discharge_94.png b/pythonscript/kickstarter/ups/discharge_94.png new file mode 100644 index 0000000..4de3af2 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_94.png differ diff --git a/pythonscript/kickstarter/ups/discharge_95.png b/pythonscript/kickstarter/ups/discharge_95.png new file mode 100644 index 0000000..959af91 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_95.png differ diff --git a/pythonscript/kickstarter/ups/discharge_96.png b/pythonscript/kickstarter/ups/discharge_96.png new file mode 100644 index 0000000..396d6e5 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_96.png differ diff --git a/pythonscript/kickstarter/ups/discharge_97.png b/pythonscript/kickstarter/ups/discharge_97.png new file mode 100644 index 0000000..9f9fe5c Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_97.png differ diff --git a/pythonscript/kickstarter/ups/discharge_98.png b/pythonscript/kickstarter/ups/discharge_98.png new file mode 100644 index 0000000..5107108 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_98.png differ diff --git a/pythonscript/kickstarter/ups/discharge_99.png b/pythonscript/kickstarter/ups/discharge_99.png new file mode 100644 index 0000000..a0d4bd3 Binary files /dev/null and b/pythonscript/kickstarter/ups/discharge_99.png differ diff --git a/pythonscript/kickstarter/ups/loading_0.png b/pythonscript/kickstarter/ups/loading_0.png new file mode 100644 index 0000000..2b82e29 Binary files /dev/null and b/pythonscript/kickstarter/ups/loading_0.png differ