diff --git a/source/ar1uninstall.png b/source/ar1uninstall.png deleted file mode 100644 index 2232b6f..0000000 Binary files a/source/ar1uninstall.png and /dev/null differ diff --git a/source/argoneon.png b/source/argoneon.png new file mode 100644 index 0000000..9a06439 Binary files /dev/null and b/source/argoneon.png differ diff --git a/source/oled/bgcpu.bin b/source/oled/bgcpu.bin new file mode 100644 index 0000000..8bbb026 Binary files /dev/null and b/source/oled/bgcpu.bin differ diff --git a/source/oled/bgdefault.bin b/source/oled/bgdefault.bin new file mode 100644 index 0000000..a7a4446 Binary files /dev/null and b/source/oled/bgdefault.bin differ diff --git a/source/oled/bgip.bin b/source/oled/bgip.bin new file mode 100644 index 0000000..a5fcd5f Binary files /dev/null and b/source/oled/bgip.bin differ diff --git a/source/oled/bgraid.bin b/source/oled/bgraid.bin new file mode 100644 index 0000000..3ebd92c Binary files /dev/null and b/source/oled/bgraid.bin differ diff --git a/source/oled/bgram.bin b/source/oled/bgram.bin new file mode 100644 index 0000000..08333b8 Binary files /dev/null and b/source/oled/bgram.bin differ diff --git a/source/oled/bgstorage.bin b/source/oled/bgstorage.bin new file mode 100644 index 0000000..dd28c62 Binary files /dev/null and b/source/oled/bgstorage.bin differ diff --git a/source/oled/bgtemp.bin b/source/oled/bgtemp.bin new file mode 100644 index 0000000..aef7da5 Binary files /dev/null and b/source/oled/bgtemp.bin differ diff --git a/source/oled/bgtime.bin b/source/oled/bgtime.bin new file mode 100644 index 0000000..ae064a2 Binary files /dev/null and b/source/oled/bgtime.bin differ diff --git a/source/oled/font16x12.bin b/source/oled/font16x12.bin new file mode 100644 index 0000000..e8c819f Binary files /dev/null and b/source/oled/font16x12.bin differ diff --git a/source/oled/font16x8.bin b/source/oled/font16x8.bin new file mode 100644 index 0000000..1234c77 Binary files /dev/null and b/source/oled/font16x8.bin differ diff --git a/source/oled/font24x16.bin b/source/oled/font24x16.bin new file mode 100644 index 0000000..9a4b1a7 Binary files /dev/null and b/source/oled/font24x16.bin differ diff --git a/source/oled/font32x24.bin b/source/oled/font32x24.bin new file mode 100644 index 0000000..0ed12db Binary files /dev/null and b/source/oled/font32x24.bin differ diff --git a/source/oled/font48x32.bin b/source/oled/font48x32.bin new file mode 100644 index 0000000..843f756 Binary files /dev/null and b/source/oled/font48x32.bin differ diff --git a/source/oled/font64x48.bin b/source/oled/font64x48.bin new file mode 100644 index 0000000..9a03080 Binary files /dev/null and b/source/oled/font64x48.bin differ diff --git a/source/oled/font8x6.bin b/source/oled/font8x6.bin new file mode 100644 index 0000000..3b8f7ad Binary files /dev/null and b/source/oled/font8x6.bin differ diff --git a/source/oled/logo1v5.bin b/source/oled/logo1v5.bin new file mode 100644 index 0000000..43aeed0 Binary files /dev/null and b/source/oled/logo1v5.bin differ diff --git a/source/scripts/argon-blstrdac.sh b/source/scripts/argon-blstrdac.sh new file mode 100644 index 0000000..1da8613 --- /dev/null +++ b/source/scripts/argon-blstrdac.sh @@ -0,0 +1,194 @@ +#!/bin/bash + + +if [ -e /boot/firmware/config.txt ] ; then + FIRMWARE=/firmware +else + FIRMWARE= +fi +CONFIG=/boot${FIRMWARE}/config.txt + +# Check if Raspbian +CHECKPLATFORM="Others" +if [ -f "/etc/os-release" ] +then + source /etc/os-release + if [ "$ID" = "raspbian" ] + then + CHECKPLATFORM="Raspbian" + elif [ "$ID" = "debian" ] + then + # For backwards compatibility, continue using raspbian + CHECKPLATFORM="Raspbian" + fi +fi + + +echo "------------------------------------" +echo " Argon BLSTR DAC Configuration Tool" +echo "------------------------------------" + +get_number () { + read curnumber + if [ -z "$curnumber" ] + then + echo "-2" + return + elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]] + then + if [ $curnumber -lt 0 ] + then + echo "-1" + return + elif [ $curnumber -gt 100 ] + then + echo "-1" + return + fi + echo $curnumber + return + fi + echo "-1" + return +} + +irexecrcfile=/etc/lirc/irexec.lircrc +irexecshfile=/etc/argon/argonirexec +irdecodefile=/etc/argon/argonirdecoder +kodiuserdatafolder="$HOME/.kodi/userdata" +kodilircmapfile="$kodiuserdatafolder/Lircmap.xml" +remotemode="" +needinstallation=1 + + +CONFIGSETTING="dtoverlay=hifiberry-dacplus,slave" + +if grep -q -E "$CONFIGSETTING" $CONFIG +then + # Already installed + needinstallation=0 +fi + + +loopflag=1 +while [ $loopflag -eq 1 ] +do + echo + echo "Select option:" + if [ $needinstallation -eq 1 ] + then + echo " 1. Enable BLSTR DAC" + echo " 2. Cancel" + echo -n "Enter Number (1-2):" + else + echo " 1. Select audio configuration" + echo " 2. Disable BLSTR DAC" + echo " 3. Cancel" + echo -n "Enter Number (1-3):" + fi + newmode=$( get_number ) + if [[ $newmode -ge 1 && $newmode -le 3 ]] + then + if [[ $needinstallation -eq 1 && $newmode -ge 3 ]] + then + # Invalid option + loopflag=1 + # Uninstall + else + loopflag=0 + if [ $needinstallation -eq 1 ] + then + if [ $newmode -eq 2 ] + then + # Cancel + newmode=4 + fi + else + if [ $newmode -eq 1 ] + then + # Audio Conf + newmode=3 + fi + + fi + + + fi + fi +done + +needrestart=0 + +echo +if [ $newmode -eq 2 ] +then + # Uninstall + blstrdactmpconfigfile=/dev/shm/argonblstrdacconfig.txt + + cat $CONFIG | grep -v "$CONFIGSETTING" > $blstrdactmpconfigfile + cat $blstrdactmpconfigfile | sudo tee $CONFIG 1> /dev/null + sudo rm $blstrdactmpconfigfile + + echo "Uninstall Completed" + echo + + needrestart=1 + +elif [ $newmode -eq 3 ] +then + # Audio Conf + + loopflag=1 + while [ $loopflag -eq 1 ] + do + echo + echo "Select audio configuration:" + echo " 1. PulseAudio" + echo " 2. Pipewire" + echo " 3. Cancel" + echo -n "Enter Number (1-3):" + + newmode=$( get_number ) + if [[ $newmode -ge 1 && $newmode -le 3 ]] + then + loopflag=0 + fi + done + + if [[ $newmode -ge 1 && $newmode -le 2 ]] + then + sudo raspi-config nonint do_audioconf $newmode + else + echo "Cancelled" + fi + +elif [ $newmode -eq 1 ] +then + # Install + + echo "$CONFIGSETTING" | sudo tee -a $CONFIG 1> /dev/null + + #sudo raspi-config nonint do_audioconf 1 + #systemctl --global -q disable pipewire-pulse + #systemctl --global -q disable wireplumber + #systemctl --global -q enable pulseaudio + #if [ -e /etc/alsa/conf.d/99-pipewire-default.conf ] ; then + # rm /etc/alsa/conf.d/99-pipewire-default.conf + #fi + + echo "Please run configuration and choose different audio configuration if there are problems" + + needrestart=1 +else + echo "Cancelled" + #exit +fi + + +echo +#echo "Thank you." +if [ $needrestart -eq 1 ] +then + echo "Changes should take after reboot." +fi + diff --git a/source/scripts/argon-rpi-eeprom-config-psu.py b/source/scripts/argon-rpi-eeprom-config-psu.py new file mode 100644 index 0000000..d7c486f --- /dev/null +++ b/source/scripts/argon-rpi-eeprom-config-psu.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/source/scripts/argon-shutdown.sh b/source/scripts/argon-shutdown.sh new file mode 100644 index 0000000..fdcd539 --- /dev/null +++ b/source/scripts/argon-shutdown.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +pythonbin=/usr/bin/python3 +argononefanscript=/etc/argon/argononed.py +argoneonrtcscript=/etc/argon/argoneond.py +argonirconfigscript=/etc/argon/argonone-ir + +if [ ! -z "$1" ] +then + $pythonbin $argononefanscript FANOFF + if [ "$1" = "poweroff" ] || [ "$1" = "halt" ] + then + if [ -f $argonirconfigscript ] + then + if [ -f $argoneonrtcscript ] + then + $pythonbin $argoneonrtcscript SHUTDOWN + fi + $pythonbin $argononefanscript SHUTDOWN + fi + fi +fi diff --git a/source/scripts/argon-status.sh b/source/scripts/argon-status.sh new file mode 100644 index 0000000..7c13f44 --- /dev/null +++ b/source/scripts/argon-status.sh @@ -0,0 +1,106 @@ +#!/bin/bash + + +get_number () { + read curnumber + if [ -z "$curnumber" ] + then + echo "-2" + return + elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]] + then + if [ $curnumber -lt 0 ] + then + echo "-1" + return + elif [ $curnumber -gt 100 ] + then + echo "-1" + return + fi + echo $curnumber + return + fi + echo "-1" + return +} + +INSTALLATIONFOLDER=/etc/argon +pythonbin="sudo /usr/bin/python3" +argonstatusscript=$INSTALLATIONFOLDER/argonstatus.py +argondashboardscript=$INSTALLATIONFOLDER/argondashboard.py +argononefanscript=$INSTALLATIONFOLDER/argononed.py +argoneonrtcscript=$INSTALLATIONFOLDER/argoneond.py + + +echo "--------------------------" +echo " Argon System Information" +echo "--------------------------" + + +loopflag=1 +while [ $loopflag -eq 1 ] +do + echo + echo " 1. Temperatures" + echo " 2. CPU Utilization" + echo " 3. Storage Capacity" + echo " 4. RAM" + echo " 5. IP Address" + lastoption=5 + if [ -f $argononefanscript ] + then + echo " 6. Fan Speed" + lastoption=6 + fi + if [ -f "$argoneonrtcscript" ] + then + echo " 7. RTC Schedules" + echo " 8. RAID" + lastoption=8 + fi + lastoption=$((lastoption + 1)) + echo " ${lastoption}. Dashboard" + echo + echo " 0. Back" + echo -n "Enter Number (0-${lastoption}):" + + newmode=$( get_number ) + if [ $newmode -eq 0 ] + then + loopflag=0 + elif [ $newmode -gt 0 ] && [ $newmode -le $lastoption ] + then + echo "--------------------------" + if [ $newmode -eq $lastoption ] + then + $pythonbin $argondashboardscript + elif [ $newmode -eq 1 ] + then + $pythonbin $argonstatusscript "temperature" + elif [ $newmode -eq 2 ] + then + $pythonbin $argonstatusscript "cpu usage" + elif [ $newmode -eq 3 ] + then + $pythonbin $argonstatusscript "storage" + elif [ $newmode -eq 4 ] + then + $pythonbin $argonstatusscript "ram" + elif [ $newmode -eq 5 ] + then + $pythonbin $argonstatusscript "ip" + elif [ $newmode -eq 6 ] + then + $pythonbin $argonstatusscript "temperature" "fan configuration" "fan speed" + elif [ $newmode -eq 7 ] + then + $pythonbin $argoneonrtcscript GETSCHEDULELIST + elif [ $newmode -eq 8 ] + then + $pythonbin $argonstatusscript "raid" + fi + echo "--------------------------" + fi +done + diff --git a/source/scripts/argon-uninstall.sh b/source/scripts/argon-uninstall.sh new file mode 100644 index 0000000..88e9e99 --- /dev/null +++ b/source/scripts/argon-uninstall.sh @@ -0,0 +1,131 @@ +#!/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 + + +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 + +# 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 + +sudo rm /usr/bin/argon-config + +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 +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/source/scripts/argon-unitconfig.sh b/source/scripts/argon-unitconfig.sh new file mode 100644 index 0000000..636490e --- /dev/null +++ b/source/scripts/argon-unitconfig.sh @@ -0,0 +1,105 @@ +#!/bin/bash + +unitconfigfile=/etc/argonunits.conf + +get_number () { + read curnumber + if [ -z "$curnumber" ] + then + echo "-2" + return + elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]] + then + if [ $curnumber -lt 0 ] + then + echo "-1" + return + elif [ $curnumber -gt 100 ] + then + echo "-1" + return + fi + echo $curnumber + return + fi + echo "-1" + return +} + +saveconfig () { + echo "#" > $unitconfigfile + echo "# Argon Unit Configuration" >> $unitconfigfile + echo "#" >> $unitconfigfile + echo "temperature=$1" >> $unitconfigfile +} + +updateconfig=1 +unitloopflag=1 +while [ $unitloopflag -eq 1 ] +do + if [ $updateconfig -eq 1 ] + then + . $unitconfigfile + fi + + updateconfig=0 + if [ -z "$temperature" ] + then + temperature="C" + updateconfig=1 + fi + + # Write default values to config file, daemon already uses default so no need to restart service + if [ $updateconfig -eq 1 ] + then + saveconfig $temperature + updateconfig=0 + fi + + + echo "-----------------------------" + echo "Argon Display Units" + echo "-----------------------------" + echo "Choose from the list:" + echo " 1. Temperature: $temperature" + echo + echo " 0. Back" + echo -n "Enter Number (0-1):" + + newmode=$( get_number ) + if [ $newmode -eq 0 ] + then + unitloopflag=0 + elif [ $newmode -eq 1 ] + then + echo + echo "-----------------------------" + echo "Temperature Display" + echo "-----------------------------" + echo "Choose from the list:" + echo " 1. Celsius" + echo " 2. Fahrenheit" + echo + echo " 0. Cancel" + echo -n "Enter Number (0-2):" + + cmdmode=$( get_number ) + if [ $cmdmode -eq 1 ] + then + temperature="C" + updateconfig=1 + elif [ $cmdmode -eq 2 ] + then + temperature="F" + updateconfig=1 + fi + fi + + if [ $updateconfig -eq 1 ] + then + saveconfig $temperature + sudo systemctl restart argononed.service + fi +done + +echo diff --git a/source/scripts/argon-versioninfo.sh b/source/scripts/argon-versioninfo.sh new file mode 100644 index 0000000..e10889d --- /dev/null +++ b/source/scripts/argon-versioninfo.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +VERSIONINFO="2505003" + +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/source/scripts/argondashboard.py b/source/scripts/argondashboard.py new file mode 100644 index 0000000..d5c4032 --- /dev/null +++ b/source/scripts/argondashboard.py @@ -0,0 +1,371 @@ +#!/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 + +curses.wrapper(mainloop) diff --git a/source/scripts/argoneon-oledconfig.sh b/source/scripts/argoneon-oledconfig.sh new file mode 100644 index 0000000..f95e1e8 --- /dev/null +++ b/source/scripts/argoneon-oledconfig.sh @@ -0,0 +1,294 @@ +#!/bin/bash + +oledconfigfile=/etc/argoneonoled.conf + +get_number () { + read curnumber + if [ -z "$curnumber" ] + then + echo "-2" + return + elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]] + then + if [ $curnumber -lt 0 ] + then + echo "-1" + return + elif [ $curnumber -gt 100 ] + then + echo "-1" + return + fi + echo $curnumber + return + fi + echo "-1" + return +} + +get_pagename() { + if [ "$1" == "clock" ] + then + pagename="Current Date/Time" + elif [ "$1" == "cpu" ] + then + pagename="CPU Utilization" + elif [ "$1" == "storage" ] + then + pagename="Storage Utilization" + elif [ "$1" == "raid" ] + then + pagename="RAID Information" + elif [ "$1" == "ram" ] + then + pagename="Available RAM" + elif [ "$1" == "temp" ] + then + pagename="CPU/HDD Temperature" + elif [ "$1" == "ip" ] + then + pagename="IP Address" + else + pagename="Invalid" + fi +} + +configure_pagelist () { + pagemasterlist="clock cpu storage raid ram temp ip" + newscreenlist="$1" + pageloopflag=1 + while [ $pageloopflag -eq 1 ] + do + echo "--------------------------------" + echo " OLED Pages " + echo "--------------------------------" + i=1 + for curpage in $newscreenlist + do + get_pagename $curpage + echo " $i. Remove $pagename" + i=$((i+1)) + done + if [ $i -eq 1 ] + then + echo " No page configured" + fi + echo + echo " $i. Add Page" + echo + echo " 0. Done" + echo -n "Enter Number (0-$i):" + + cmdmode=$( get_number ) + if [ $cmdmode -eq 0 ] + then + pageloopflag=0 + elif [[ $cmdmode -eq $i ]] + then + + echo "--------------------------------" + echo " Choose Page to Add" + echo "--------------------------------" + echo + i=1 + for curpage in $pagemasterlist + do + get_pagename $curpage + echo " $i. $pagename" + i=$((i+1)) + done + + echo + echo " 0. Cancel" + echo -n "Enter Number (0-$i):" + pagenum=$( get_number ) + if [[ $pagenum -ge 1 && $pagenum -le $i ]] + then + i=1 + for curpage in $pagemasterlist + do + if [ $i -eq $pagenum ] + then + if [ "$newscreenlist" == "" ] + then + newscreenlist="$curpage" + else + newscreenlist="$newscreenlist $curpage" + fi + fi + i=$((i+1)) + done + fi + elif [[ $cmdmode -ge 1 && $cmdmode -lt $i ]] + then + tmpscreenlist="" + i=1 + for curpage in $newscreenlist + do + if [ ! $i -eq $cmdmode ] + then + tmpscreenlist="$tmpscreenlist $curpage" + fi + i=$((i+1)) + done + if [ "$tmpscreenlist" == "" ] + then + newscreenlist="$tmpscreenlist" + else + # Remove leading space + newscreenlist="${tmpscreenlist:1}" + fi + fi + done +} + +saveconfig () { + echo "#" > $oledconfigfile + echo "# Argon OLED Configuration" >> $oledconfigfile + echo "#" >> $oledconfigfile + echo "enabled=$1" >> $oledconfigfile + echo "switchduration=$2" >> $oledconfigfile + echo "screensaver=$3" >> $oledconfigfile + echo "screenlist=\"$4\"" >> $oledconfigfile +} + +updateconfig=1 +oledloopflag=1 +while [ $oledloopflag -eq 1 ] +do + if [ $updateconfig -eq 1 ] + then + . $oledconfigfile + fi + + updateconfig=0 + if [ -z "$enabled" ] + then + enabled="Y" + updateconfig=1 + fi + + if [ -z "$screenlist" ] + then + screenlist="clock ip" + updateconfig=1 + fi + + if [ -z "$screensaver" ] + then + screensaver=120 + updateconfig=1 + fi + + if [ -z "$switchduration" ] + then + switchduration=0 + updateconfig=1 + fi + + # Write default values to config file, daemon already uses default so no need to restart service + if [ $updateconfig -eq 1 ] + then + saveconfig $enabled $switchduration $screensaver "$screenlist" + updateconfig=0 + fi + + displaystring=": Manually" + if [ $switchduration -gt 1 ] + then + displaystring="Every $switchduration secs" + fi + + echo "-----------------------------" + echo "Argon OLED Configuration Tool" + echo "-----------------------------" + echo "Choose from the list:" + echo " 1. Switch Page $displaystring" + echo " 2. Configure Pages" + echo " 3. Turn OFF OLED Screen when unchanged after $screensaver secs" + echo " 4. Enable OLED Pages: $enabled" + echo + echo " 0. Back" + echo -n "Enter Number (0-3):" + + newmode=$( get_number ) + if [ $newmode -eq 0 ] + then + oledloopflag=0 + elif [ $newmode -eq 1 ] + then + echo + echo -n "Enter # of Seconds (10-60, Manual if 0):" + + cmdmode=$( get_number ) + if [ $cmdmode -eq 0 ] + then + switchduration=0 + updateconfig=1 + elif [[ $cmdmode -ge 10 && $cmdmode -le 60 ]] + then + updateconfig=1 + switchduration=$cmdmode + else + echo + echo "Invalid duration" + echo + fi + elif [ $newmode -eq 3 ] + then + echo + echo -n "Enter # of Seconds (60 or above, Manual if 0):" + + cmdmode=$( get_number ) + if [ $cmdmode -eq 0 ] + then + screensaver=0 + updateconfig=1 + elif [ $cmdmode -ge 60 ] + then + updateconfig=1 + screensaver=$cmdmode + else + echo + echo "Invalid duration" + echo + fi + elif [ $newmode -eq 2 ] + then + configure_pagelist "$screenlist" + if [ ! "$screenlist" == "$newscreenlist" ] + then + screenlist="$newscreenlist" + updateconfig=1 + fi + elif [ $newmode -eq 4 ] + then + echo + echo -n "Enable OLED Pages (Y/n)?:" + read -n 1 confirm + tmpenabled="$enabled" + if [[ "$confirm" == "n" || "$confirm" == "N" ]] + then + tmpenabled="N" + elif [[ "$confirm" == "y" || "$confirm" == "Y" ]] + then + tmpenabled="Y" + else + echo "Invalid response" + fi + if [ ! "$enabled" == "$tmpenabled" ] + then + enabled="$tmpenabled" + updateconfig=1 + fi + + fi + + if [ $updateconfig -eq 1 ] + then + saveconfig $enabled $switchduration $screensaver "$screenlist" + sudo systemctl restart argononed.service + fi +done + +echo diff --git a/source/scripts/argoneon-rtcconfig.sh b/source/scripts/argoneon-rtcconfig.sh new file mode 100644 index 0000000..e5199bd --- /dev/null +++ b/source/scripts/argoneon-rtcconfig.sh @@ -0,0 +1,421 @@ +#!/bin/bash + +if [ -z "$1" ] +then + rtcdaemonname=argoneond + rtcconfigfile=/etc/argoneonrtc.conf +else + rtcdaemonname=${1}d + rtcconfigfile=/etc/${1}.conf +fi + + +pythonbin=/usr/bin/python3 +argonrtcscript=/etc/argon/$rtcdaemonname.py + +CHECKPLATFORM="Others" +# Check if Raspbian +grep -q -F 'Raspbian' /etc/os-release &> /dev/null +if [ $? -eq 0 ] +then + CHECKPLATFORM="Raspbian" +else + # Ubuntu needs elevated access for SMBus + pythonbin="sudo /usr/bin/python3" +fi + + +get_number () { + read curnumber + if [ -z "$curnumber" ] + then + echo "-2" + return + elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]] + then + if [ $curnumber -lt 0 ] + then + echo "-1" + return + elif [ $curnumber -gt 100 ] + then + echo "-1" + return + fi + echo $curnumber + return + fi + echo "-1" + return +} + +configure_schedule () { + scheduleloopflag=1 + while [ $scheduleloopflag -eq 1 ] + do + echo "--------------------------------" + echo " Configure Schedule " + echo "--------------------------------" + echo " 1. Add Schedule" + echo " or" + echo " Remove Schedule" + $pythonbin $argonrtcscript GETSCHEDULELIST + echo + echo " 99. Main Menu" + echo " 0. Back" + #echo "NOTE: You can also edit $rtcconfigfile directly" + echo -n "Enter Number:" + + newmode=$( get_number ) + if [ $newmode -eq 0 ] + then + scheduleloopflag=0 + elif [ $newmode -eq 99 ] + then + scheduleloopflag=0 + rtcloopflag=2 + elif [ $newmode -eq 1 ] + then + configure_newschedule + elif [ $newmode -gt 1 ] + then + echo "CONFIRM SCHEDULE REMOVAL" + $pythonbin $argonrtcscript SHOWSCHEDULE $newmode + echo -n "Press Y to remove schedule #$newmode:" + read -n 1 confirm + if [ "$confirm" = "y" ] + then + confirm="Y" + fi + if [ "$confirm" = "Y" ] + then + $pythonbin $argonrtcscript REMOVESCHEDULE $newmode + sudo systemctl restart $rtcdaemonname.service + fi + echo "" + fi + done +} + +configure_newschedule () { + + cmdmode=1 + hour=8 + minute=0 + minuteprefix=":0" + dayidx=0 + repeat=1 + + subloopflag=1 + while [ $subloopflag -eq 1 ] + do + minuteprefix=":0" + if [ $minute -ge 10 ] + then + minuteprefix=":" + fi + + typestr="Shutdown" + if [ $cmdmode -eq 1 ] + then + typestr="Startup" + fi + + daystr="Daily" + if [ $dayidx -eq 1 ] + then + daystr="Mon" + elif [ $dayidx -eq 2 ] + then + daystr="Tue" + elif [ $dayidx -eq 3 ] + then + daystr="Wed" + elif [ $dayidx -eq 4 ] + then + daystr="Thu" + elif [ $dayidx -eq 5 ] + then + daystr="Fri" + elif [ $dayidx -eq 6 ] + then + daystr="Sat" + elif [ $dayidx -eq 7 ] + then + daystr="Sun" + fi + + repeatstr="Yes" + if [ $repeat -eq 0 ] + then + repeatstr="Once" + if [ $dayidx -eq 0 ] + then + daystr="Next Occurence" + fi + fi + + echo "--------------------------------" + echo " Configure Schedule" + echo "--------------------------------" + echo " 1. Type: $typestr" + echo " 2. Set Time: $hour$minuteprefix$minute" + echo " 3. Repeating: $repeatstr" + echo " 4. Day: $daystr" + echo + echo " 5. Add Schedule" + echo + echo " 0. Cancel" + echo -n "Enter Number (0-5):" + + setmode=$( get_number ) + if [ $setmode -eq 0 ] + then + subloopflag=0 + elif [ $setmode -eq 1 ] + then + echo "--------------------------------" + echo " Schedule Type " + echo "--------------------------------" + echo " 1. Startup" + echo " 2. Shutdown" + echo + echo -n "Enter Number (1-2):" + + tmpval=$( get_number ) + if [ $tmpval -eq 1 ] + then + cmdmode=1 + elif [ $tmpval -eq 2 ] + then + cmdmode=0 + else + echo "Invalid Option" + fi + elif [ $setmode -eq 2 ] + then + echo -n "Enter Hour (0-23):" + tmphour=$( get_number ) + echo -n "Enter Minute (0-59):" + tmpminute=$( get_number ) + if [[ $tmpminute -ge 0 && $tmpminute -le 59 && $tmphour -ge 0 && $tmphour -le 23 ]] + then + minute=$tmpminute + hour=$tmphour + else + echo "Invalid value(s)" + fi + elif [ $setmode -eq 3 ] + then + echo -n "Repeat schedule (Y/n)?:" + read -n 1 confirm + if [ "$confirm" = "y" ] + then + repeat=1 + else + repeat=0 + fi + elif [ $setmode -eq 4 ] + then + echo "Select Day of the Week:" + echo " 0. Daily" + echo " 1. Monday" + echo " 2. Tuesday" + echo " 3. Wednesday" + echo " 4. Thursday" + echo " 5. Friday" + echo " 6. Saturday" + echo " 7. Sunday" + + echo -n "Enter Number (0-7):" + tmpval=$( get_number ) + if [[ $tmpval -ge 0 && $tmpval -le 7 ]] + then + dayidx=$tmpval + else + echo "Invalid Option" + fi + elif [ $setmode -eq 5 ] + then + if [ $dayidx -eq 0 ] + then + cronweekday="*" + elif [ $dayidx -eq 7 ] + then + cronweekday="7" + else + cronweekday=$dayidx + fi + cmdcode="off" + if [ $cmdmode -eq 1 ] + then + cmdcode="on" + fi + + echo "$minute $hour * * $cronweekday $cmdcode" >> $rtcconfigfile + sudo systemctl restart $rtcdaemonname.service + subloopflag=0 + fi + done +} + +configure_newcron () { + subloopflag=1 + while [ $subloopflag -eq 1 ] + do + echo "--------------------------------" + echo " Schedule Type " + echo "--------------------------------" + echo " 1. Startup" + echo " 2. Shutdown" + echo + echo " 0. Cancel" + echo -n "Enter Number (0-2):" + + cmdmode=$( get_number ) + if [ $cmdmode -eq 0 ] + then + subloopflag=0 + elif [[ $cmdmode -ge 1 && $cmdmode -le 2 ]] + then + cmdcode="on" + echo "--------------------------------" + if [ $cmdmode -eq 1 ] + then + echo " Schedule Startup" + else + echo " Schedule Shutdown" + cmdcode="off" + fi + echo "--------------------------------" + echo "Select Schedule:" + echo " 1. Hourly" + echo " 2. Daily" + echo " 3. Weekly" + echo " 4. Monthly" + echo + echo " 0. Back" + echo -n "Enter Number (0-4):" + + newmode=$( get_number ) + if [[ $newmode -ge 1 && $newmode -le 4 ]] + then + echo "" + if [ $cmdmode -eq 1 ] + then + echo "New Startup Schedule" + else + echo "New Shutdown Schedule" + fi + + if [ $newmode -eq 1 ] + then + echo -n "Enter Minute (0-59):" + minute=$( get_number ) + if [[ $minute -ge 0 && $minute -le 59 ]] + then + echo "$minute * * * * $cmdcode" >> $rtcconfigfile + sudo systemctl restart $rtcdaemonname.service + subloopflag=0 + else + echo "Invalid value" + fi + elif [ $newmode -eq 2 ] + then + echo -n "Enter Hour (0-23):" + hour=$( get_number ) + echo -n "Enter Minute (0-59):" + minute=$( get_number ) + if [[ $minute -ge 0 && $minute -le 59 && $hour -ge 0 && $hour -le 23 ]] + then + echo "$minute $hour * * * $cmdcode" >> $rtcconfigfile + sudo systemctl restart $rtcdaemonname.service + subloopflag=0 + else + echo "Invalid value(s)" + fi + elif [ $newmode -eq 3 ] + then + echo "Select Day of the Week:" + echo " 0. Sunday" + echo " 1. Monday" + echo " 2. Tuesday" + echo " 3. Wednesday" + echo " 4. Thursday" + echo " 5. Friday" + echo " 6. Saturday" + + echo -n "Enter Number (0-6):" + weekday=$( get_number ) + echo -n "Enter Hour (0-23):" + hour=$( get_number ) + echo -n "Enter Minute (0-59):" + minute=$( get_number ) + + if [[ $minute -ge 0 && $minute -le 59 && $hour -ge 0 && $hour -le 23 && $weekday -ge 0 && $weekday -le 6 ]] + then + echo "$minute $hour * * $weekday $cmdcode" >> $rtcconfigfile + sudo systemctl restart $rtcdaemonname.service + subloopflag=0 + else + echo "Invalid value(s)" + fi + elif [ $newmode -eq 4 ] + then + echo -n "Enter Date (1-31):" + monthday=$( get_number ) + if [[ $monthday -ge 29 ]] + then + echo "WARNING: This schedule will not trigger for certain months" + fi + echo -n "Enter Hour (0-23):" + hour=$( get_number ) + echo -n "Enter Minute (0-59):" + minute=$( get_number ) + + if [[ $minute -ge 0 && $minute -le 59 && $hour -ge 0 && $hour -le 23 && $monthday -ge 1 && $monthday -le 31 ]] + then + echo "$minute $hour $monthday * * $cmdcode" >> $rtcconfigfile + sudo systemctl restart $rtcdaemonname.service + subloopflag=0 + else + echo "Invalid value(s)" + fi + fi + fi + fi + done +} + +rtcloopflag=1 +while [ $rtcloopflag -eq 1 ] +do + echo "----------------------------" + echo "Argon RTC Configuration Tool" + echo "----------------------------" + $pythonbin $argonrtcscript GETRTCTIME + echo "Choose from the list:" + echo " 1. Update RTC Time" + echo " 2. Configure Startup/Shutdown Schedules" + echo + echo " 0. Exit" + echo -n "Enter Number (0-2):" + + newmode=$( get_number ) + if [ $newmode -eq 0 ] + then + rtcloopflag=0 + elif [[ $newmode -ge 1 && $newmode -le 2 ]] + then + if [ $newmode -eq 1 ] + then + echo "Matching RTC Time to System Time..." + $pythonbin $argonrtcscript UPDATERTCTIME + elif [ $newmode -eq 2 ] + then + configure_schedule + fi + fi +done + +echo diff --git a/source/scripts/argoneond.py b/source/scripts/argoneond.py new file mode 100644 index 0000000..c00612b --- /dev/null +++ b/source/scripts/argoneond.py @@ -0,0 +1,487 @@ +#!/usr/bin/python3 + + +import sys +import datetime +import math + +import os +import time + +sys.path.append("/etc/argon/") +from argonregister import argonregister_initializebusobj +import argonrtc + + +# Initialize I2C Bus +bus = argonregister_initializebusobj() + +ADDR_RTC=0x51 + +################# +# Common/Helpers +################# + +RTC_CONFIGFILE = "/etc/argoneonrtc.conf" + +RTC_ALARM_BIT = 0x8 +RTC_TIMER_BIT = 0x4 + +# PCF8563 number system Binary Coded Decimal (BCD) + +# BCD to Decimal +def numBCDtoDEC(val): + return (val & 0xf)+(((val >> 4) & 0xf)*10) + +# Decimal to BCD +def numDECtoBCD(val): + return (math.floor(val/10)<<4) + (val % 10) + +# Check if Event Bit is raised +def hasRTCEventFlag(flagbit): + if bus is None: + return False + bus.write_byte(ADDR_RTC,1) + out = bus.read_byte_data(ADDR_RTC, 1) + return (out & flagbit) != 0 + +# Clear Event Bit if raised +def clearRTCEventFlag(flagbit): + if bus is None: + return False + + out = bus.read_byte_data(ADDR_RTC, 1) + if (out & flagbit) != 0: + # Unset only if fired + bus.write_byte_data(ADDR_RTC, 1, out&(0xff-flagbit)) + return True + return False + +# Enable Event Flag +def setRTCEventFlag(flagbit, enabled): + if bus is None: + return + + # 0x10 = TI_TP flag, 0 by default + ti_tp_flag = 0x10 + # flagbit=0x4 for timer flag, 0x1 for enable timer flag + # flagbit=0x8 for alarm flag, 0x2 for enable alarm flag + enableflagbit = flagbit>>2 + disableflagbit = 0 + if enabled == False: + disableflagbit = enableflagbit + enableflagbit = 0 + + out = bus.read_byte_data(ADDR_RTC, 1) + bus.write_byte_data(ADDR_RTC, 1, (out&(0xff-flagbit-disableflagbit - ti_tp_flag))|enableflagbit) + + +######### +# Describe Methods +######### + +# Describe Timer Setting +def describeTimer(showsetting): + if bus is None: + return "Error" + + out = bus.read_byte_data(ADDR_RTC, 14) + tmp = out & 3 + if tmp == 3: + outstr = " Minute(s)" + elif tmp == 2: + outstr = " Second(s)" + elif tmp == 1: + outstr = "/64th Second" + elif tmp == 0: + outstr = "/4096th Second" + + if (out & 0x80) != 0: + out = bus.read_byte_data(ADDR_RTC, 15) + return "Every "+(numBCDtoDEC(out)+1)+outstr + elif showsetting == True: + return "Disabled (Interval every 1"+outstr+")" + # Setting might matter to save resources + return "None" + + +# Describe Alarm Setting +def describeAlarm(): + if bus is None: + return "Error" + + minute = -1 + hour = -1 + caldate = -1 + weekday = -1 + + out = bus.read_byte_data(ADDR_RTC, 9) + if (out & 0x80) == 0: + minute = numBCDtoDEC(out & 0x7f) + + out = bus.read_byte_data(ADDR_RTC, 10) + if (out & 0x80) == 0: + hour = numBCDtoDEC(out & 0x3f) + + out = bus.read_byte_data(ADDR_RTC, 11) + if (out & 0x80) == 0: + caldate = numBCDtoDEC(out & 0x3f) + + out = bus.read_byte_data(ADDR_RTC, 12) + if (out & 0x80) == 0: + weekday = numBCDtoDEC(out & 0x7) + + if weekday < 0 and caldate < 0 and hour < 0 and minute < 0: + return "None" + + # Convert from UTC + utcschedule = argonrtc.describeSchedule([-1], [weekday], [caldate], [hour], [minute]) + weekday, caldate, hour, minute = argonrtc.convertAlarmTimezone(weekday, caldate, hour, minute, False) + + return argonrtc.describeSchedule([-1], [weekday], [caldate], [hour], [minute]) + " Local (RTC Schedule: "+utcschedule+" UTC)" + + +# Describe Control Flags +def describeControlRegisters(): + if bus is None: + print("Error") + return + + out = bus.read_byte_data(ADDR_RTC, 1) + + print("\n***************") + print("Control Status 2") + print("\tTI_TP Flag:", ((out & 0x10) != 0)) + print("\tAlarm Flag:", ((out & RTC_ALARM_BIT) != 0),"( Enabled =", (out & (RTC_ALARM_BIT>>2)) != 0, ")") + print("\tTimer Flag:", ((out & RTC_TIMER_BIT) != 0),"( Enabled =", (out & (RTC_TIMER_BIT>>2)) != 0, ")") + + print("Alarm Setting:") + print("\t"+describeAlarm()) + + print("Timer Setting:") + print("\t"+describeTimer(True)) + + print("***************\n") + + +######### +# Alarm +######### + +# Check if RTC Alarm Flag is ON +def hasRTCAlarmFlag(): + return hasRTCEventFlag(RTC_ALARM_BIT) + +# Clear RTC Alarm Flag +def clearRTCAlarmFlag(): + return clearRTCEventFlag(RTC_ALARM_BIT) + +# Enables RTC Alarm Register +def enableAlarm(registeraddr, value, mask): + if bus is None: + return + + # 0x00 is Enabled + bus.write_byte_data(ADDR_RTC, registeraddr, (numDECtoBCD(value)&mask)) + +# Disables RTC Alarm Register +def disableAlarm(registeraddr): + if bus is None: + return + + # 0x80 is disabled + bus.write_byte_data(ADDR_RTC, registeraddr, 0x80) + +# Removes all alarm settings +def removeRTCAlarm(): + setRTCEventFlag(RTC_ALARM_BIT, False) + + disableAlarm(9) + disableAlarm(10) + disableAlarm(11) + disableAlarm(12) + +# Set RTC Alarm (Negative values ignored) +def setRTCAlarm(enableflag, weekday, caldate, hour, minute): + + weekday, caldate, hour, minute = argonrtc.getRTCAlarm(weekday, caldate, hour, minute) + if caldate < 1 and weekday < 0 and hour < 0 and minute < 0: + return -1 + + clearRTCAlarmFlag() + setRTCEventFlag(RTC_ALARM_BIT, enableflag) + + if minute >= 0: + enableAlarm(9, minute, 0x7f) + else: + disableAlarm(9) + + if hour >= 0: + enableAlarm(10, hour, 0x7f) + else: + disableAlarm(10) + + if caldate >= 0: + enableAlarm(11, caldate, 0x7f) + else: + disableAlarm(11) + + if weekday >= 0: + # 0 - Sun (datetime 0 - Mon) + if weekday > 5: + weekday = 0 + else: + weekday = weekday + 1 + enableAlarm(12, weekday, 0x7f) + else: + disableAlarm(12) + + return 0 + +# Set RTC Hourly Alarm +def setRTCAlarmHourly(enableflag, minute): + return setRTCAlarm(enableflag, -1, -1, -1, minute) + +# Set RTC Daily Alarm +def setRTCAlarmDaily(enableflag, hour, minute): + return setRTCAlarm(enableflag, -1, -1, hour, minute) + +# Set RTC Weekly Alarm +def setRTCAlarmWeekly(enableflag, dayofweek, hour, minute): + return setRTCAlarm(enableflag, dayofweek, -1, hour, minute) + +# Set RTC Monthly Alarm +def setRTCAlarmMonthly(enableflag, caldate, hour, minute): + return setRTCAlarm(enableflag, -1, caldate, hour, minute) + +######### +# Timer +######### + +# Check if RTC Timer Flag is ON +def hasRTCTimerFlag(): + return hasRTCEventFlag(RTC_TIMER_BIT) + +# Clear RTC Timer Flag +def clearRTCTimerFlag(): + return clearRTCEventFlag(RTC_TIMER_BIT) + +# Remove RTC Timer Setting +def removeRTCTimer(): + if bus is None: + return + + setRTCEventFlag(RTC_TIMER_BIT, False) + + # Timer disable and Set Timer frequency to lowest (0x3=1 per minute) + bus.write_byte_data(ADDR_RTC, 14, 3) + bus.write_byte_data(ADDR_RTC, 15, 0) + +# Set RTC Timer Interval +def setRTCTimerInterval(enableflag, value, inSeconds = False): + if bus is None: + return -1 + + if value > 255 or value < 1: + return -1 + clearRTCTimerFlag() + setRTCEventFlag(RTC_TIMER_BIT, enableflag) + + # 0x80 Timer Enabled, mode: 0x3=1/Min, 0x2=1/Sec, 0x1=Per 64th Sec, 0=Per 4096th Sec + timerconfigFlag = 0x83 + if inSeconds == True: + timerconfigFlag = 0x82 + + bus.write_byte_data(ADDR_RTC, 14, timerconfigFlag) + bus.write_byte_data(ADDR_RTC, 15, numDECtoBCD(value&0xff)) + return 0 + +############# +# Date/Time +############# + +# Returns RTC timestamp as datetime object +def getRTCdatetime(): + if bus is None: + return datetime.datetime(2000, 1, 1, 0, 0, 0) + + # Data Sheet Recommends to read this manner (instead of from registers) + bus.write_byte(ADDR_RTC,2) + + out = bus.read_byte(ADDR_RTC) + out = numBCDtoDEC(out & 0x7f) + second = out + #warningflag = (out & 0x80)>>7 + + out = bus.read_byte(ADDR_RTC) + minute = numBCDtoDEC(out & 0x7f) + + out = bus.read_byte(ADDR_RTC) + hour = numBCDtoDEC(out & 0x3f) + + out = bus.read_byte(ADDR_RTC) + caldate = numBCDtoDEC(out & 0x3f) + + out = bus.read_byte(ADDR_RTC) + #weekDay = numBCDtoDEC(out & 7) + + out = bus.read_byte(ADDR_RTC) + month = numBCDtoDEC(out & 0x1f) + + out = bus.read_byte(ADDR_RTC) + year = numBCDtoDEC(out) + + #print({"year":year, "month": month, "date": caldate, "hour": hour, "minute": minute, "second": second}) + + if month == 0: + # Reset, uninitialized RTC + month = 1 + + # Timezone is GMT/UTC +0 + # Year is from 2000 + try: + return datetime.datetime(year+2000, month, caldate, hour, minute, second)+argonrtc.getLocaltimeOffset() + except: + return datetime.datetime(2000, 1, 1, 0, 0, 0) + +# set RTC time using datetime object (Local time) +def setRTCdatetime(localdatetime): + if bus is None: + return + # Set local time to UTC + localdatetime = localdatetime - argonrtc.getLocaltimeOffset() + + # python Sunday = 6, RTC Sunday = 0 + weekDay = localdatetime.weekday() + if weekDay == 6: + weekDay = 0 + else: + weekDay = weekDay + 1 + + # Write to respective registers + bus.write_byte_data(ADDR_RTC,2,numDECtoBCD(localdatetime.second)) + bus.write_byte_data(ADDR_RTC,3,numDECtoBCD(localdatetime.minute)) + bus.write_byte_data(ADDR_RTC,4,numDECtoBCD(localdatetime.hour)) + bus.write_byte_data(ADDR_RTC,5,numDECtoBCD(localdatetime.day)) + bus.write_byte_data(ADDR_RTC,6,numDECtoBCD(weekDay)) + bus.write_byte_data(ADDR_RTC,7,numDECtoBCD(localdatetime.month)) + + # Year is from 2000 + bus.write_byte_data(ADDR_RTC,8,numDECtoBCD(localdatetime.year-2000)) + + +######### +# Config +######### + +# Set Next Alarm on RTC +def setNextAlarm(commandschedulelist, prevdatetime): + nextcommandtime, weekday, caldate, hour, minute = argonrtc.getNextAlarm(commandschedulelist, prevdatetime) + if prevdatetime >= nextcommandtime: + return prevdatetime + if weekday < 0 and caldate < 0 and hour < 0 and minute < 0: + # No schedule + # nextcommandtime is current time, which will be replaced/checked next iteration + removeRTCAlarm() + return nextcommandtime + + setRTCAlarm(True, nextcommandtime.weekday(), nextcommandtime.day, nextcommandtime.hour, nextcommandtime.minute) + return nextcommandtime + + +def allowshutdown(): + uptime = 0.0 + errorflag = False + try: + cpuctr = 0 + tempfp = open("/proc/uptime", "r") + alllines = tempfp.readlines() + for temp in alllines: + infolist = temp.split(" ") + if len(infolist) > 1: + uptime = float(infolist[0]) + break + tempfp.close() + except IOError: + errorflag = True + # 120=2mins minimum up time + return uptime > 120 + +###### +if len(sys.argv) > 1: + cmd = sys.argv[1].upper() + + # Enable Alarm/Timer Flags + enableflag = True + + if cmd == "CLEAN": + removeRTCAlarm() + removeRTCTimer() + elif cmd == "SHUTDOWN": + clearRTCAlarmFlag() + clearRTCTimerFlag() + + elif cmd == "GETRTCSCHEDULE": + print("Alarm Setting:") + print("\t"+describeAlarm()) + #print("Timer Setting:") + #print("\t"+describeTimer(True)) + + elif cmd == "GETRTCTIME": + print("RTC Time:", getRTCdatetime()) + + elif cmd == "UPDATERTCTIME": + setRTCdatetime(datetime.datetime.now()) + print("RTC Time:", getRTCdatetime()) + + elif cmd == "GETSCHEDULELIST": + argonrtc.describeConfigList(RTC_CONFIGFILE) + + elif cmd == "SHOWSCHEDULE": + if len(sys.argv) > 2: + if sys.argv[2].isdigit(): + # Display starts at 2, maps to 0-based index + configidx = int(sys.argv[2])-2 + configlist = argonrtc.loadConfigList(RTC_CONFIGFILE) + if len(configlist) > configidx: + print (" ",argonrtc.describeConfigListEntry(configlist[configidx])) + else: + print(" Invalid Schedule") + + elif cmd == "REMOVESCHEDULE": + if len(sys.argv) > 2: + if sys.argv[2].isdigit(): + # Display starts at 2, maps to 0-based index + configidx = int(sys.argv[2])-2 + argonrtc.removeConfigEntry(RTC_CONFIGFILE, configidx) + + elif cmd == "SERVICE": + argonrtc.updateSystemTime(getRTCdatetime()) + + commandschedulelist = argonrtc.formCommandScheduleList(argonrtc.loadConfigList(RTC_CONFIGFILE)) + nextrtcalarmtime = setNextAlarm(commandschedulelist, datetime.datetime.now()) + serviceloop = True + while serviceloop==True: + clearRTCAlarmFlag() + clearRTCTimerFlag() + + tmpcurrenttime = datetime.datetime.now() + if nextrtcalarmtime <= tmpcurrenttime: + # Update RTC Alarm to next iteration + nextrtcalarmtime = setNextAlarm(commandschedulelist, nextrtcalarmtime) + if len(argonrtc.getCommandForTime(commandschedulelist, tmpcurrenttime, "off")) > 0: + # Shutdown detected, issue command then end service loop + if allowshutdown(): + os.system("shutdown now -h") + serviceloop = False + # Don't break to sleep while command executes (prevents service to restart) + + + time.sleep(60) + + +elif False: + print("System Time: ", datetime.datetime.now()) + print("RTC Time: ", getRTCdatetime()) + + describeControlRegisters() diff --git a/source/scripts/argoneond.service b/source/scripts/argoneond.service new file mode 100644 index 0000000..3330da2 --- /dev/null +++ b/source/scripts/argoneond.service @@ -0,0 +1,10 @@ +[Unit] +Description=Argon EON RTC Service +After=multi-user.target +[Service] +Type=simple +Restart=always +RemainAfterExit=true +ExecStart=/usr/bin/python3 /etc/argon/argoneond.py SERVICE +[Install] +WantedBy=multi-user.target diff --git a/source/scripts/argoneonoled.py b/source/scripts/argoneonoled.py new file mode 100644 index 0000000..bc344a6 --- /dev/null +++ b/source/scripts/argoneonoled.py @@ -0,0 +1,345 @@ +#!/usr/bin/python3 + + +import sys +import datetime +import math + +import os +import time + +# Initialize I2C Bus +import smbus + +try: + bus=smbus.SMBus(1) +except Exception: + try: + # Older version + bus=smbus.SMBus(0) + except Exception: + print("Unable to detect i2c") + bus=None + + +OLED_WD=128 +OLED_HT=64 +OLED_SLAVEADDRESS=0x6a +ADDR_OLED=0x3c + +OLED_NUMFONTCHAR=256 + +OLED_BUFFERIZE = ((OLED_WD*OLED_HT)>>3) +oled_imagebuffer = [0] * OLED_BUFFERIZE + + +def oled_getmaxY(): + return OLED_HT + +def oled_getmaxX(): + return OLED_WD + +def oled_loadbg(bgname): + if bgname == "bgblack": + oled_clearbuffer() + return + elif bgname == "bgwhite": + oled_clearbuffer(1) + return + try: + file = open("/etc/argon/oled/"+bgname+".bin", "rb") + bgbytes = list(file.read()) + file.close() + ctr = len(bgbytes) + if ctr == OLED_BUFFERIZE: + oled_imagebuffer[:] = bgbytes + elif ctr > OLED_BUFFERIZE: + oled_imagebuffer[:] = bgbytes[0:OLED_BUFFERIZE] + else: + oled_imagebuffer[0:ctr] = bgbytes + # Clear the rest of the buffer + while ctr < OLED_BUFFERIZE: + oled_imagebuffer[ctr] = 0 + ctr=ctr+1 + except FileNotFoundError: + oled_clearbuffer() + + +def oled_clearbuffer(value = 0): + if value != 0: + value = 0xff + ctr = 0 + while ctr < OLED_BUFFERIZE: + oled_imagebuffer[ctr] = value + ctr=ctr+1 + +def oled_writebyterow(x,y,bytevalue, mode = 0): + bufferoffset = OLED_WD*(y>>3) + x + if mode == 0: + oled_imagebuffer[bufferoffset] = bytevalue + elif mode == 1: + oled_imagebuffer[bufferoffset] = bytevalue^oled_imagebuffer[bufferoffset] + else: + oled_imagebuffer[bufferoffset] = bytevalue|oled_imagebuffer[bufferoffset] + + +def oled_writebuffer(x,y,value, mode = 0): + + yoffset = y>>3 + yshift = y&0x7 + ybit = (1<>3 + blocksize = 32 + if bus is None: + return + try: + # Set COM-H Addressing + bus.write_byte_data(ADDR_OLED, 0, 0x20) + bus.write_byte_data(ADDR_OLED, 0, 0x1) + + # Set Column range + bus.write_byte_data(ADDR_OLED, 0, 0x21) + bus.write_byte_data(ADDR_OLED, 0, xoffset) + bus.write_byte_data(ADDR_OLED, 0, xoffset+blocksize-1) + + # Set Row Range + bus.write_byte_data(ADDR_OLED, 0, 0x22) + bus.write_byte_data(ADDR_OLED, 0, yoffset) + bus.write_byte_data(ADDR_OLED, 0, yoffset) + + # Set Display Start Line + bus.write_byte_data(ADDR_OLED, 0, 0x40) + + bufferoffset = OLED_WD*yoffset + xoffset + # Write Out Buffer + bus.write_i2c_block_data(ADDR_OLED, OLED_SLAVEADDRESS, oled_imagebuffer[bufferoffset:(bufferoffset+blocksize)]) + except: + return + +def oled_drawfilledrectangle(x, y, wd, ht, mode = 0): + ymax = y + ht + cury = y&0xF8 + + xmax = x + wd + curx = x + if ((y & 0x7)) != 0: + yshift = y&0x7 + bytevalue = (0xFF<>yshift) + + while curx < xmax: + oled_writebyterow(curx,cury,bytevalue, mode) + curx = curx + 1 + cury = cury + 8 + # Draw 8 rows at a time when possible + while cury + 8 < ymax: + curx = x + while curx < xmax: + oled_writebyterow(curx,cury,0xFF, mode) + curx = curx + 1 + cury = cury + 8 + + if cury < ymax: + yshift = 8-((ymax-cury)&0x7) + bytevalue = (0xFF>>yshift) + + curx = x + while curx < xmax: + oled_writebyterow(curx,cury,bytevalue, mode) + curx = curx + 1 + + +def oled_writetextaligned(textdata, x, y, boxwidth, alignmode, charwd = 6, mode = 0): + leftoffset = 0 + if alignmode == 1: + # Centered + leftoffset = (boxwidth-len(textdata)*charwd)>>1 + elif alignmode == 2: + # Right aligned + leftoffset = (boxwidth-len(textdata)*charwd) + + oled_writetext(textdata, x+leftoffset, y, charwd, mode) + + +def oled_writetext(textdata, x, y, charwd = 6, mode = 0): + if charwd < 6: + charwd = 6 + + charht = int((charwd<<3)/6) + if charht & 0x7: + charht = (charht&0xF8) + 8 + + try: + file = open("/etc/argon/oled/font"+str(charht)+"x"+str(charwd)+".bin", "rb") + fontbytes = list(file.read()) + file.close() + except FileNotFoundError: + try: + # Default to smallest + file = open("/etc/argon/oled/font8x6.bin", "rb") + fontbytes = list(file.read()) + file.close() + except FileNotFoundError: + return + + if ((y & 0x7)) == 0: + # Use optimized loading + oled_fastwritetext(textdata, x, y, charht, charwd, fontbytes, mode) + return + + numfontrow = charht>>3 + ctr = 0 + while ctr < len(textdata): + fontoffset = ord(textdata[ctr])*charwd + fontcol = 0 + while fontcol < charwd and x < OLED_WD: + fontrow = 0 + row = y + while fontrow < numfontrow and row < OLED_HT and x >= 0: + curbit = 0x80 + curbyte = (fontbytes[fontoffset + fontcol + (OLED_NUMFONTCHAR*charwd*fontrow)]) + subrow = 0 + while subrow < 8 and row < OLED_HT: + value = 0 + if (curbyte&curbit) != 0: + value = 1 + oled_writebuffer(x,row,value, mode) + curbit = curbit >> 1 + row = row + 1 + subrow = subrow + 1 + fontrow = fontrow + 1 + fontcol = fontcol + 1 + x = x + 1 + ctr = ctr + 1 + +def oled_fastwritetext(textdata, x, y, charht, charwd, fontbytes, mode = 0): + + numfontrow = charht>>3 + ctr = 0 + while ctr < len(textdata): + fontoffset = ord(textdata[ctr])*charwd + fontcol = 0 + while fontcol < charwd and x < OLED_WD: + fontrow = 0 + row = y&0xF8 + while fontrow < numfontrow and row < OLED_HT and x >= 0: + curbyte = (fontbytes[fontoffset + fontcol + (OLED_NUMFONTCHAR*charwd*fontrow)]) + oled_writebyterow(x,row,curbyte, mode) + fontrow = fontrow + 1 + row = row + 8 + fontcol = fontcol + 1 + x = x + 1 + ctr = ctr + 1 + return + + +def oled_power(turnon = True): + cmd = 0xAE + if turnon == True: + cmd = cmd|1 + if bus is None: + return + try: + bus.write_byte_data(ADDR_OLED, 0, cmd) + except: + return + + +def oled_inverse(enable = True): + cmd = 0xA6 + if enable == True: + cmd = cmd|1 + if bus is None: + return + try: + bus.write_byte_data(ADDR_OLED, 0, cmd) + except: + return + + +def oled_fullwhite(enable = True): + cmd = 0xA4 + if enable == True: + cmd = cmd|1 + if bus is None: + return + + try: + bus.write_byte_data(ADDR_OLED, 0, cmd) + except: + return + + + +def oled_reset(): + if bus is None: + return + try: + # Set COM-H Addressing + bus.write_byte_data(ADDR_OLED, 0, 0x20) + bus.write_byte_data(ADDR_OLED, 0, 0x1) + + # Set Column range + bus.write_byte_data(ADDR_OLED, 0, 0x21) + bus.write_byte_data(ADDR_OLED, 0, 0) + bus.write_byte_data(ADDR_OLED, 0, OLED_WD-1) + + # Set Row Range + bus.write_byte_data(ADDR_OLED, 0, 0x22) + bus.write_byte_data(ADDR_OLED, 0, 0) + bus.write_byte_data(ADDR_OLED, 0, (OLED_HT>>3)-1) + + # Set Page Addressing + bus.write_byte_data(ADDR_OLED, 0, 0x20) + bus.write_byte_data(ADDR_OLED, 0, 0x2) + # Set GDDRAM Address + bus.write_byte_data(ADDR_OLED, 0, 0xB0) + + # Set Display Start Line + bus.write_byte_data(ADDR_OLED, 0, 0x40) + except: + return + + diff --git a/source/scripts/argonone-fanconfig.sh b/source/scripts/argonone-fanconfig.sh new file mode 100644 index 0000000..6e1b585 --- /dev/null +++ b/source/scripts/argonone-fanconfig.sh @@ -0,0 +1,254 @@ +#!/bin/bash + +daemonconfigfile=/etc/argononed.conf +unitconfigfile=/etc/argonunits.conf +fanmode="CPU" + +if [ "$1" == "hdd" ] +then + daemonconfigfile=/etc/argononed-hdd.conf + fanmode="HDD" +fi + +if [ -f "$unitconfigfile" ] +then + . $unitconfigfile +fi + +if [ -z "$temperature" ] +then + temperature="C" +fi + +echo "------------------------------------------" +echo " Argon Fan Speed Configuration Tool ($fanmode)" +echo "------------------------------------------" +echo "WARNING: This will remove existing configuration." +echo -n "Press Y to continue:" +read -n 1 confirm +echo + + +fanloopflag=1 +newmode=0 +if [ "$confirm" = "y" ] +then + confirm="Y" +fi + +if [ "$confirm" != "Y" ] +then + fanloopflag=0 + echo "Cancelled." +else + echo "Thank you." +fi + +get_number () { + read curnumber + if [ -z "$curnumber" ] + then + echo "-2" + return + elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]] + then + if [ $curnumber -lt 0 ] + then + echo "-1" + return + elif [ $curnumber -gt 212 ] + then + # 212F = 100C + echo "-1" + return + fi + echo $curnumber + return + fi + echo "-1" + return +} + +while [ $fanloopflag -eq 1 ] +do + echo + echo "Select fan mode:" + echo " 1. Always on" + if [ "$fanmode" == "HDD" ] + then + if [ "$temperature" == "C" ] + then + echo " 2. Adjust to temperatures (35C, 40C, and 45C)" + else + echo " 2. Adjust to temperatures (95F, 104F, and 113F)" + fi + else + if [ "$temperature" == "C" ] + then + echo " 2. Adjust to temperatures (55C, 60C, and 65C)" + else + echo " 2. Adjust to temperatures (130F, 140F, and 150F)" + fi + fi + echo " 3. Customize temperature cut-offs" + echo + echo " 0. Exit" + echo "NOTE: You can also edit $daemonconfigfile directly" + echo -n "Enter Number (0-3):" + newmode=$( get_number ) + + if [[ $newmode -eq 0 ]] + then + fanloopflag=0 + elif [ $newmode -eq 1 ] + then + echo "#" > $daemonconfigfile + echo "# Argon Fan Speed Configuration $fanmode" >> $daemonconfigfile + echo "#" >> $daemonconfigfile + echo "# Min Temp=Fan Speed" >> $daemonconfigfile + + errorfanflag=1 + while [ $errorfanflag -eq 1 ] + do + echo -n "Please provide fan speed (30-100 only):" + + curfan=$( get_number ) + if [ $curfan -ge 30 ] + then + errorfanflag=0 + elif [ $curfan -gt 100 ] + then + errorfanflag=0 + fi + done + + echo "1="$curfan >> $daemonconfigfile + sudo systemctl restart argononed.service + echo "Fan always on." + elif [ $newmode -eq 2 ] + then + echo "#" > $daemonconfigfile + echo "# Argon Fan Speed Configuration $fanmode" >> $daemonconfigfile + echo "#" >> $daemonconfigfile + echo "# Min Temp=Fan Speed" >> $daemonconfigfile + + echo "Please provide fan speeds for the following temperatures:" + curtemp=55 + maxtemp=70 + if [ "$fanmode" == "HDD" ] + then + curtemp=30 + maxtemp=60 + fi + while [ $curtemp -lt $maxtemp ] + do + errorfanflag=1 + while [ $errorfanflag -eq 1 ] + do + displaytemp=$curtemp + if [ "$temperature" == "F" ] + then + # Convert C to F + displaytemp=$((($curtemp*9/5)+32)) + fi + echo -n ""$displaytemp"$temperature (30-100 only):" + + curfan=$( get_number ) + if [ $curfan -ge 30 ] + then + errorfanflag=0 + elif [ $curfan -gt 100 ] + then + errorfanflag=0 + fi + done + echo $curtemp"="$curfan >> $daemonconfigfile + curtemp=$((curtemp+5)) + done + + sudo systemctl restart argononed.service + echo "Configuration updated." + elif [ $newmode -eq 3 ] + then + echo "Please provide fan speeds and temperature pairs" + echo + + subloopflag=1 + paircounter=0 + while [ $subloopflag -eq 1 ] + do + errortempflag=1 + errorfanflag=1 + echo "(You may set a blank value to end configuration)" + while [ $errortempflag -eq 1 ] + do + echo -n "Provide minimum temperature of $fanmode (in $temperature) then [ENTER]:" + + curtemp=$( get_number ) + if [ $curtemp -ge 0 ] + then + errortempflag=0 + elif [ $curtemp -eq -2 ] + then + # Blank + errortempflag=0 + errorfanflag=0 + subloopflag=0 + fi + done + while [ $errorfanflag -eq 1 ] + do + echo -n "Provide fan speed for "$curtemp"$temperature (30-100) then [ENTER]:" + curfan=$( get_number ) + if [ $curfan -ge 30 ] + then + errorfanflag=0 + elif [ $curfan -gt 100 ] + then + errorfanflag=0 + elif [ $curfan -eq -2 ] + then + # Blank + errortempflag=0 + errorfanflag=0 + subloopflag=0 + fi + done + if [ $subloopflag -eq 1 ] + then + if [ $paircounter -eq 0 ] + then + echo "#" > $daemonconfigfile + echo "# Argon Fan Configuration" >> $daemonconfigfile + echo "#" >> $daemonconfigfile + echo "# Min Temp=Fan Speed" >> $daemonconfigfile + fi + + displaytemp=$curtemp + paircounter=$((paircounter+1)) + if [ "$temperature" == "F" ] + then + # Convert to F to C + curtemp=$((($curtemp-32)*5/9)) + fi + echo $curtemp"="$curfan >> $daemonconfigfile + + echo "* Fan speed will be set to "$curfan" once $fanmode temperature reaches "$displaytemp"$temperature" + echo + fi + done + + echo + if [ $paircounter -gt 0 ] + then + echo "Thank you! We saved "$paircounter" pairs." + sudo systemctl restart argononed.service + echo "Changes should take effect now." + else + echo "Cancelled, no data saved." + fi + fi +done + +echo + diff --git a/source/argonone-irconfig.sh b/source/scripts/argonone-irconfig.sh similarity index 93% rename from source/argonone-irconfig.sh rename to source/scripts/argonone-irconfig.sh index 9be2028..3d0cb2b 100644 --- a/source/argonone-irconfig.sh +++ b/source/scripts/argonone-irconfig.sh @@ -1,5 +1,6 @@ #!/bin/bash + if [ -e /boot/firmware/config.txt ] ; then FIRMWARE=/firmware else @@ -24,9 +25,10 @@ then fi fi -echo "--------------------------------" -echo "Argon One IR Configuration Tool" -echo "--------------------------------" + +echo "-----------------------------" +echo " Argon IR Configuration Tool" +echo "------------------------------" echo "WARNING: This only supports NEC" echo " protocol only." echo -n "Press Y to continue:" @@ -70,8 +72,8 @@ get_number () { } irexecrcfile=/etc/lirc/irexec.lircrc -irexecshfile=/usr/bin/argonirexec -irdecodefile=/usr/bin/argonirdecoder +irexecshfile=/etc/argon/argonirexec +irdecodefile=/etc/argon/argonirdecoder kodiuserdatafolder="$HOME/.kodi/userdata" kodilircmapfile="$kodiuserdatafolder/Lircmap.xml" remotemode="" @@ -176,9 +178,9 @@ then fi elif [ $newmode -eq 2 ] then - echo "--------------------------------" - echo "Argon One IR Configuration Tool" - echo "--------------------------------" + echo "-----------------------------" + echo " Argon IR Configuration Tool" + echo "-----------------------------" echo "WARNING: This will install LIRC" echo " and related libraries." echo -n "Press Y to agree:" @@ -320,7 +322,10 @@ then echo ' KEY_DOWN' | tee -a $kodilircmapfile 1> /dev/null echo ' ' | tee -a $kodilircmapfile 1> /dev/null echo ' KEY_HOME' | tee -a $kodilircmapfile 1> /dev/null - echo ' KEY_MENUBACK' | tee -a $kodilircmapfile 1> /dev/null + # 20240611: User reported mapping is incorrect + #echo ' KEY_MENUBACK' | tee -a $kodilircmapfile 1> /dev/null + echo ' KEY_MENU' | tee -a $kodilircmapfile 1> /dev/null + echo ' KEY_BACK' | tee -a $kodilircmapfile 1> /dev/null echo ' KEY_VOLUMEUP' | tee -a $kodilircmapfile 1> /dev/null echo ' KEY_VOLUMEDOWN' | tee -a $kodilircmapfile 1> /dev/null echo ' ' | tee -a $kodilircmapfile 1> /dev/null diff --git a/source/scripts/argonone-upsconfig.sh b/source/scripts/argonone-upsconfig.sh new file mode 100644 index 0000000..351ba8f --- /dev/null +++ b/source/scripts/argonone-upsconfig.sh @@ -0,0 +1,305 @@ +#!/bin/bash + + +if [ -e /boot/firmware/config.txt ] ; then + FIRMWARE=/firmware +else + FIRMWARE= +fi +CONFIG=/boot${FIRMWARE}/config.txt + +CHECKGPIOMODE="libgpiod" # gpiod 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 + +pythonbin=/usr/bin/python3 + +# Files +ARGONDOWNLOADSERVER=https://download.argon40.com +INSTALLATIONFOLDER=/etc/argon +basename="argononeups" +daemonname=$basename"d" + +daemonupsservice=/lib/systemd/system/$daemonname.service +upsdaemonscript=$INSTALLATIONFOLDER/$daemonname.py + +rtcdaemonname="argonupsrtcd" + +daemonrtcservice=/lib/systemd/system/$rtcdaemonname.service +rtcdaemonscript=$INSTALLATIONFOLDER/$rtcdaemonname.py + + +requireinstall=0 +newmode=0 +echo "-----------------------------------" +echo " Argon Industria UPS Configuration" +echo "-----------------------------------" +if [ ! -f "$upsdaemonscript" ] +then + echo "Install Argon Industria UPS Tools" + 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 + + requireinstall=1 + newmode=3 # Reinstall + +fi + + +get_number () { + read curnumber + if [ -z "$curnumber" ] + then + echo "-2" + return + elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]] + then + if [ $curnumber -lt 0 ] + then + echo "-1" + return + elif [ $curnumber -gt 100 ] + then + echo "-1" + return + fi + echo $curnumber + return + fi + echo "-1" + return +} + +UPSCMDFILE="/dev/shm/upscmd.txt" +UPSSTATUSFILE="/dev/shm/upslog.txt" +rtcconfigscript=$INSTALLATIONFOLDER/argonups-rtcconfig.sh + +if [ -f "$UPSSTATUSFILE" ] +then +# cat $UPSSTATUSFILE + sudo $pythonbin $rtcdaemonscript GETBATTERY +fi + + +loopflag=1 +while [ $loopflag -eq 1 ] +do + if [ $requireinstall -eq 0 ] + then + echo + echo "Select option:" + echo " 1. UPS Battery Status" + echo " 2. Configure RTC and/or Schedule" + echo " 3. Reinstall UPS Tools" + echo " 4. Uninstall UPS Tools" + echo "" + echo " 0. Back" + + echo -n "Enter Number (0-4):" + + newmode=$( get_number ) + fi + if [[ $newmode -ge 0 && $newmode -le 4 ]] + then + if [ $newmode -eq 1 ] + then + sudo $pythonbin $rtcdaemonscript GETBATTERY + #if [ -f "$UPSSTATUSFILE" ] + #then + # cat $UPSSTATUSFILE + #else + # echo "Unable to retrieve status" + #fi + elif [ $newmode -eq 2 ] + then + $rtcconfigscript "argonupsrtc" + #TMPTIMESTR=`date +"%Y%d%m%H%M%S"` + #TMPDATASTR=`date +"%Y %m %d %H %M %S"` + + #echo "$TMPTIMESTR" > $UPSCMDFILE + #echo "3 $TMPDATASTR" >> $UPSCMDFILE + elif [ $newmode -eq 3 ] + then + # Start installation + if [ ! -d "$INSTALLATIONFOLDER/ups" ] + then + sudo mkdir $INSTALLATIONFOLDER/ups + fi + + + rtcconfigfile=/etc/argonupsrtc.conf + # Generate default RTC config file if non-existent + if [ ! -f $rtcconfigfile ]; then + sudo touch $rtcconfigfile + sudo chmod 666 $rtcconfigfile + + echo '#' >> $rtcconfigfile + echo '# Argon RTC Configuration' >> $rtcconfigfile + echo '#' >> $rtcconfigfile + fi + + for iconfile in battery_0 battery_2 battery_4 battery_charging battery_unknown battery_1 battery_3 battery_alert battery_plug + do + sudo wget $ARGONDOWNLOADSERVER/ups/${iconfile}.png -O $INSTALLATIONFOLDER/ups/${iconfile}.png --quiet + done + + 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 + + # Desktop Icon + destfoldername=$USERNAME + if [ -z "$destfoldername" ] + then + destfoldername=$USER + fi + if [ -z "$destfoldername" ] + then + destfoldername="pi" + fi + + shortcutfile="/home/$destfoldername/Desktop/argonone-ups.desktop" + if [ -d "/home/$destfoldername/Desktop" ] + then + terminalcmd="lxterminal --working-directory=/home/$destfoldername/ -t" + if [ -f "/home/$destfoldername/.twisteros.twid" ] + then + terminalcmd="xfce4-terminal --default-working-directory=/home/$destfoldername/ -T" + fi + + echo "[Desktop Entry]" > $shortcutfile + echo "Name=Argon UPS" >> $shortcutfile + echo "Comment=Argon UPS" >> $shortcutfile + echo "Icon=/etc/argon/ups/loading_0.png" >> $shortcutfile + echo 'Exec='$terminalcmd' "Argon UPS" -e "'$rtcconfigscript' argonupsrtc"' >> $shortcutfile + echo "Type=Application" >> $shortcutfile + echo "Encoding=UTF-8" >> $shortcutfile + echo "Terminal=false" >> $shortcutfile + echo "Categories=None;" >> $shortcutfile + chmod 755 $shortcutfile + fi + + # Stopped using default battery indicator + ## Build Kernel Module + #sourcecodefolder=$INSTALLATIONFOLDER/tmp + #buildfolder=$sourcecodefolder/build + #if [ -d $sourcecodefolder ] + #then + # sudo rm -rf $sourcecodefolder + #fi + #if [ "$CHECKPLATFORM" = "Ubuntu" ] + #then + # sudo apt-get install build-essential + #fi + #sudo mkdir -p $buildfolder + #sudo chmod -R 755 $buildfolder + + #FILELIST="COPYING Makefile argonbatteryicon.c" + #for fname in $FILELIST + #do + # sudo wget $ARGONDOWNLOADSERVER/modules/argonbatteryicon/$fname -O $buildfolder/#$fname --quiet + #done + + ## Start Build + #cd $buildfolder/ + #sudo make + #sudo cp "$buildfolder/argonbatteryicon.ko" "$INSTALLATIONFOLDER/ups/" + + ## Cleanup + #cd $INSTALLATIONFOLDER/ + #sudo rm -Rf "$sourcecodefolder" + + sudo wget $ARGONDOWNLOADSERVER/scripts/argononeupsd.py -O "$upsdaemonscript" --quiet + sudo wget $ARGONDOWNLOADSERVER/scripts/argononeupsd.service -O "$daemonupsservice" --quiet + sudo chmod 666 $daemonupsservice + #echo "User=$destfoldername" >> "$daemonupsservice" + #echo "Group=$destfoldername" >> "$daemonupsservice" + + sudo chmod 644 $daemonupsservice + + sudo wget $ARGONDOWNLOADSERVER/scripts/argoneon-rtcconfig.sh -O $rtcconfigscript --quiet + sudo chmod 755 $rtcconfigscript + + sudo wget $ARGONDOWNLOADSERVER/scripts/argonrtc.py -O $INSTALLATIONFOLDER/argonrtc.py --quiet + sudo wget $ARGONDOWNLOADSERVER/scripts/argonupsrtcd.py -O "$rtcdaemonscript" --quiet + sudo wget $ARGONDOWNLOADSERVER/scripts/argonupsrtcd.service -O "$daemonrtcservice" --quiet + sudo chmod 644 $daemonrtcservice + + if [ $requireinstall -eq 1 ] + then + requireinstall=0 + sudo systemctl enable "$daemonname.service" + sudo systemctl start "$daemonname.service" + + sudo systemctl enable "$rtcdaemonname.service" + sudo systemctl start "$rtcdaemonname.service" + else + sudo systemctl restart "$daemonname.service" + sudo systemctl restart "$rtcdaemonname.service" + loopflag=0 + fi + # Serial I/O is here + sudo systemctl restart argononed.service + elif [ $newmode -eq 4 ] + then + sudo systemctl stop "$daemonname.service" + sudo systemctl disable "$daemonname.service" + sudo rm $daemonupsservice + sudo rm $upsdaemonscript + + sudo systemctl stop "$rtcdaemonname.service" + sudo systemctl disable "$rtcdaemonname.service" + sudo rm $daemonrtcservice + sudo rm $rtcdaemonscript + + sudo rm -R -f $INSTALLATIONFOLDER/ups + + echo "Uninstall Completed" + loopflag=0 + else + echo "Cancelled" + loopflag=0 + fi + fi +done + + diff --git a/source/scripts/argononed.py b/source/scripts/argononed.py new file mode 100644 index 0000000..d38ee27 --- /dev/null +++ b/source/scripts/argononed.py @@ -0,0 +1,600 @@ +#!/usr/bin/python3 + +# +# This script set fan speed and monitor power button events. +# +# Fan Speed is set by sending 0 to 100 to the MCU (Micro Controller Unit) +# The values will be interpreted as the percentage of fan speed, 100% being maximum +# +# Power button events are sent as a pulse signal to BCM Pin 4 (BOARD P7) +# A pulse width of 20-30ms indicates reboot request (double-tap) +# A pulse width of 40-50ms indicates shutdown request (hold and release after 3 secs) +# +# Additional comments are found in each function below +# +# Standard Deployment/Triggers: +# * Raspbian, OSMC: Runs as service via /lib/systemd/system/argononed.service +# * lakka, libreelec: Runs as service via /storage/.config/system.d/argononed.service +# * recalbox: Runs as service via /etc/init.d/ +# + +import sys +import os +import time +from threading import Thread +from queue import Queue + +sys.path.append("/etc/argon/") +from argonsysinfo import * +from argonregister import * +from argonpowerbutton import * + +# Initialize I2C Bus +bus = argonregister_initializebusobj() + +OLED_ENABLED=False + +if os.path.exists("/etc/argon/argoneonoled.py"): + import datetime + from argoneonoled import * + OLED_ENABLED=True + +OLED_CONFIGFILE = "/etc/argoneonoled.conf" +UNIT_CONFIGFILE = "/etc/argonunits.conf" + +# This function converts the corresponding fanspeed for the given temperature +# The configuration data is a list of strings in the form "=" + +def get_fanspeed(tempval, configlist): + for curconfig in configlist: + curpair = curconfig.split("=") + tempcfg = float(curpair[0]) + fancfg = int(float(curpair[1])) + if tempval >= tempcfg: + if fancfg < 1: + return 0 + elif fancfg < 25: + return 25 + return fancfg + return 0 + +# This function retrieves the fanspeed configuration list from a file, arranged by temperature +# It ignores lines beginning with "#" and checks if the line is a valid temperature-speed pair +# The temperature values are formatted to uniform length, so the lines can be sorted properly + +def load_config(fname): + newconfig = [] + try: + with open(fname, "r") as fp: + for curline in fp: + if not curline: + continue + tmpline = curline.strip() + if not tmpline: + continue + if tmpline[0] == "#": + continue + tmppair = tmpline.split("=") + if len(tmppair) != 2: + continue + tempval = 0 + fanval = 0 + try: + tempval = float(tmppair[0]) + if tempval < 0 or tempval > 100: + continue + except: + continue + try: + fanval = int(float(tmppair[1])) + if fanval < 0 or fanval > 100: + continue + except: + continue + newconfig.append( "{:5.1f}={}".format(tempval,fanval)) + if len(newconfig) > 0: + newconfig.sort(reverse=True) + except: + return [] + return newconfig + +# Load OLED Config file +def load_oledconfig(fname): + output={} + screenduration=-1 + screenlist=[] + try: + with open(fname, "r") as fp: + for curline in fp: + if not curline: + continue + tmpline = curline.strip() + if not tmpline: + continue + if tmpline[0] == "#": + continue + tmppair = tmpline.split("=") + if len(tmppair) != 2: + continue + if tmppair[0] == "switchduration": + output['screenduration']=int(tmppair[1]) + elif tmppair[0] == "screensaver": + output['screensaver']=int(tmppair[1]) + elif tmppair[0] == "screenlist": + output['screenlist']=tmppair[1].replace("\"", "").split(" ") + elif tmppair[0] == "enabled": + output['enabled']=tmppair[1].replace("\"", "") + except: + return {} + return output + +# Load Unit Config file +def load_unitconfig(fname): + output={"temperature": "C"} + try: + with open(fname, "r") as fp: + for curline in fp: + if not curline: + continue + tmpline = curline.strip() + if not tmpline: + continue + if tmpline[0] == "#": + continue + tmppair = tmpline.split("=") + if len(tmppair) != 2: + continue + if tmppair[0] == "temperature": + output['temperature']=tmppair[1].replace("\"", "") + except: + return {} + return output + +def load_fancpuconfig(): + fanconfig = ["65=100", "60=55", "55=30"] + tmpconfig = load_config("/etc/argononed.conf") + if len(tmpconfig) > 0: + fanconfig = tmpconfig + return fanconfig + + +def load_fanhddconfig(): + fanhddconfig = ["50=100", "40=55", "30=30"] + fanhddconfigfile = "/etc/argononed-hdd.conf" + + if os.path.isfile(fanhddconfigfile): + tmpconfig = load_config(fanhddconfigfile) + if len(tmpconfig) > 0: + fanhddconfig = tmpconfig + else: + fanhddconfig = [] + return fanhddconfig + +# This function is the thread that monitors temperature and sets the fan speed +# The value is fed to get_fanspeed to get the new fan speed +# To prevent unnecessary fluctuations, lowering fan speed is delayed by 30 seconds +# +# Location of config file varies based on OS +# +def temp_check(): + INITIALSPEEDVAL = 200 # ensures fan speed gets set during initialization (e.g. change settings) + argonregsupport = argonregister_checksupport(bus) + + fanconfig = load_fancpuconfig() + fanhddconfig = load_fanhddconfig() + + prevspeed=INITIALSPEEDVAL + while True: + # Speed based on CPU Temp + val = argonsysinfo_getcputemp() + newspeed = get_fanspeed(val, fanconfig) + # Speed based on HDD Temp + val = argonsysinfo_getmaxhddtemp() + tmpspeed = get_fanspeed(val, fanhddconfig) + + # Use faster fan speed + if tmpspeed > newspeed: + newspeed = tmpspeed + + if prevspeed == newspeed: + time.sleep(30) + continue + elif newspeed < prevspeed and prevspeed != INITIALSPEEDVAL: + # Pause 30s before speed reduction to prevent fluctuations + time.sleep(30) + prevspeed = newspeed + try: + if newspeed > 0: + # Spin up to prevent issues on older units + argonregister_setfanspeed(bus, 100, argonregsupport) + # Set fan speed has sleep + argonregister_setfanspeed(bus, newspeed, argonregsupport) + time.sleep(30) + except IOError: + time.sleep(60) + +# +# This function is the thread that updates OLED +# +def display_loop(readq): + weekdaynamelist = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] + monthlist = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"] + oledscreenwidth = oled_getmaxX() + + fontwdSml = 6 # Maps to 6x8 + fontwdReg = 8 # Maps to 8x16 + stdleftoffset = 54 + + temperature="C" + tmpconfig=load_unitconfig(UNIT_CONFIGFILE) + if "temperature" in tmpconfig: + temperature = tmpconfig["temperature"] + + screensavermode = False + screensaversec = 120 + screensaverctr = 0 + + screenenabled = ["clock", "ip"] + prevscreen = "" + curscreen = "" + screenid = 0 + screenjogtime = 0 + screenjogflag = 0 # start with screenid 0 + cpuusagelist = [] + curlist = [] + + tmpconfig=load_oledconfig(OLED_CONFIGFILE) + + if "screensaver" in tmpconfig: + screensaversec = tmpconfig["screensaver"] + if "screenduration" in tmpconfig: + screenjogtime = tmpconfig["screenduration"] + if "screenlist" in tmpconfig: + screenenabled = tmpconfig["screenlist"] + + if "enabled" in tmpconfig: + if tmpconfig["enabled"] == "N": + screenenabled = [] + + while len(screenenabled) > 0: + if len(curlist) == 0 and screenjogflag == 1: + # Reset Screen Saver + screensavermode = False + screensaverctr = 0 + + # Update screen info + screenid = screenid + screenjogflag + if screenid >= len(screenenabled): + screenid = 0 + prevscreen = curscreen + curscreen = screenenabled[screenid] + + if screenjogtime == 0: + # Resets jogflag (if switched manually) + screenjogflag = 0 + else: + screenjogflag = 1 + + needsUpdate = False + if curscreen == "cpu": + # CPU Usage + if len(curlist) == 0: + try: + if len(cpuusagelist) == 0: + cpuusagelist = argonsysinfo_listcpuusage() + curlist = cpuusagelist + except: + curlist = [] + if len(curlist) > 0: + oled_loadbg("bgcpu") + + # Display List + yoffset = 0 + tmpmax = 4 + while tmpmax > 0 and len(curlist) > 0: + curline = "" + tmpitem = curlist.pop(0) + curline = tmpitem["title"]+": "+str(tmpitem["value"])+"%" + oled_writetext(curline, stdleftoffset, yoffset, fontwdSml) + oled_drawfilledrectangle(stdleftoffset, yoffset+12, int((oledscreenwidth-stdleftoffset-4)*tmpitem["value"]/100), 2) + tmpmax = tmpmax - 1 + yoffset = yoffset + 16 + + needsUpdate = True + else: + # Next page due to error/no data + screenjogflag = 1 + elif curscreen == "storage": + # Storage Info + if len(curlist) == 0: + try: + tmpobj = argonsysinfo_listhddusage() + for curdev in tmpobj: + curlist.append({"title": curdev, "value": argonsysinfo_kbstr(tmpobj[curdev]['total']), "usage": int(100*tmpobj[curdev]['used']/tmpobj[curdev]['total']) }) + #curlist = argonsysinfo_liststoragetotal() + except: + curlist = [] + if len(curlist) > 0: + oled_loadbg("bgstorage") + + yoffset = 16 + tmpmax = 3 + while tmpmax > 0 and len(curlist) > 0: + tmpitem = curlist.pop(0) + # Right column first, safer to overwrite white space + oled_writetextaligned(tmpitem["value"], 77, yoffset, oledscreenwidth-77, 2, fontwdSml) + oled_writetextaligned(str(tmpitem["usage"])+"%", 50, yoffset, 74-50, 2, fontwdSml) + tmpname = tmpitem["title"] + if len(tmpname) > 8: + tmpname = tmpname[0:8] + oled_writetext(tmpname, 0, yoffset, fontwdSml) + + tmpmax = tmpmax - 1 + yoffset = yoffset + 16 + needsUpdate = True + else: + # Next page due to error/no data + screenjogflag = 1 + + elif curscreen == "raid": + # Raid Info + if len(curlist) == 0: + try: + tmpobj = argonsysinfo_listraid() + curlist = tmpobj['raidlist'] + except: + curlist = [] + if len(curlist) > 0: + oled_loadbg("bgraid") + tmpitem = curlist.pop(0) + oled_writetextaligned(tmpitem["title"], 0, 0, stdleftoffset, 1, fontwdSml) + oled_writetextaligned(tmpitem["value"], 0, 8, stdleftoffset, 1, fontwdSml) + oled_writetextaligned(argonsysinfo_kbstr(tmpitem["info"]["size"]), 0, 56, stdleftoffset, 1, fontwdSml) + + if len(tmpitem['info']['state']) > 0: + oled_writetext( tmpitem['info']['state'], stdleftoffset, 8, fontwdSml ) + + if len(tmpitem['info']['rebuildstat']) > 0: + oled_writetext("Rebuild:" + tmpitem['info']['rebuildstat'], stdleftoffset, 16, fontwdSml) + + # TODO: May need to use different method for each raid type (i.e. check raidlist['raidlist'][raidctr]['value']) + #oled_writetext("Used:"+str(int(100*tmpitem["info"]["used"]/tmpitem["info"]["size"]))+"%", stdleftoffset, 24, fontwdSml) + + + oled_writetext("Active:"+str(int(tmpitem["info"]["active"]))+"/"+str(int(tmpitem["info"]["devices"])), stdleftoffset, 32, fontwdSml) + oled_writetext("Working:"+str(int(tmpitem["info"]["working"]))+"/"+str(int(tmpitem["info"]["devices"])), stdleftoffset, 40, fontwdSml) + oled_writetext("Failed:"+str(int(tmpitem["info"]["failed"]))+"/"+str(int(tmpitem["info"]["devices"])), stdleftoffset, 48, fontwdSml) + needsUpdate = True + else: + # Next page due to error/no data + screenjogflag = 1 + + elif curscreen == "ram": + # RAM + try: + oled_loadbg("bgram") + tmpraminfo = argonsysinfo_getram() + oled_writetextaligned(tmpraminfo[0], stdleftoffset, 8, oledscreenwidth-stdleftoffset, 1, fontwdReg) + oled_writetextaligned("of", stdleftoffset, 24, oledscreenwidth-stdleftoffset, 1, fontwdReg) + oled_writetextaligned(tmpraminfo[1], stdleftoffset, 40, oledscreenwidth-stdleftoffset, 1, fontwdReg) + needsUpdate = True + except: + needsUpdate = False + # Next page due to error/no data + screenjogflag = 1 + elif curscreen == "temp": + # Temp + try: + oled_loadbg("bgtemp") + 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: + oled_writetext(curdev.upper()+": "+ tmpstr+ chr(167) +temperature, stdleftoffset, displayrow, fontwdSml) + + else: + oled_writetext(curdev.upper()+":", stdleftoffset, displayrow, fontwdSml) + + oled_writetext(" "+ tmpstr+ chr(167) +temperature, stdleftoffset, displayrow+displayrowht, fontwdSml) + displayrow = displayrow + displayrowht*2 + 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] + + oled_writetextaligned(tmpstr+ chr(167) +temperature, stdleftoffset, 24, oledscreenwidth-stdleftoffset, 1, fontwdReg) + + # Temperature Bar: 40C is min, 80C is max + maxht = 21 + barht = int(maxht*(maxcval-40)/40) + if barht > maxht: + barht = maxht + elif barht < 1: + barht = 1 + oled_drawfilledrectangle(24, 20+(maxht-barht), 3, barht, 2) + + + needsUpdate = True + except: + needsUpdate = False + # Next page due to error/no data + screenjogflag = 1 + elif curscreen == "ip": + # IP Address + try: + oled_loadbg("bgip") + oled_writetextaligned(argonsysinfo_getip(), 0, 8, oledscreenwidth, 1, fontwdReg) + needsUpdate = True + except: + needsUpdate = False + # Next page due to error/no data + screenjogflag = 1 + elif curscreen == "logo1v5": + # Logo + try: + oled_loadbg("logo1v5") + needsUpdate = True + except: + needsUpdate = False + # Next page due to error/no data + screenjogflag = 1 + else: + try: + oled_loadbg("bgtime") + # Date and Time HH:MM + curtime = datetime.datetime.now() + + # Month/Day + outstr = str(curtime.day).strip() + if len(outstr) < 2: + outstr = " "+outstr + outstr = monthlist[curtime.month-1]+outstr + oled_writetextaligned(outstr, stdleftoffset, 8, oledscreenwidth-stdleftoffset, 1, fontwdReg) + + # Day of Week + oled_writetextaligned(weekdaynamelist[curtime.weekday()], stdleftoffset, 24, oledscreenwidth-stdleftoffset, 1, fontwdReg) + + # Time + outstr = str(curtime.minute).strip() + if len(outstr) < 2: + outstr = "0"+outstr + outstr = str(curtime.hour)+":"+outstr + if len(outstr) < 5: + outstr = "0"+outstr + oled_writetextaligned(outstr, stdleftoffset, 40, oledscreenwidth-stdleftoffset, 1, fontwdReg) + + needsUpdate = True + except: + needsUpdate = False + # Next page due to error/no data + screenjogflag = 1 + + if needsUpdate == True: + if screensavermode == False: + # Update screen if not screen saver mode + oled_power(True) + oled_flushimage(prevscreen != curscreen) + oled_reset() + + timeoutcounter = 0 + while timeoutcounter= 60 and screensavermode == False: + # Refresh data every minute, unless screensaver got triggered + screenjogflag = 0 + break + display_defaultimg() + +def display_defaultimg(): + # Load default image + #oled_power(True) + #oled_loadbg("bgdefault") + #oled_flushimage() + oled_fill(0) + oled_reset() + +if len(sys.argv) > 1: + cmd = sys.argv[1].upper() + if cmd == "SHUTDOWN": + # Signal poweroff + argonregister_signalpoweroff(bus) + + elif cmd == "FANOFF": + # Turn off fan + argonregister_setfanspeed(bus,0) + + if OLED_ENABLED == True: + display_defaultimg() + + elif cmd == "SERVICE": + # Starts the power button and temperature monitor threads + try: + ipcq = Queue() + if len(sys.argv) > 2: + cmd = sys.argv[2].upper() + if cmd == "OLEDSWITCH": + t1 = Thread(target = argonpowerbutton_monitorswitch, args =(ipcq, )) + else: + t1 = Thread(target = argonpowerbutton_monitor, args =(ipcq, )) + + t2 = Thread(target = temp_check) + if OLED_ENABLED == True: + t3 = Thread(target = display_loop, args =(ipcq, )) + + t1.start() + t2.start() + if OLED_ENABLED == True: + t3.start() + + ipcq.join() + except Exception: + sys.exit(1) diff --git a/source/scripts/argononed.service b/source/scripts/argononed.service new file mode 100644 index 0000000..463a5ef --- /dev/null +++ b/source/scripts/argononed.service @@ -0,0 +1,10 @@ +[Unit] +Description=Argon One Fan and Button Service +After=multi-user.target +[Service] +Type=simple +Restart=always +RemainAfterExit=true +ExecStart=/usr/bin/python3 /etc/argon/argononed.py SERVICE +[Install] +WantedBy=multi-user.target diff --git a/source/scripts/argononeoled.py b/source/scripts/argononeoled.py new file mode 100644 index 0000000..cd98e46 --- /dev/null +++ b/source/scripts/argononeoled.py @@ -0,0 +1,333 @@ +#!/usr/bin/python3 + +from luma.core.interface.serial import i2c +from luma.oled.device import ssd1306 +from PIL import Image + +import sys +import datetime +import math + +import os +import time + +# Initialize I2C Bus +import smbus + +oledport=1 + +try: + bus=smbus.SMBus(1) +except Exception: + try: + oledport=0 + # Older version + bus=smbus.SMBus(0) + except Exception: + print("Unable to detect i2c") + bus=None + + +ADDR_OLED=0x3c +OLED_WD=1 +OLED_HT=1 +oled_device=None +try: + oled_device=ssd1306(i2c(port=oledport, address=ADDR_OLED)) + + OLED_WD=oled_device.bounding_box[2]+1 + OLED_HT=oled_device.bounding_box[3]+1 +except Exception: + print("Unable to initialize OLED") + bus=None + +OLED_NUMFONTCHAR=256 + +OLED_BUFFERIZE = ((OLED_WD*OLED_HT)>>3) +oled_imagebuffer = [0] * OLED_BUFFERIZE + + +def oled_getmaxY(): + return OLED_HT + +def oled_getmaxX(): + return OLED_WD + +def oled_loadbg(bgname): + if bgname == "bgblack": + oled_clearbuffer() + return + elif bgname == "bgwhite": + oled_clearbuffer(1) + return + try: + file = open("/etc/argon/oled/"+bgname+".bin", "rb") + bgbytes = list(file.read()) + file.close() + ctr = len(bgbytes) + if ctr == OLED_BUFFERIZE: + oled_imagebuffer[:] = bgbytes + elif ctr > OLED_BUFFERIZE: + oled_imagebuffer[:] = bgbytes[0:OLED_BUFFERIZE] + else: + oled_imagebuffer[0:ctr] = bgbytes + # Clear the rest of the buffer + while ctr < OLED_BUFFERIZE: + oled_imagebuffer[ctr] = 0 + ctr=ctr+1 + except FileNotFoundError: + oled_clearbuffer() + + +def oled_clearbuffer(value = 0): + if value != 0: + value = 0xff + ctr = 0 + while ctr < OLED_BUFFERIZE: + oled_imagebuffer[ctr] = value + ctr=ctr+1 + +def oled_writebyterow(x,y,bytevalue, mode = 0): + bufferoffset = OLED_WD*(y>>3) + x + if mode == 0: + oled_imagebuffer[bufferoffset] = bytevalue + elif mode == 1: + oled_imagebuffer[bufferoffset] = bytevalue^oled_imagebuffer[bufferoffset] + else: + oled_imagebuffer[bufferoffset] = bytevalue|oled_imagebuffer[bufferoffset] + + +def oled_writebuffer(x,y,value, mode = 0): + + yoffset = y>>3 + yshift = y&0x7 + ybit = (1<> 1 + xidx = xidx + 1 + if xidx >= 8: + tmplist[srcidx+outoffsetidx + outyoffset] = outbyte + xmask = 0x80 + xidx = 0 + outbyte = 0 + outoffsetidx = outoffsetidx + 1 + + xoffset = xoffset + 1 + + outyoffset = outyoffset + (OLED_WD>>3) + yidx = yidx + 1 + ymask = ymask << 1 + + srcidx = srcidx + OLED_WD + + oled_device.display(Image.frombytes("1", [OLED_WD, OLED_HT], bytes(tmplist))) + + if hidescreen == True: + # Display + oled_power(True) + + + +def oled_drawfilledrectangle(x, y, wd, ht, mode = 0): + ymax = y + ht + cury = y&0xF8 + + xmax = x + wd + curx = x + if ((y & 0x7)) != 0: + yshift = y&0x7 + bytevalue = (0xFF<>yshift) + + while curx < xmax: + oled_writebyterow(curx,cury,bytevalue, mode) + curx = curx + 1 + cury = cury + 8 + # Draw 8 rows at a time when possible + while cury + 8 < ymax: + curx = x + while curx < xmax: + oled_writebyterow(curx,cury,0xFF, mode) + curx = curx + 1 + cury = cury + 8 + + if cury < ymax: + yshift = 8-((ymax-cury)&0x7) + bytevalue = (0xFF>>yshift) + + curx = x + while curx < xmax: + oled_writebyterow(curx,cury,bytevalue, mode) + curx = curx + 1 + + +def oled_writetextaligned(textdata, x, y, boxwidth, alignmode, charwd = 6, mode = 0): + leftoffset = 0 + if alignmode == 1: + # Centered + leftoffset = (boxwidth-len(textdata)*charwd)>>1 + elif alignmode == 2: + # Right aligned + leftoffset = (boxwidth-len(textdata)*charwd) + + oled_writetext(textdata, x+leftoffset, y, charwd, mode) + + +def oled_writetext(textdata, x, y, charwd = 6, mode = 0): + if charwd < 6: + charwd = 6 + + charht = int((charwd<<3)/6) + if charht & 0x7: + charht = (charht&0xF8) + 8 + + try: + file = open("/etc/argon/oled/font"+str(charht)+"x"+str(charwd)+".bin", "rb") + fontbytes = list(file.read()) + file.close() + except FileNotFoundError: + try: + # Default to smallest + file = open("/etc/argon/oled/font8x6.bin", "rb") + fontbytes = list(file.read()) + file.close() + except FileNotFoundError: + return + + if ((y & 0x7)) == 0: + # Use optimized loading + oled_fastwritetext(textdata, x, y, charht, charwd, fontbytes, mode) + return + + numfontrow = charht>>3 + ctr = 0 + while ctr < len(textdata): + fontoffset = ord(textdata[ctr])*charwd + fontcol = 0 + while fontcol < charwd and x < OLED_WD: + fontrow = 0 + row = y + while fontrow < numfontrow and row < OLED_HT and x >= 0: + curbit = 0x80 + curbyte = (fontbytes[fontoffset + fontcol + (OLED_NUMFONTCHAR*charwd*fontrow)]) + subrow = 0 + while subrow < 8 and row < OLED_HT: + value = 0 + if (curbyte&curbit) != 0: + value = 1 + oled_writebuffer(x,row,value, mode) + curbit = curbit >> 1 + row = row + 1 + subrow = subrow + 1 + fontrow = fontrow + 1 + fontcol = fontcol + 1 + x = x + 1 + ctr = ctr + 1 + +def oled_fastwritetext(textdata, x, y, charht, charwd, fontbytes, mode = 0): + + numfontrow = charht>>3 + ctr = 0 + while ctr < len(textdata): + fontoffset = ord(textdata[ctr])*charwd + fontcol = 0 + while fontcol < charwd and x < OLED_WD: + fontrow = 0 + row = y&0xF8 + while fontrow < numfontrow and row < OLED_HT and x >= 0: + curbyte = (fontbytes[fontoffset + fontcol + (OLED_NUMFONTCHAR*charwd*fontrow)]) + oled_writebyterow(x,row,curbyte, mode) + fontrow = fontrow + 1 + row = row + 8 + fontcol = fontcol + 1 + x = x + 1 + ctr = ctr + 1 + return + + +def oled_power(turnon = True): + if bus is None: + return + try: + if turnon == True: + oled_device.show() + else: + oled_device.hide() + except: + return + + +def oled_inverse(enable = True): + # Not supported? + return + + +def oled_fullwhite(enable = True): + # Not supported? + return + + + +def oled_reset(): + return + + diff --git a/source/scripts/argononeoledd.service b/source/scripts/argononeoledd.service new file mode 100644 index 0000000..f735236 --- /dev/null +++ b/source/scripts/argononeoledd.service @@ -0,0 +1,10 @@ +[Unit] +Description=Argon One Fan and Button Service +After=multi-user.target +[Service] +Type=simple +Restart=always +RemainAfterExit=true +ExecStart=/usr/bin/python3 /etc/argon/argononed.py SERVICE OLEDSWITCH +[Install] +WantedBy=multi-user.target diff --git a/source/scripts/argonpowerbutton-libgpiod.py b/source/scripts/argonpowerbutton-libgpiod.py new file mode 100644 index 0000000..8f8c6d8 --- /dev/null +++ b/source/scripts/argonpowerbutton-libgpiod.py @@ -0,0 +1,89 @@ + +# 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 + +# 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_monitor(writeq): + + try: + # Reference https://github.com/brgl/libgpiod/blob/master/bindings/python/examples/gpiomon.py + + # Pin Assignments + LINE_SHUTDOWN=4 + try: + # Pi5 mapping + chip = gpiod.Chip('4') + except Exception as gpioerr: + # Old mapping + chip = gpiod.Chip('0') + + lineobj = chip.get_line(LINE_SHUTDOWN) + lineobj.request(consumer="argon", type=gpiod.LINE_REQ_EV_BOTH_EDGES) + while True: + hasevent = lineobj.event_wait(10) + if hasevent: + pulsetime = 1 + eventdata = lineobj.event_read() + if eventdata.type == gpiod.LineEvent.RISING_EDGE: + # Time pulse data + while lineobj.get_value() == 1: + 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") + lineobj.release() + chip.close() + except Exception: + writeq.put("ERROR") + + +def argonpowerbutton_monitorswitch(writeq): + + try: + # Reference https://github.com/brgl/libgpiod/blob/master/bindings/python/examples/gpiomon.py + + # Pin Assignments + LINE_SHUTDOWN=4 + try: + # Pi5 mapping + chip = gpiod.Chip('4') + except Exception as gpioerr: + # Old mapping + chip = gpiod.Chip('0') + + lineobj = chip.get_line(LINE_SHUTDOWN) + lineobj.request(consumer="argon", type=gpiod.LINE_REQ_EV_BOTH_EDGES) + while True: + hasevent = lineobj.event_wait(10) + if hasevent: + pulsetime = 1 + eventdata = lineobj.event_read() + if eventdata.type == gpiod.LineEvent.RISING_EDGE: + # Time pulse data + while lineobj.get_value() == 1: + time.sleep(0.01) + pulsetime += 1 + + if pulsetime >= 10: + writeq.put("OLEDSWITCH") + lineobj.release() + chip.close() + except Exception: + writeq.put("ERROR") diff --git a/source/scripts/argonpowerbutton-rpigpio.py b/source/scripts/argonpowerbutton-rpigpio.py new file mode 100644 index 0000000..4472654 --- /dev/null +++ b/source/scripts/argonpowerbutton-rpigpio.py @@ -0,0 +1,66 @@ + +# 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 + +# 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_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/source/scripts/argonregister-v1.py b/source/scripts/argonregister-v1.py new file mode 100644 index 0000000..edd9c1d --- /dev/null +++ b/source/scripts/argonregister-v1.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/source/scripts/argonregister.py b/source/scripts/argonregister.py new file mode 100644 index 0000000..d156c8b --- /dev/null +++ b/source/scripts/argonregister.py @@ -0,0 +1,109 @@ +#!/usr/bin/python3 + +# +# Argon Register Helper methods +# + +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): + if busobj is None: + return False + try: + oldval = argonregister_getbyte(busobj, ADDR_ARGONONEREG_DUTYCYCLE) + newval = oldval + 1 + if newval >= 100: + newval = 98 + argonregister_setbyte(busobj, ADDR_ARGONONEREG_DUTYCYCLE, newval) + newval = argonregister_getbyte(busobj, ADDR_ARGONONEREG_DUTYCYCLE) + if newval != oldval: + argonregister_setbyte(busobj, ADDR_ARGONONEREG_DUTYCYCLE, oldval) + return True + return False + except: + 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): + if busobj is None: + return 0 + + usereg=False + if regsupport is None: + usereg=argonregister_checksupport(busobj) + else: + usereg=regsupport + if usereg == True: + return argonregister_getbyte(busobj, ADDR_ARGONONEREG_DUTYCYCLE) + else: + return 0 + + +def argonregister_setfanspeed(busobj, newspeed, regsupport=None): + if busobj is None: + return + + if newspeed > 100: + newspeed = 100 + elif newspeed < 0: + newspeed = 0 + usereg=False + if regsupport is None: + usereg=argonregister_checksupport(busobj) + else: + usereg=regsupport + if usereg == True: + argonregister_setbyte(busobj, ADDR_ARGONONEREG_DUTYCYCLE, newspeed) + else: + busobj.write_byte(ADDR_ARGONONEFAN,newspeed) + time.sleep(1) + +def argonregister_signalpoweroff(busobj): + if busobj is None: + return + + if argonregister_checksupport(busobj): + argonregister_setbyte(busobj, ADDR_ARGONONEREG_CTRL, 1) + else: + 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/source/scripts/argonrtc.py b/source/scripts/argonrtc.py new file mode 100644 index 0000000..ba6c64e --- /dev/null +++ b/source/scripts/argonrtc.py @@ -0,0 +1,642 @@ +#!/usr/bin/python3 + +import os +import datetime + +######### +# Describe Methods +######### + +# Helper method to add proper suffix to numbers +def getNumberSuffix(numval): + onesvalue = numval % 10 + if onesvalue == 1: + return "st" + elif onesvalue == 2: + return "nd" + elif onesvalue == 3: + return "rd" + return "th" + +def describeHourMinute(hour, minute): + if hour < 0: + return "" + outstr = "" + ampmstr = "" + if hour <= 0: + hour = 0 + outstr = outstr + "12" + ampmstr = "am" + elif hour <= 12: + outstr = outstr + str(hour) + if hour == 12: + ampmstr = "pm" + else: + ampmstr = "am" + else: + outstr = outstr + str(hour-12) + ampmstr = "pm" + + if minute >= 10: + outstr = outstr+":" + elif minute > 0: + outstr = outstr+":0" + else: + if hour == 0: + ampmstr = "mn" + elif hour == 12: + ampmstr = "nn" + return outstr+ampmstr + + if minute <= 0: + minute = 0 + outstr = outstr+str(minute) + + return outstr+ampmstr + +# Describe Schedule Parameter Values +def describeSchedule(monthlist, weekdaylist, datelist, hourlist, minutelist): + weekdaynamelist = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] + monthnamelist = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + + curprefix = "" + hasDate = False + hasMonth = False + foundvalue = False + monthdatestr = "" + for curmonth in monthlist: + for curdate in datelist: + if curdate >= 0: + hasDate = True + if curmonth >= 0: + hasMonth = True + monthdatestr = monthdatestr + "," + monthnamelist[curmonth-1]+" "+str(curdate) + getNumberSuffix(curdate) + else: + monthdatestr = monthdatestr + ","+str(curdate) + getNumberSuffix(curdate) + else: + if curmonth >= 0: + monthdatestr = monthdatestr + "," + monthnamelist[curmonth-1] + + if len(monthdatestr) > 0: + foundvalue = True + # Remove Leading Comma + monthdatestr = monthdatestr[1:] + if hasMonth == True: + curprefix = "Annually:" + else: + curprefix = "Monthly:" + monthdatestr = monthdatestr + " of the Month" + monthdatestr = " Every "+monthdatestr + + weekdaystr = "" + for curweekday in weekdaylist: + if curweekday >= 0: + hasDate = True + weekdaystr = weekdaystr + "," + weekdaynamelist[curweekday] + + if len(weekdaystr) > 0: + foundvalue = True + # Remove Leading Comma + weekdaystr = weekdaystr[1:] + if len(curprefix) == 0: + curprefix = "Weekly:" + weekdaystr = " on " + weekdaystr + else: + weekdaystr = ",on " + weekdaystr + + hasHour = False + hasMinute = False + hourminstr = "" + for curhour in hourlist: + for curminute in minutelist: + if curhour >= 0: + hasHour = True + if curminute >= 0: + hasMinute = True + hourminstr = hourminstr + "," + describeHourMinute(curhour, curminute) + elif curminute >= 0: + hasMinute = True + hourminstr = hourminstr + "," + str(curminute) + getNumberSuffix(curminute) + + if len(hourminstr) > 0: + foundvalue = True + # Remove Leading Comma + hourminstr = hourminstr[1:] + if hasHour == True: + if hasDate == True: + hourminstr = "at " + hourminstr + else: + hourminstr = "Daily: " + hourminstr + if hasMinute == False: + hourminstr = hourminstr + " every minute" + else: + if hourminstr == "0": + hourminstr = "At the start of every hour" + else: + hourminstr = "Hourly: At " + hourminstr + " minute" + else: + hourminstr = "Every minute" + + if len(curprefix) > 0: + hourminstr = ","+hourminstr + + return (curprefix + monthdatestr + weekdaystr + hourminstr).strip() + + +######### +# Alarm +######### + +# Alarm to UTC/Local time +def convertAlarmTimezone(weekday, caldate, hour, minute, toutc): + utcdiffsec = getLocaltimeOffset().seconds + if toutc == False: + utcdiffsec = utcdiffsec*(-1) + + utcdiffsec = utcdiffsec - (utcdiffsec%60) + utcdiffmin = utcdiffsec % 3600 + utcdiffhour = int((utcdiffsec - utcdiffmin)/3600) + utcdiffmin = int(utcdiffmin/60) + + addhour = 0 + if minute >= 0: + minute = minute - utcdiffmin + if minute < 0: + addhour = -1 + minute = minute + 60 + elif minute > 59: + addhour = 1 + minute = minute - 60 + + addday = 0 + if hour >= 0: + hour = hour - utcdiffhour + tmphour = hour + addhour + if hour < 0: + hour = hour + 24 + elif hour > 23: + hour = hour - 24 + if tmphour < 0: + addday = -1 + elif tmphour > 23: + addday = 1 + + if addday != 0: + if weekday >= 0: + weekday = weekday + addday + if weekday < 0: + weekday = weekday + 7 + elif weekday > 6: + weekday = weekday - 7 + if caldate > 0: + # Edge cases might not be handled properly though + curtime = datetime.datetime.now() + maxmonthdate = getLastMonthDate(curtime.year, curtime.month) + caldate = caldate + addday + if caldate == 0: + # move to end of the month + caldate = maxmonthdate + elif caldate > maxmonthdate: + # move to next month + caldate = 1 + + return [weekday, caldate, hour, minute] + + +# Get RTC Alarm Setting (Negative values ignored) +def getRTCAlarm(weekday, caldate, hour, minute): + hasError = False + if caldate < 1 and weekday < 0 and hour < 0 and minute < 0: + hasError = True + elif minute > 59: + hasError = True + elif hour > 23: + hasError = True + elif weekday > 6: + hasError = True + elif caldate > 31: + hasError = True + + if hasError == True: + return [-1, -1, -1, -1] + # Convert to UTC + return convertAlarmTimezone(weekday, caldate, hour, minute, True) + +######### +# Date/Time tools +######### + +# Get local time vs UTC +def getLocaltimeOffset(): + localdatetime = datetime.datetime.now() + utcdatetime = datetime.datetime.fromtimestamp(localdatetime.timestamp(), datetime.timezone.utc) + # Remove TZ info to allow subtraction + utcdatetime = utcdatetime.replace(tzinfo = None) + + return localdatetime - utcdatetime + + +# Sync Time to RTC Time (for Daemon use) +def updateSystemTime(rtctime): + os.system("date -s '"+rtctime.isoformat()+"' >/dev/null 2>&1") + + +######### +# Config +######### + +# Load config value as array of integers +def getConfigValue(valuestr): + try: + if valuestr == "*": + return [-1] + tmplist = valuestr.split(",") + map_object = map(int, tmplist) + return list(map_object) + except: + return [-1] + +# Load config line data as array of Command schedule +def newCommandSchedule(curline): + result = [] + linedata = curline.split(" ") + if len(linedata) < 6: + return result + + minutelist = getConfigValue(linedata[0]) + hourlist = getConfigValue(linedata[1]) + datelist = getConfigValue(linedata[2]) + #monthlist = getConfigValue(linedata[3]) + monthlist = [-1] # Certain edge cases will not be handled properly + weekdaylist = getConfigValue(linedata[4]) + + cmd = "" + ctr = 5 + while ctr < len(linedata): + cmd = cmd + " " + linedata[ctr] + ctr = ctr + 1 + cmd = cmd.strip() + + for curmin in minutelist: + for curhour in hourlist: + for curdate in datelist: + for curmonth in monthlist: + for curweekday in weekdaylist: + result.append({ "minute": curmin, "hour": curhour, "date": curdate, "month":curmonth, "weekday": curweekday, "cmd":cmd }) + + return result + +# Save updated config file +def saveConfigList(fname, configlist): + f = open(fname, "w") + f.write("#\n") + f.write("# Argon RTC Configuration\n") + f.write("# - Follows cron general format, but with only * and csv support\n") + f.write("# - Each row follows the following format:\n") + f.write("# min hour date month dayOfWeek Command\n") + f.write("# e.g. Shutdown daily at 1am\n") + f.write("# 0 1 * * * off\n") + f.write("# Shutdown daily at 1am and 1pm\n") + f.write("# 0 1,13 * * * off\n") + f.write("# - Commands are currently on or off only\n") + f.write("# - Limititations\n") + f.write("# Requires MINUTE value\n") + f.write("# Month values are ignored (edge cases not supported)\n") + f.write("#\n") + + for config in configlist: + f.write(config+"\n") + f.close() + +# Remove config line +def removeConfigEntry(fname, entryidx): + configlist = loadConfigList(fname) + if len(configlist) > entryidx: + configlist.pop(entryidx) + saveConfigList(fname, configlist) + +# Load config list (removes invalid data) +def loadConfigList(fname): + try: + result = [] + with open(fname, "r") as fp: + for curline in fp: + if not curline: + continue + curline = curline.strip().replace('\t', ' ') + # Handle special characters that get encoded + tmpline = "".join([c if 0x20<=ord(c) and ord(c)<=0x7e else "" for c in curline]) + + if not tmpline: + continue + if tmpline[0] == "#": + continue + checkdata = tmpline.split(" ") + if len(checkdata) > 5: + # Don't include every minute type of schedule + if checkdata[0] != "*": + result.append(tmpline) + return result + except: + return [] + +# Form Command Schedule list from config list +def formCommandScheduleList(configlist): + try: + result = [] + for config in configlist: + result = result + newCommandSchedule(config) + return result + except: + return [] + +# Describe config list entry +def describeConfigListEntry(configlistitem): + linedata = configlistitem.split(" ") + if len(linedata) < 6: + return "" + + minutelist = getConfigValue(linedata[0]) + hourlist = getConfigValue(linedata[1]) + datelist = getConfigValue(linedata[2]) + #monthlist = getConfigValue(linedata[3]) + monthlist = [-1] # Certain edge cases will not be handled properly + weekdaylist = getConfigValue(linedata[4]) + + cmd = "" + ctr = 5 + while ctr < len(linedata): + cmd = cmd + " " + linedata[ctr] + ctr = ctr + 1 + cmd = cmd.strip().lower() + if cmd == "on": + cmd = "Startup" + else: + cmd = "Shutdown" + + return cmd+" | "+describeSchedule(monthlist, weekdaylist, datelist, hourlist, minutelist) + +# Describe config list and show indices +def describeConfigList(fname): + # 1 is reserved for New schedule + ctr = 2 + configlist = loadConfigList(fname) + for config in configlist: + tmpline = describeConfigListEntry(config) + if len(tmpline) > 0: + print(" "+str(ctr)+". ", tmpline) + ctr = ctr + 1 + if ctr == 2: + print(" No Existing Schedules") + +# Check Command schedule if it should fire for the give time +def checkDateForCommandSchedule(commandschedule, datetimeobj): + testminute = commandschedule.get("minute", -1) + testhour = commandschedule.get("hour", -1) + testdate = commandschedule.get("date", -1) + testmonth = commandschedule.get("month", -1) + testweekday = commandschedule.get("weekday", -1) + + if testminute < 0 or testminute == datetimeobj.minute: + if testhour < 0 or testhour == datetimeobj.hour: + if testdate < 0 or testdate == datetimeobj.day: + if testmonth < 0 or testmonth == datetimeobj.month: + if testweekday < 0: + return True + else: + # python Sunday = 6, RTC Sunday = 0 + weekDay = datetimeobj.weekday() + if weekDay == 6: + weekDay = 0 + else: + weekDay = weekDay + 1 + if testweekday == weekDay: + return True + return False + +# Get current command +def getCommandForTime(commandschedulelist, datetimeobj, checkcmd): + ctr = 0 + while ctr < len(commandschedulelist): + testcmd = commandschedulelist[ctr].get("cmd", "") + if (testcmd.lower() == checkcmd or len(checkcmd) == 0) and len(testcmd) > 0: + if checkDateForCommandSchedule(commandschedulelist[ctr], datetimeobj) == True: + return testcmd + ctr = ctr + 1 + return "" + +# Get Last Date of Month +def getLastMonthDate(year, month): + if month < 12: + testtime = datetime.datetime(year, month+1, 1) + else: + testtime = datetime.datetime(year+1, 1, 1) + testtime = testtime - datetime.timedelta(days=1) + return testtime.day + +# Increment to the next iteration of command schedule +def incrementCommandScheduleTime(commandschedule, testtime, addmode): + testminute = commandschedule.get("minute", -1) + testhour = commandschedule.get("hour", -1) + testdate = commandschedule.get("date", -1) + testmonth = commandschedule.get("month", -1) + testweekday = commandschedule.get("weekday", -1) + + if addmode == "minute": + testfield = commandschedule.get(addmode, -1) + if testfield < 0: + if testtime.minute < 59: + return testtime + datetime.timedelta(minutes=1) + else: + return incrementCommandScheduleTime(commandschedule, testtime.replace(minute=0), "hour") + else: + return incrementCommandScheduleTime(commandschedule, testtime, "hour") + elif addmode == "hour": + testfield = commandschedule.get(addmode, -1) + if testfield < 0: + if testtime.hour < 23: + return testtime + datetime.timedelta(hours=1) + else: + return incrementCommandScheduleTime(commandschedule, testtime.replace(hour=0), "date") + else: + return incrementCommandScheduleTime(commandschedule, testtime, "date") + elif addmode == "date": + testfield = commandschedule.get(addmode, -1) + if testfield < 0: + maxmonthdate = getLastMonthDate(testtime.year, testtime.month) + if testtime.day < maxmonthdate: + return testtime + datetime.timedelta(days=1) + else: + return incrementCommandScheduleTime(commandschedule, testtime.replace(day=1), "month") + else: + return incrementCommandScheduleTime(commandschedule, testtime, "month") + elif addmode == "month": + testfield = commandschedule.get(addmode, -1) + if testfield < 0: + nextmonth = testtime.month + nextyear = testtime.year + while True: + if nextmonth < 12: + nextmonth = nextmonth + 1 + else: + nextmonth = 1 + nextyear = nextyear + 1 + maxmonthdate = getLastMonthDate(nextyear, nextmonth) + if testtime.day <= maxmonthdate: + return testtime.replace(month=nextmonth, year=nextyear) + else: + return incrementCommandScheduleTime(commandschedule, testtime, "year") + else: + # Year + if testtime.month == 2 and testtime.day == 29: + # Leap day handling + nextyear = testtime.year + while True: + nextyear = nextyear + 1 + maxmonthdate = getLastMonthDate(nextyear, testtime.month) + if testtime.day <= maxmonthdate: + return testtime.replace(year=nextyear) + else: + return testtime.replace(year=(testtime.year+1)) + +# Set Next Alarm on RTC +def getNextAlarm(commandschedulelist, prevdatetime): + curtime = datetime.datetime.now() + if prevdatetime > curtime: + return [prevdatetime, -1, -1, -1, -1] + + # Divisible by 4 for leap day + checklimityears = 12 + foundnextcmd = False + nextcommandschedule = {} + # To be sure it's later than any schedule + nextcommandtime = curtime.replace(year=(curtime.year+checklimityears)) + + ctr = 0 + while ctr < len(commandschedulelist): + testcmd = commandschedulelist[ctr].get("cmd", "").lower() + if testcmd == "on": + invaliddata = False + testminute = commandschedulelist[ctr].get("minute", -1) + testhour = commandschedulelist[ctr].get("hour", -1) + testdate = commandschedulelist[ctr].get("date", -1) + testmonth = commandschedulelist[ctr].get("month", -1) + testweekday = commandschedulelist[ctr].get("weekday", -1) + + tmpminute = testminute + tmphour = testhour + tmpdate = testdate + tmpmonth = testmonth + tmpyear = curtime.year + + if tmpminute < 0: + tmpminute = curtime.minute + + if tmphour < 0: + tmphour = curtime.hour + + if tmpdate < 0: + tmpdate = curtime.day + + if tmpmonth < 0: + tmpmonth = curtime.month + + maxmonthdate = getLastMonthDate(tmpyear, tmpmonth) + if tmpdate > maxmonthdate: + # Invalid month date + if testdate < 0: + tmpdate = maxmonthdate + else: + # Date is fixed + if testminute < 0: + tmpminute = 0 + if testhour < 0: + tmphour = 0 + if testmonth < 0 and testdate <= 31: + # Look for next valid month + while tmpdate > maxmonthdate: + if tmpmonth < 12: + tmpmonth = tmpmonth + 1 + else: + tmpmonth = 1 + tmpyear = tmpyear + 1 + maxmonthdate = getLastMonthDate(tmpyear, tmpmonth) + elif tmpdate == 29 and tmpmonth == 2: + # Fixed to leap day + while tmpdate > maxmonthdate: + tmpyear = tmpyear + 1 + maxmonthdate = getLastMonthDate(tmpyear, tmpmonth) + else: + invaliddata = True + if invaliddata == False: + try: + testtime = datetime.datetime(tmpyear, tmpmonth, tmpdate, tmphour, tmpminute) + except: + # Force time diff + testtime = curtime - datetime.timedelta(hours=1) + tmptimediff = (curtime - testtime).total_seconds() + else: + tmptimediff = 0 + + if testweekday >= 0: + # Day of Week check + # python Sunday = 6, RTC Sunday = 0 + weekDay = testtime.weekday() + if weekDay == 6: + weekDay = 0 + else: + weekDay = weekDay + 1 + + + if weekDay != testweekday or tmptimediff > 0: + # Resulting 0-ed time will be <= the testtime + if testminute < 0: + testtime = testtime.replace(minute=0) + if testhour < 0: + testtime = testtime.replace(hour=0) + + dayoffset = testweekday-weekDay + if dayoffset < 0: + dayoffset = dayoffset + 7 + elif dayoffset == 0: + dayoffset = 7 + + testtime = testtime + datetime.timedelta(days=dayoffset) + + # Just look for the next valid weekday; Can be optimized + while checkDateForCommandSchedule(commandschedulelist[ctr], testtime) == False and (testtime.year - curtime.year) < checklimityears: + testtime = testtime + datetime.timedelta(days=7) + + if (testtime.year - curtime.year) >= checklimityears: + # Too many iterations, abort/ignore + tmptimediff = 0 + else: + tmptimediff = (curtime - testtime).total_seconds() + if tmptimediff > 0: + # Find next iteration that's greater than the current time (Day of Week check already handled) + while tmptimediff >= 0: + testtime = incrementCommandScheduleTime(commandschedulelist[ctr], testtime, "minute") + tmptimediff = (curtime - testtime).total_seconds() + + if nextcommandtime > testtime and tmptimediff < 0: + nextcommandschedule = commandschedulelist[ctr] + nextcommandtime = testtime + foundnextcmd = True + + + ctr = ctr + 1 + if foundnextcmd == True: + # Schedule Alarm + # Assume no date,weekday involved just shift the hour and minute accordingly + paramminute = nextcommandschedule.get("minute", -1) + paramhour = nextcommandschedule.get("hour", -1) + if nextcommandschedule.get("weekday", -1) >=0 or nextcommandschedule.get("date", -1) > 0: + # Set alarm based on hour/minute of next occurrence to factor in timezone changes if any + paramminute = nextcommandtime.minute + paramhour = nextcommandtime.hour + weekday, caldate, hour, minute = getRTCAlarm(nextcommandschedule.get("weekday", -1), nextcommandschedule.get("date", -1), paramhour, paramminute) + return [nextcommandtime, weekday, caldate, hour, minute] + + # This will ensure that this will be replaced next iteration + return [curtime, -1, -1, -1, -1] + diff --git a/source/scripts/argonstatus.py b/source/scripts/argonstatus.py new file mode 100644 index 0000000..843f0a8 --- /dev/null +++ b/source/scripts/argonstatus.py @@ -0,0 +1,172 @@ +#!/usr/bin/python3 + +import sys +import os + +sys.path.append("/etc/argon/") +from argonsysinfo import * +from argonregister import * +from argononed import * + +def getFahrenheit(celsiustemp): + try: + return (32+9*(celsiustemp)/5) + except: + return 0 + + +temperature="C" +tmpconfig=load_unitconfig(UNIT_CONFIGFILE) +if "temperature" in tmpconfig: + temperature = tmpconfig["temperature"] + +baseleftoffset = "" + +stdleftoffset = " " + +#if len(sys.argv) > 2: +# baseleftoffset = stdleftoffset +baseleftoffset = stdleftoffset + +argctr = 1 +while argctr < len(sys.argv): + cmd = sys.argv[argctr].lower() + argctr = argctr + 1 + if baseleftoffset != "": + print(cmd.upper(),"INFORMATION:") + if cmd == "cpu usage": + # CPU Usage + curlist = argonsysinfo_listcpuusage() + + while len(curlist) > 0: + curline = "" + tmpitem = curlist.pop(0) + curline = tmpitem["title"]+": "+str(tmpitem["value"])+"%" + print(baseleftoffset+curline) + elif cmd == "storage": + # Storage Info + curlist = [] + try: + tmpobj = argonsysinfo_listhddusage() + for curdev in tmpobj: + curlist.append({"title": curdev, "value": argonsysinfo_kbstr(tmpobj[curdev]['total']), "usage": int(100*tmpobj[curdev]['used']/tmpobj[curdev]['total']) }) + #curlist = argonsysinfo_liststoragetotal() + except Exception: + curlist = [] + + while len(curlist) > 0: + tmpitem = curlist.pop(0) + # Right column first, safer to overwrite white space + print(baseleftoffset+tmpitem["title"], str(tmpitem["usage"])+"%","used of", tmpitem["value"]) + + elif cmd == "raid": + # Raid Info + curlist = [] + try: + tmpobj = argonsysinfo_listraid() + curlist = tmpobj['raidlist'] + except Exception: + curlist = [] + + if len(curlist) > 0: + tmpitem = curlist.pop(0) + print(baseleftoffset+tmpitem["title"], tmpitem["value"], argonsysinfo_kbstr(tmpitem["info"]["size"])) + + if len(tmpitem['info']['state']) > 0: + print(baseleftoffset+stdleftoffset,tmpitem['info']['state']) + + if len(tmpitem['info']['rebuildstat']) > 0: + print(baseleftoffset+stdleftoffset,"Rebuild:" + tmpitem['info']['rebuildstat']) + + + print(baseleftoffset+stdleftoffset,"Active:"+str(int(tmpitem["info"]["active"]))+"/"+str(int(tmpitem["info"]["devices"]))) + print(baseleftoffset+stdleftoffset,"Working:"+str(int(tmpitem["info"]["working"]))+"/"+str(int(tmpitem["info"]["devices"]))) + print(baseleftoffset+stdleftoffset,"Failed:"+str(int(tmpitem["info"]["failed"]))+"/"+str(int(tmpitem["info"]["devices"]))) + else: + print(baseleftoffset+stdleftoffset,"N/A") + + elif cmd == "ram": + # RAM + try: + tmpraminfo = argonsysinfo_getram() + print(baseleftoffset+tmpraminfo[0],"of", tmpraminfo[1]) + except Exception: + pass + + elif cmd == "temperature": + # Temp + try: + hddtempctr = 0 + maxcval = 0 + mincval = 200 + + alltempobj = {"cpu": argonsysinfo_getcputemp()} + # Get min/max of hdd temp + hddtempobj = argonsysinfo_gethddtemp() + for curdev in hddtempobj: + alltempobj[curdev] = hddtempobj[curdev] + if hddtempobj[curdev] < mincval: + mincval = hddtempobj[curdev] + if hddtempobj[curdev] > maxcval: + maxcval = hddtempobj[curdev] + hddtempctr = hddtempctr + 1 + + if hddtempctr > 0: + alltempobj["hdd min"]=mincval + alltempobj["hdd max"]=maxcval + + for curdev in alltempobj: + if temperature == "C": + # Celsius + tmpstr = str(alltempobj[curdev]) + if len(tmpstr) > 4: + tmpstr = tmpstr[0:4] + else: + # Fahrenheit + tmpstr = str(getFahrenheit(alltempobj[curdev])) + if len(tmpstr) > 5: + tmpstr = tmpstr[0:5] + print(baseleftoffset+curdev.upper()+": "+ tmpstr+ chr(176) +temperature) + + except Exception: + pass + elif cmd == "ip": + # IP Address + try: + print(baseleftoffset+argonsysinfo_getip()) + except Exception: + pass + elif cmd == "fan speed": + # Fan Speed + try: + newspeed = argonregister_getfanspeed(argonregister_initializebusobj()) + if newspeed <= 0: + fanconfig = load_fancpuconfig() + fanhddconfig = load_fanhddconfig() + + # Speed based on CPU Temp + val = argonsysinfo_getcputemp() + newspeed = get_fanspeed(val, fanconfig) + + val = argonsysinfo_getmaxhddtemp() + tmpspeed = get_fanspeed(val, fanhddconfig) + if tmpspeed > newspeed: + newspeed = tmpspeed + print(baseleftoffset+"Fan Speed",str(newspeed)) + except Exception: + pass + elif cmd == "fan configuration": + fanconfig = load_fancpuconfig() + fanhddconfig = load_fanhddconfig() + + if len(fanhddconfig) > 0: + print(baseleftoffset+"Fan Temp-Speed cut-offs") + for curconfig in fanconfig: + print(baseleftoffset+stdleftoffset,curconfig) + + if len(fanhddconfig) > 0: + print(baseleftoffset+"HDD Temp-Speed cut-offs") + for curconfig in fanhddconfig: + print(baseleftoffset+stdleftoffset,curconfig) + + diff --git a/source/scripts/argonsysinfo.py b/source/scripts/argonsysinfo.py new file mode 100644 index 0000000..102f2e5 --- /dev/null +++ b/source/scripts/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/source/tools/setntpserver.sh b/source/tools/setntpserver.sh new file mode 100644 index 0000000..1baf7f9 --- /dev/null +++ b/source/tools/setntpserver.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +NTPSERVER="time.google.com" +TMPCONFIG=/dev/shm/tmpconfig.conf + + +# timesyncd +CONFIG=/etc/systemd/timesyncd.conf +if [ -f "$CONFIG" ] +then + cat "$CONFIG" | grep -v -e 'NTP=' > "$TMPCONFIG" + echo "NTP=$NTPSERVER" >> "$TMPCONFIG" + + sudo chown root:root "$TMPCONFIG" + sudo chmod 644 "$TMPCONFIG" + sudo mv "$TMPCONFIG" "$CONFIG" + + # /usr/sbin/ntpd + + sudo service systemd-timesyncd restart > /dev/null 2>&1 +fi + + +for CURSERVICECONFIG in ntp chrony +do + CONFIG=/etc/${CURSERVICECONFIG}.conf + if [ -f "$CONFIG" ] + then + cat "$CONFIG" | grep -v -e 'pool ' > "$TMPCONFIG" + #echo "server $NTPSERVER" >> "$TMPCONFIG" + echo "pool time1.google.com iburst" >> "$TMPCONFIG" + echo "pool time2.google.com iburst" >> "$TMPCONFIG" + echo "pool time3.google.com iburst" >> "$TMPCONFIG" + echo "pool time4.google.com iburst" >> "$TMPCONFIG" + + sudo chown root:root "$TMPCONFIG" + sudo chmod 644 "$TMPCONFIG" + sudo mv "$TMPCONFIG" "$CONFIG" + + sudo service ${CURSERVICECONFIG} restart > /dev/null 2>&1 + fi +done