mirror of
https://github.com/okunze/Argon40-ArgonOne-Script.git
synced 2026-03-26 00:48:35 +01:00
Automated Change by GitHub Action
This commit is contained in:
committed by
github-actions[bot]
parent
d018932082
commit
57498e55cd
576
source/scripts/argon-rpi-eeprom-config-default.py
Normal file
576
source/scripts/argon-rpi-eeprom-config-default.py
Normal file
@@ -0,0 +1,576 @@
|
||||
#!/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 NVMe boot priority etc if not yet set
|
||||
foundnewsetting = 0
|
||||
addsetting="\nBOOT_UART=1\nWAKE_ON_GPIO=0\nPOWER_OFF_ON_HALT=1\nBOOT_ORDER=0xf416\nPCIE_PROBE=1"
|
||||
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] == "BOOT_UART":
|
||||
newsetting = "BOOT_UART=1"
|
||||
elif current_config_pair[0] == "WAKE_ON_GPIO":
|
||||
newsetting = "WAKE_ON_GPIO=0"
|
||||
elif current_config_pair[0] == "POWER_OFF_ON_HALT":
|
||||
newsetting = "POWER_OFF_ON_HALT=1"
|
||||
elif current_config_pair[0] == "BOOT_ORDER":
|
||||
newsetting = "BOOT_ORDER=0xf416"
|
||||
elif current_config_pair[0] == "PCIE_PROBE":
|
||||
newsetting = "PCIE_PROBE=1"
|
||||
|
||||
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()
|
||||
824
source/scripts/argonkeyboard.py
Normal file
824
source/scripts/argonkeyboard.py
Normal file
@@ -0,0 +1,824 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
#
|
||||
# This script monitor battery via ic2 and keyboard events.
|
||||
#
|
||||
# Additional comments are found in each function below
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
from evdev import InputDevice, categorize, ecodes, list_devices
|
||||
from select import select
|
||||
|
||||
import subprocess
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
from threading import Thread
|
||||
from queue import Queue
|
||||
|
||||
|
||||
UPS_LOGFILE="/dev/shm/upslog.txt"
|
||||
KEYBOARD_LOCKFILE="/dev/shm/argononeupkeyboardlock.txt"
|
||||
|
||||
|
||||
KEYCODE_BRIGHTNESSUP = "KEY_BRIGHTNESSUP"
|
||||
KEYCODE_BRIGHTNESSDOWN = "KEY_BRIGHTNESSDOWN"
|
||||
KEYCODE_VOLUMEUP = "KEY_VOLUMEUP"
|
||||
KEYCODE_VOLUMEDOWN = "KEY_VOLUMEDOWN"
|
||||
KEYCODE_PAUSE = "KEY_PAUSE"
|
||||
KEYCODE_MUTE = "KEY_MUTE"
|
||||
|
||||
|
||||
###################
|
||||
# Utilty Functions
|
||||
###################
|
||||
|
||||
# Debug Logger
|
||||
def debuglog(typestr, logstr):
|
||||
return
|
||||
# try:
|
||||
# DEBUGFILE="/dev/shm/argononeupkeyboarddebuglog.txt"
|
||||
# tmpstrpadding = " "
|
||||
|
||||
# with open(DEBUGFILE, "a") as txt_file:
|
||||
# txt_file.write("["+time.asctime(time.localtime(time.time()))+"] "+typestr.upper()+" "+logstr.strip().replace("\n","\n"+tmpstrpadding)+"\n")
|
||||
# except:
|
||||
# pass
|
||||
|
||||
def runcmdlist(key, cmdlist):
|
||||
try:
|
||||
cmdresult = subprocess.run(cmdlist,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
#debuglog(key+"-result-output",str(cmdresult.stdout))
|
||||
if cmdresult.stderr:
|
||||
debuglog(key+"-result-error",str(cmdresult.stderr))
|
||||
#debuglog(key+"-result-code",str(cmdresult.returncode))
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
debuglog(key+"-error-output",str(e.stdout))
|
||||
if e.stderr:
|
||||
debuglog(key+"-error-error",str(e.stderr))
|
||||
debuglog(key+"-error-code",str(e.returncode))
|
||||
except FileNotFoundError:
|
||||
debuglog(key+"-error-filenotfound","Command Not Found")
|
||||
except Exception as othererr:
|
||||
try:
|
||||
debuglog(key+"-error-other", str(othererr))
|
||||
except:
|
||||
debuglog(key+"-error-other", "Other Error")
|
||||
|
||||
def createlockfile(fname):
|
||||
# try:
|
||||
# if os.path.isfile(fname):
|
||||
# return True
|
||||
# except Exception as checklockerror:
|
||||
# try:
|
||||
# debuglog("keyboard-lock-error", str(checklockerror))
|
||||
# except:
|
||||
# debuglog("keyboard-lock-error", "Error Checking Lock File")
|
||||
# try:
|
||||
# with open(fname, "w") as txt_file:
|
||||
# txt_file.write(time.asctime(time.localtime(time.time()))+"\n")
|
||||
# except Exception as lockerror:
|
||||
# try:
|
||||
# debuglog("keyboard-lock-error", str(lockerror))
|
||||
# except:
|
||||
# debuglog("keyboard-lock-error", "Error Creating Lock File")
|
||||
return False
|
||||
|
||||
def deletelockfile(fname):
|
||||
# try:
|
||||
# os.remove(fname)
|
||||
# except Exception as lockerror:
|
||||
# try:
|
||||
# debuglog("keyboard-lock-error", str(lockerror))
|
||||
# except:
|
||||
# debuglog("keyboard-lock-error", "Error Removing Lock File")
|
||||
return True
|
||||
|
||||
|
||||
# System Notifcation
|
||||
def notifymessage(message, iscritical):
|
||||
if not isinstance(message, str) or len(message.strip()) == 0:
|
||||
return
|
||||
|
||||
wftype="notify"
|
||||
if iscritical:
|
||||
wftype="critical"
|
||||
os.system("export SUDO_UID=1000; wfpanelctl "+wftype+" \""+message+"\"")
|
||||
os.system("export DISPLAY=:0.0; lxpanelctl notify \""+message+"\"")
|
||||
|
||||
|
||||
#############
|
||||
# Battery (copied)
|
||||
#############
|
||||
|
||||
def battery_loadlogdata():
|
||||
# status, version, time, schedule
|
||||
outobj = {}
|
||||
try:
|
||||
fp = open(UPS_LOGFILE, "r")
|
||||
logdata = fp.read()
|
||||
alllines = logdata.split("\n")
|
||||
ctr = 0
|
||||
while ctr < len(alllines):
|
||||
tmpval = alllines[ctr].strip()
|
||||
curinfo = tmpval.split(":")
|
||||
if len(curinfo) > 1:
|
||||
tmpattrib = curinfo[0].lower().split(" ")
|
||||
# The rest are assumed to be value
|
||||
outobj[tmpattrib[0]] = tmpval[(len(curinfo[0])+1):].strip()
|
||||
ctr = ctr + 1
|
||||
except Exception as einit:
|
||||
try:
|
||||
debuglog("keyboard-battery-error", str(einit))
|
||||
except:
|
||||
debuglog("keyboard-battery-error", "Error getting battery status")
|
||||
#pass
|
||||
|
||||
return outobj
|
||||
|
||||
|
||||
def keyboardevent_getdevicepaths():
|
||||
outlist = []
|
||||
try:
|
||||
for path in list_devices():
|
||||
try:
|
||||
tmpdevice = InputDevice(path)
|
||||
keyeventlist = tmpdevice.capabilities().get(ecodes.EV_KEY, [])
|
||||
# Keyboard has EV_KEY (key) and EV_REP (autorepeat)
|
||||
if ecodes.KEY_BRIGHTNESSDOWN in keyeventlist and ecodes.KEY_BRIGHTNESSDOWN in keyeventlist:
|
||||
outlist.append(path)
|
||||
#debuglog("keyboard-device-keys", path)
|
||||
#debuglog("keyboard-device-keys", str(keyeventlist))
|
||||
elif ecodes.KEY_F2 in keyeventlist and ecodes.KEY_F3 in keyeventlist:
|
||||
# Keyboards with FN key sometimes do not include KEY_BRIGHTNESS in declaration
|
||||
outlist.append(path)
|
||||
#debuglog("keyboard-device-keys", path)
|
||||
#debuglog("keyboard-device-keys", str(keyeventlist))
|
||||
tmpdevice.close()
|
||||
except:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
return outlist
|
||||
|
||||
def keyboardevent_devicechanged(curlist, newlist):
|
||||
try:
|
||||
for curpath in curlist:
|
||||
if curpath not in newlist:
|
||||
return True
|
||||
for newpath in newlist:
|
||||
if newpath not in curlist:
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
def keyboardevent_getbrigthnesstoolid():
|
||||
toolid = 0
|
||||
try:
|
||||
output = subprocess.check_output(["ddcutil", "--version"], text=True, stderr=subprocess.DEVNULL)
|
||||
lines = output.splitlines()
|
||||
if len(lines) > 0:
|
||||
tmpline = lines[0].strip()
|
||||
toolid = int(tmpline.split(" ")[1].split(".")[0])
|
||||
except Exception as einit:
|
||||
try:
|
||||
debuglog("keyboard-brightness-tool-error", str(einit))
|
||||
except:
|
||||
debuglog("keyboard-brightness-tool-error", "Error getting tool id value")
|
||||
|
||||
debuglog("keyboard-brightness-tool", toolid)
|
||||
return toolid
|
||||
|
||||
def keyboardevent_getbrigthnessinfo(toolid, defaultlevel=50):
|
||||
level = defaultlevel
|
||||
try:
|
||||
# VCP code x10(Brightness ): current value = 90, max value = 100
|
||||
if toolid > 1:
|
||||
# Disabled dynamic sleep "--disable-dynamic-sleep", "--sleep-multiplier", "0.1"
|
||||
output = subprocess.check_output(["ddcutil", "--skip-ddc-checks", "--disable-dynamic-sleep", "--sleep-multiplier", "0.1", "getvcp", "10"], text=True, stderr=subprocess.DEVNULL)
|
||||
else:
|
||||
output = subprocess.check_output(["ddcutil", "--sleep-multiplier", "0.1", "getvcp", "10"], text=True, stderr=subprocess.DEVNULL)
|
||||
debuglog("keyboard-brightness-info", output)
|
||||
level = int(output.split(":")[-1].split(",")[0].split("=")[-1].strip())
|
||||
except Exception as einit:
|
||||
try:
|
||||
debuglog("keyboard-brightness-error", str(einit))
|
||||
except:
|
||||
debuglog("keyboard-brightness-error", "Error getting base value")
|
||||
|
||||
|
||||
return {
|
||||
"level": level
|
||||
}
|
||||
|
||||
|
||||
def keyboardevent_adjustbrigthness(toolid, baselevel, adjustval=5):
|
||||
curlevel = baselevel
|
||||
if adjustval == 0:
|
||||
return {
|
||||
"level": baselevel
|
||||
}
|
||||
|
||||
# Moved reading because ddcutil has delay
|
||||
# try:
|
||||
# tmpobj = keyboardevent_getbrigthnessinfo(toolid, curlevel)
|
||||
# curlevel = tmpobj["level"]
|
||||
# except Exception:
|
||||
# pass
|
||||
|
||||
tmpval = max(10, min(100, curlevel + adjustval))
|
||||
if tmpval != curlevel:
|
||||
try:
|
||||
debuglog("keyboard-brightness", str(curlevel)+"% to "+str(tmpval)+"%")
|
||||
if toolid > 1:
|
||||
# Disabled dynamic sleep "--disable-dynamic-sleep", "--sleep-multiplier", "0.1"
|
||||
runcmdlist("brightness", ["ddcutil", "--skip-ddc-checks", "--disable-dynamic-sleep", "--sleep-multiplier", "0.1", "setvcp", "10", str(tmpval)])
|
||||
else:
|
||||
runcmdlist("brightness", ["ddcutil", "--sleep-multiplier", "0.1", "setvcp", "10", str(tmpval)])
|
||||
notifymessage("Brightness: "+str(tmpval)+"%", False)
|
||||
except Exception as adjusterr:
|
||||
try:
|
||||
debuglog("keyboard-brightness-error", str(adjusterr))
|
||||
except:
|
||||
debuglog("keyboard-brightness-error", "Error adjusting value")
|
||||
return {
|
||||
"level": curlevel
|
||||
}
|
||||
|
||||
# DEBUG: Checking
|
||||
#keyboardevent_getbrigthnessinfo(toolid, tmpval)
|
||||
return {
|
||||
"level": tmpval
|
||||
}
|
||||
|
||||
|
||||
def keyboardevent_getvolumesinkid(usedefault=True):
|
||||
if usedefault == True:
|
||||
return "@DEFAULT_SINK@"
|
||||
cursinkid = 0
|
||||
try:
|
||||
output = subprocess.check_output(["wpctl", "status"], text=True, encoding='utf-8', stderr=subprocess.DEVNULL)
|
||||
|
||||
# Find Audio section
|
||||
tmpline = ""
|
||||
foundidx = 0
|
||||
lines = output.splitlines()
|
||||
lineidx = 0
|
||||
while lineidx < len(lines):
|
||||
tmpline = lines[lineidx].strip()
|
||||
if tmpline == "Audio":
|
||||
foundidx = lineidx
|
||||
break
|
||||
lineidx = lineidx + 1
|
||||
|
||||
if foundidx < 1:
|
||||
return 0
|
||||
|
||||
# Find Sinks section
|
||||
foundidx = 0
|
||||
lineidx = lineidx + 1
|
||||
while lineidx < len(lines):
|
||||
if "Sinks:" in lines[lineidx]:
|
||||
foundidx = lineidx
|
||||
break
|
||||
elif len(lines[lineidx]) < 1:
|
||||
break
|
||||
lineidx = lineidx + 1
|
||||
|
||||
if foundidx < 1:
|
||||
return 0
|
||||
|
||||
# Get find default id, or first id
|
||||
lineidx = lineidx + 1
|
||||
while lineidx < len(lines):
|
||||
if "vol:" in lines[lineidx] and "." in lines[lineidx]:
|
||||
tmpstr = lines[lineidx].split(".")[0]
|
||||
tmplist = tmpstr.split()
|
||||
if len(tmplist) > 1:
|
||||
if tmplist[len(tmplist)-2] == "*":
|
||||
return int(tmplist[len(tmplist)-1])
|
||||
if len(tmplist) > 0 and cursinkid < 1:
|
||||
cursinkid = int(tmplist[len(tmplist)-1])
|
||||
elif len(lines[lineidx]) < 3:
|
||||
break
|
||||
lineidx = lineidx + 1
|
||||
except Exception as einit:
|
||||
try:
|
||||
debuglog("keyboard-volume-error", str(einit))
|
||||
except:
|
||||
debuglog("keyboard-volume-error", "Error getting device ID")
|
||||
|
||||
return cursinkid
|
||||
|
||||
|
||||
def keyboardevent_getvolumeinfo(deviceidstr="", defaultlevel=50, defaultmuted=0):
|
||||
muted = defaultmuted
|
||||
level = defaultlevel
|
||||
try:
|
||||
if deviceidstr == "":
|
||||
audioidstr = str(keyboardevent_getvolumesinkid())
|
||||
if audioidstr == "0":
|
||||
debuglog("keyboard-volume-error", "Error getting device id")
|
||||
return {
|
||||
"level": defaultmuted,
|
||||
"muted": defaultlevel
|
||||
}
|
||||
|
||||
deviceidstr = audioidstr
|
||||
|
||||
output = subprocess.check_output(["wpctl", "get-volume", deviceidstr], text=True, stderr=subprocess.DEVNULL)
|
||||
debuglog("keyboard-volume-info", output)
|
||||
|
||||
muted = 0
|
||||
level = 0
|
||||
# Parse output, examples
|
||||
# Volume: 0.65
|
||||
# Volume: 0.55 [MUTED]
|
||||
outlist = output.split()
|
||||
if len(outlist) > 0:
|
||||
# Get last element
|
||||
tmpstr = outlist[len(outlist)-1]
|
||||
# Check if muted
|
||||
if "MUTE" in tmpstr:
|
||||
muted = 1
|
||||
if len(outlist) > 1:
|
||||
tmpstr = outlist[len(outlist)-2]
|
||||
if tmpstr.endswith("%"):
|
||||
# Level 100% to 0%
|
||||
level = int(float(tmpstr[:-1]))
|
||||
elif tmpstr.replace('.', '').isdigit():
|
||||
# Level 1.00 to 0.00
|
||||
level = int(float(tmpstr) * 100.0)
|
||||
except Exception as einit:
|
||||
try:
|
||||
debuglog("keyboard-volume-error", str(einit))
|
||||
except:
|
||||
debuglog("keyboard-volume-error", "Error getting base value")
|
||||
return {
|
||||
"level": defaultmuted,
|
||||
"muted": defaultlevel
|
||||
}
|
||||
|
||||
#debuglog("keyboard-volume-get", str(level)+"% Mute:"+str(muted))
|
||||
|
||||
return {
|
||||
"level": level,
|
||||
"muted": muted
|
||||
}
|
||||
|
||||
|
||||
def keyboardevent_adjustvolume(baselevel, basemuted, adjustval=5):
|
||||
curlevel = baselevel
|
||||
curmuted = basemuted
|
||||
needsnotification = False
|
||||
|
||||
deviceidstr = str(keyboardevent_getvolumesinkid())
|
||||
if deviceidstr == "0":
|
||||
debuglog("keyboard-volume-error", "Error getting device id")
|
||||
return {
|
||||
"level": baselevel,
|
||||
"muted": basemuted
|
||||
}
|
||||
|
||||
# try:
|
||||
# tmpobj = keyboardevent_getvolumeinfo(deviceidstr, curlevel, curmuted)
|
||||
# curlevel = tmpobj["level"]
|
||||
# curmuted = tmpobj["muted"]
|
||||
# except Exception:
|
||||
# pass
|
||||
|
||||
tmpmuted = curmuted
|
||||
if adjustval == 0:
|
||||
# Toggle Mute
|
||||
if curmuted == 0:
|
||||
tmpmuted = 1
|
||||
else:
|
||||
tmpmuted = 0
|
||||
|
||||
tmpval = max(10, min(100, curlevel + adjustval))
|
||||
if tmpval != curlevel:
|
||||
try:
|
||||
debuglog("keyboard-volume", str(curlevel)+"% to "+str(tmpval)+"%")
|
||||
runcmdlist("volume", ["wpctl", "set-volume", deviceidstr, f"{tmpval}%"])
|
||||
needsnotification = True
|
||||
tmpmuted = 0
|
||||
except Exception as adjusterr:
|
||||
try:
|
||||
debuglog("keyboard-volume-error", str(adjusterr))
|
||||
except:
|
||||
debuglog("keyboard-volume-error", "Error adjusting value")
|
||||
return {
|
||||
"level": curlevel,
|
||||
"muted": curmuted
|
||||
}
|
||||
elif adjustval != 0:
|
||||
# To unmute even if no volume level change
|
||||
tmpmuted = 0
|
||||
|
||||
if tmpmuted != curmuted:
|
||||
try:
|
||||
debuglog("keyboard-mute", str(tmpmuted))
|
||||
runcmdlist("mute", ["wpctl", "set-mute", deviceidstr, str(tmpmuted)])
|
||||
needsnotification = True
|
||||
except Exception as adjusterr:
|
||||
try:
|
||||
debuglog("keyboard-mute-error", str(adjusterr))
|
||||
except:
|
||||
debuglog("keyboard-mute-error", "Error adjusting value")
|
||||
return {
|
||||
"level": tmpval,
|
||||
"muted": curmuted
|
||||
}
|
||||
#if tmpmuted == 1:
|
||||
# notifymessage("Volume: Muted", False)
|
||||
#else:
|
||||
# notifymessage("Volume: "+str(tmpval)+"%", False)
|
||||
|
||||
# DEBUG: Checking
|
||||
#keyboardevent_getvolumeinfo(deviceidstr, tmpval, tmpmuted)
|
||||
|
||||
return {
|
||||
"level": tmpval,
|
||||
"muted": tmpmuted
|
||||
}
|
||||
|
||||
def keyboard_getlayoutfieldvalue(tmpval):
|
||||
debuglog("keyboard-layout-lang", tmpval)
|
||||
if "us" in tmpval:
|
||||
debuglog("keyboard-layout-langout", "us")
|
||||
return "us"
|
||||
debuglog("keyboard-layout-langout", "gb")
|
||||
return "gb" # uk, gb, etc
|
||||
#return tmpval
|
||||
|
||||
|
||||
def keyboard_getdevicefw(kbdevice):
|
||||
# info: vendor 0x6080=24704, product 0x8062=32866
|
||||
try:
|
||||
if kbdevice.info.vendor == 24704 and kbdevice.info.product == 32866:
|
||||
# Special HID
|
||||
return "314"
|
||||
except Exception as infoerr:
|
||||
pass
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
def keyboardevemt_keyhandler(readq):
|
||||
|
||||
ADJUSTTYPE_NONE=0
|
||||
ADJUSTTYPE_BRIGHTNESS=1
|
||||
ADJUSTTYPE_VOLUME=2
|
||||
ADJUSTTYPE_MUTE=3
|
||||
ADJUSTTYPE_BATTERYINFO=4
|
||||
|
||||
DATAREFRESHINTERVALSEC = 10
|
||||
|
||||
PRESSWAITINTERVALSEC = 0.5
|
||||
FIRSTHOLDINTERVALSEC = 0.5
|
||||
HOLDWAITINTERVALSEC = 0.5
|
||||
|
||||
|
||||
# Get current levels
|
||||
volumetime = time.time()
|
||||
curvolumemuted = 0
|
||||
curvolume = 50
|
||||
|
||||
brightnesstime = volumetime
|
||||
curbrightness = 50
|
||||
brightnesstoolid = 0
|
||||
|
||||
try:
|
||||
brightnesstoolid = keyboardevent_getbrigthnesstoolid()
|
||||
except Exception:
|
||||
brightnesstoolid = 0
|
||||
pass
|
||||
|
||||
try:
|
||||
tmpobj = keyboardevent_getbrigthnessinfo(brightnesstoolid)
|
||||
curbrightness = tmpobj["level"]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
tmpobj = keyboardevent_getvolumeinfo()
|
||||
curvolumemuted = tmpobj["muted"]
|
||||
curvolume = tmpobj["level"]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
while True:
|
||||
try:
|
||||
tmpkeymode = 0
|
||||
tmpkeycode = ""
|
||||
adjustval = 0
|
||||
adjusttype = ADJUSTTYPE_NONE
|
||||
|
||||
tmpcode = readq.get() # Blocking
|
||||
try:
|
||||
codeelements = tmpcode.split("+")
|
||||
if len(codeelements) == 2:
|
||||
if codeelements[0] == "PRESS":
|
||||
tmpkeymode = 1
|
||||
else:
|
||||
tmpkeymode = 2
|
||||
tmpkeycode = codeelements[1]
|
||||
elif tmpcode == "EXIT":
|
||||
readq.task_done()
|
||||
return
|
||||
|
||||
except Exception:
|
||||
tmpkeycode = ""
|
||||
tmpkeymode = 0
|
||||
pass
|
||||
tmptime = time.time()
|
||||
if tmpkeycode in [KEYCODE_BRIGHTNESSDOWN, KEYCODE_BRIGHTNESSUP]:
|
||||
if tmpkeymode == 1 and tmptime - brightnesstime > DATAREFRESHINTERVALSEC:
|
||||
# Do not update value during hold
|
||||
try:
|
||||
tmpobj = keyboardevent_getbrigthnessinfo(brightnesstoolid)
|
||||
curbrightness = tmpobj["level"]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
adjusttype = ADJUSTTYPE_BRIGHTNESS
|
||||
if tmpkeycode == KEYCODE_BRIGHTNESSDOWN:
|
||||
adjustval = -5*tmpkeymode
|
||||
else:
|
||||
adjustval = 5*tmpkeymode
|
||||
brightnesstime = tmptime
|
||||
elif tmpkeycode in [KEYCODE_MUTE, KEYCODE_VOLUMEDOWN, KEYCODE_VOLUMEUP]:
|
||||
if tmpkeymode == 1 and tmptime - volumetime > DATAREFRESHINTERVALSEC and tmpkeymode == 1:
|
||||
# Do not update value during hold
|
||||
try:
|
||||
tmpobj = keyboardevent_getvolumeinfo()
|
||||
curvolumemuted = tmpobj["muted"]
|
||||
curvolume = tmpobj["level"]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if tmpkeycode == KEYCODE_MUTE:
|
||||
adjusttype = ADJUSTTYPE_MUTE
|
||||
adjustval = 0
|
||||
else:
|
||||
adjusttype = ADJUSTTYPE_VOLUME
|
||||
if tmpkeycode == KEYCODE_VOLUMEDOWN:
|
||||
adjustval = -5*tmpkeymode
|
||||
else:
|
||||
adjustval = 5*tmpkeymode
|
||||
volumetime = tmptime
|
||||
|
||||
elif tmpkeycode == KEYCODE_PAUSE:
|
||||
adjusttype = ADJUSTTYPE_BATTERYINFO
|
||||
else:
|
||||
readq.task_done()
|
||||
continue
|
||||
|
||||
try:
|
||||
tmplockfilea = KEYBOARD_LOCKFILE+".a"
|
||||
if createlockfile(tmplockfilea) == False:
|
||||
# Debug ONLY
|
||||
# if tmpkeymode == 1:
|
||||
# debuglog("keyboard-event", "Press Key Code: "+str(tmpkeycode))
|
||||
# else:
|
||||
# debuglog("keyboard-event", "Hold Key Code: "+str(tmpkeycode))
|
||||
|
||||
if adjusttype == ADJUSTTYPE_BRIGHTNESS:
|
||||
try:
|
||||
tmpobj = keyboardevent_adjustbrigthness(brightnesstoolid, curbrightness, adjustval)
|
||||
curbrightness = tmpobj["level"]
|
||||
except Exception as brightnesserr:
|
||||
try:
|
||||
debuglog("keyboard-brightnessother-error", str(brightnesserr))
|
||||
except:
|
||||
debuglog("keyboard-brightnessother-error", "Error adjusting value")
|
||||
pass
|
||||
elif adjusttype == ADJUSTTYPE_VOLUME or adjusttype == ADJUSTTYPE_MUTE:
|
||||
try:
|
||||
tmpobj = keyboardevent_adjustvolume(curvolume, curvolumemuted, adjustval)
|
||||
curvolumemuted = tmpobj["muted"]
|
||||
curvolume = tmpobj["level"]
|
||||
except Exception as volumeerr:
|
||||
try:
|
||||
debuglog("keyboard-volumeother-error", str(volumeerr))
|
||||
except:
|
||||
debuglog("keyboard-volumeother-error", "Error adjusting value")
|
||||
pass
|
||||
elif adjusttype == ADJUSTTYPE_BATTERYINFO:
|
||||
outobj = battery_loadlogdata()
|
||||
try:
|
||||
notifymessage(outobj["power"], False)
|
||||
except:
|
||||
pass
|
||||
deletelockfile(tmplockfilea)
|
||||
|
||||
|
||||
except Exception as keyhandlererr:
|
||||
try:
|
||||
debuglog("keyboard-handlererror", str(keyhandleerr))
|
||||
except:
|
||||
debuglog("keyboard-handlererror", "Error")
|
||||
|
||||
readq.task_done()
|
||||
|
||||
except Exception as mainerr:
|
||||
time.sleep(10)
|
||||
# While True
|
||||
|
||||
|
||||
def keyboardevent_monitor(writeq):
|
||||
|
||||
READTIMEOUTSECS = 1.0
|
||||
|
||||
FIRSTHOLDINTERVALSEC = 0.5
|
||||
HOLDWAITINTERVALSEC = 0.5
|
||||
|
||||
while True:
|
||||
try:
|
||||
keypresstimestamp = {}
|
||||
keyholdtimestamp = {}
|
||||
# Get Devices
|
||||
devicelist = []
|
||||
devicefdlist = []
|
||||
devicepathlist = keyboardevent_getdevicepaths()
|
||||
devicefwlist = []
|
||||
|
||||
deviceidx = 0
|
||||
while deviceidx < len(devicepathlist):
|
||||
try:
|
||||
tmpdevice = InputDevice(devicepathlist[deviceidx])
|
||||
devicelist.append(tmpdevice)
|
||||
devicefdlist.append(tmpdevice.fd)
|
||||
devicefwlist.append(keyboard_getdevicefw(tmpdevice))
|
||||
#debuglog("keyboard-device-info", devicepathlist[deviceidx])
|
||||
#debuglog("keyboard-device-info", str(tmpdevice.info))
|
||||
except Exception as deverr:
|
||||
try:
|
||||
debuglog("keyboard-deviceerror", str(deverr)+ " "+ devicepathlist[deviceidx])
|
||||
except:
|
||||
debuglog("keyboard-deviceerror", "Error "+devicepathlist[deviceidx])
|
||||
deviceidx = deviceidx + 1
|
||||
|
||||
try:
|
||||
debuglog("keyboard-update", str(len(devicefdlist))+" Devices")
|
||||
while len(devicefdlist) > 0:
|
||||
# Exception when one of the devices gets removed
|
||||
# Wait for events on any registered device
|
||||
r, w, x = select(devicefdlist, [], [], READTIMEOUTSECS)
|
||||
for fd in r:
|
||||
found = False
|
||||
curdevicefw = ""
|
||||
deviceidx = 0
|
||||
while deviceidx < len(devicefdlist):
|
||||
if devicefdlist[deviceidx] == fd:
|
||||
curdevicefw = devicefwlist[deviceidx]
|
||||
found = True
|
||||
break
|
||||
deviceidx = deviceidx + 1
|
||||
if found:
|
||||
for event in devicelist[deviceidx].read():
|
||||
try:
|
||||
# Process the event
|
||||
#print("Device: "+devicelist[deviceidx].path+", Event: ", event)
|
||||
#debuglog("keyboard-event", "Device: "+devicelist[deviceidx].path+", Event: "+str(event))
|
||||
if event.type == ecodes.EV_KEY:
|
||||
key_event = categorize(event)
|
||||
keycodelist = []
|
||||
# 2 hold, 0 release, 1 press
|
||||
if event.value == 2 or event.value == 1:
|
||||
#debuglog("keyboard-event", "Mode:"+str(event.value)+" Key Code: "+str(key_event.keycode))
|
||||
|
||||
if isinstance(key_event.keycode, str):
|
||||
keycodelist = [key_event.keycode]
|
||||
else:
|
||||
keycodelist = key_event.keycode
|
||||
else:
|
||||
continue
|
||||
|
||||
keycodelistidx = 0
|
||||
while keycodelistidx < len(keycodelist):
|
||||
tmpkeycode = keycodelist[keycodelistidx]
|
||||
if curdevicefw == "314":
|
||||
# Remap printscreen event as pause and vice versa for special handling
|
||||
if tmpkeycode == "KEY_PRINTSCREEN":
|
||||
tmpkeycode = KEYCODE_PAUSE
|
||||
elif tmpkeycode == "KEY_SYSRQ":
|
||||
# This gets fired for some devices
|
||||
tmpkeycode = KEYCODE_PAUSE
|
||||
elif tmpkeycode == KEYCODE_PAUSE:
|
||||
# Some other key so it will not fire
|
||||
tmpkeycode = "KEY_PRINTSCREEN"
|
||||
#debuglog("keyboard-event", "FW:" + curdevicefw+ " Key Code: "+tmpkeycode + " Press:"+keycodelist[keycodelistidx])
|
||||
|
||||
|
||||
keycodelistidx = keycodelistidx + 1
|
||||
# if tmpkeycode not in [KEYCODE_BRIGHTNESSDOWN, KEYCODE_BRIGHTNESSUP, KEYCODE_VOLUMEDOWN, KEYCODE_VOLUMEUP]:
|
||||
# if event.value == 2:
|
||||
# # Skip hold for unhandled keys
|
||||
# continue
|
||||
# elif tmpkeycode not in [KEYCODE_PAUSE, KEYCODE_MUTE]:
|
||||
# # Skip press for unhandled keys
|
||||
# continue
|
||||
if tmpkeycode not in [KEYCODE_BRIGHTNESSDOWN, KEYCODE_BRIGHTNESSUP]:
|
||||
if event.value == 2:
|
||||
# Skip hold for unhandled keys
|
||||
continue
|
||||
elif tmpkeycode not in [KEYCODE_PAUSE]:
|
||||
# Skip press for unhandled keys
|
||||
continue
|
||||
|
||||
tmptime = time.time()
|
||||
finalmode = event.value
|
||||
if event.value == 2:
|
||||
# Hold needs checking
|
||||
if tmpkeycode in keypresstimestamp:
|
||||
# Guard time before first for hold
|
||||
if (tmptime - keypresstimestamp[tmpkeycode]) >= FIRSTHOLDINTERVALSEC:
|
||||
# Guard time for hold
|
||||
if tmpkeycode in keyholdtimestamp:
|
||||
if (tmptime - keyholdtimestamp[tmpkeycode]) < HOLDWAITINTERVALSEC:
|
||||
#debuglog("keyboard-event", "Hold Key Code: "+str(tmpkeycode)+" - Skip")
|
||||
continue
|
||||
else:
|
||||
#debuglog("keyboard-event", "Hold Key Code: "+str(tmpkeycode)+" - Skip")
|
||||
continue
|
||||
else:
|
||||
# Should not happen, but treat as if first press
|
||||
finalmode = 1
|
||||
|
||||
#debuglog("keyboard-event", "Mode:"+str(event.value) + " Final:"+str(finalmode)+" " +str(tmpkeycode))
|
||||
|
||||
if finalmode == 1:
|
||||
keypresstimestamp[tmpkeycode] = tmptime
|
||||
writeq.put("PRESS+"+tmpkeycode)
|
||||
else:
|
||||
keyholdtimestamp[tmpkeycode] = tmptime
|
||||
writeq.put("HOLD+"+tmpkeycode)
|
||||
|
||||
except Exception as keyhandleerr:
|
||||
try:
|
||||
debuglog("keyboard-keyerror", str(keyhandleerr))
|
||||
except:
|
||||
debuglog("keyboard-keyerror", "Error")
|
||||
|
||||
newpathlist = keyboardevent_getdevicepaths()
|
||||
if keyboardevent_devicechanged(devicepathlist, newpathlist):
|
||||
debuglog("keyboard-update", "Device list changed")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
try:
|
||||
debuglog("keyboard-mainerror", str(e))
|
||||
except:
|
||||
debuglog("keyboard-mainerror", "Error")
|
||||
|
||||
# Close devices
|
||||
while len(devicelist) > 0:
|
||||
tmpdevice = devicelist.pop(0)
|
||||
try:
|
||||
tmpdevice.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
except Exception as mainerr:
|
||||
time.sleep(10)
|
||||
# While True
|
||||
try:
|
||||
writeq.put("EXIT")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
cmd = sys.argv[1].upper()
|
||||
if cmd == "SERVICE":
|
||||
if createlockfile(KEYBOARD_LOCKFILE) == True:
|
||||
debuglog("keyboard-service", "Already running")
|
||||
else:
|
||||
try:
|
||||
debuglog("keyboard-service", "Service Starting")
|
||||
ipcq = Queue()
|
||||
t1 = Thread(target = keyboardevemt_keyhandler, args =(ipcq, ))
|
||||
t2 = Thread(target = keyboardevent_monitor, args =(ipcq, ))
|
||||
t1.start()
|
||||
t2.start()
|
||||
|
||||
ipcq.join()
|
||||
|
||||
except Exception as einit:
|
||||
try:
|
||||
debuglog("keyboard-service-error", str(einit))
|
||||
except:
|
||||
debuglog("keyboard-service-error", "Error")
|
||||
debuglog("keyboard-service", "Service Stopped")
|
||||
deletelockfile(KEYBOARD_LOCKFILE)
|
||||
525
source/scripts/argonone-irdecoder-libgpiod.py
Normal file
525
source/scripts/argonone-irdecoder-libgpiod.py
Normal file
@@ -0,0 +1,525 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# Standard Headers
|
||||
import sys
|
||||
import smbus
|
||||
|
||||
# For GPIO
|
||||
import gpiod
|
||||
from datetime import datetime
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
# Check if Lirc Lib is installed
|
||||
haslirclib = os.path.isfile("/usr/bin/mode2")
|
||||
if haslirclib == True:
|
||||
from multiprocessing import Process
|
||||
|
||||
#########################
|
||||
# Use GPIO
|
||||
|
||||
def getGPIOPulseData():
|
||||
# Counter
|
||||
ctr = 0
|
||||
|
||||
# Pin Assignments
|
||||
LINE_IRRECEIVER=23
|
||||
|
||||
try:
|
||||
try:
|
||||
# Pi5 mapping
|
||||
chip = gpiod.Chip('4')
|
||||
except Exception as gpioerr:
|
||||
# Old mapping
|
||||
chip = gpiod.Chip('0')
|
||||
lineobj = chip.get_line(LINE_IRRECEIVER)
|
||||
lineobj.request(consumer="argon", type=gpiod.LINE_REQ_EV_BOTH_EDGES)
|
||||
except Exception as e:
|
||||
# GPIO Error
|
||||
return [(-2, -2)]
|
||||
|
||||
# Start reading
|
||||
value = lineobj.get_value()
|
||||
|
||||
# mark time
|
||||
startTime = datetime.now()
|
||||
pulseTime = startTime
|
||||
|
||||
# Pulse Data
|
||||
pulsedata = []
|
||||
|
||||
aborted = False
|
||||
while aborted == False:
|
||||
# Wait for transition
|
||||
try:
|
||||
while True:
|
||||
hasevent = lineobj.event_wait(10)
|
||||
if hasevent:
|
||||
# Event data needs to be read
|
||||
eventdata = lineobj.event_read()
|
||||
break
|
||||
except Exception as e:
|
||||
# GPIO Error
|
||||
lineobj.release()
|
||||
chip.close()
|
||||
return [(-2, -2)]
|
||||
|
||||
# high/low Length
|
||||
now = datetime.now()
|
||||
pulseLength = now - pulseTime
|
||||
pulseTime = now
|
||||
|
||||
# Update value (changed triggered), this also inverts value before saving
|
||||
if value:
|
||||
value = 0
|
||||
else:
|
||||
value = 1
|
||||
|
||||
if pulseLength.microseconds > PULSETAIL_MAXMICROS_NEC and ctr == 0:
|
||||
continue
|
||||
|
||||
pulsedata.append((value, pulseLength.microseconds))
|
||||
|
||||
ctr = ctr + 1
|
||||
if pulseLength.microseconds > PULSETAIL_MAXMICROS_NEC:
|
||||
break
|
||||
elif ctr > PULSEDATA_MAXCOUNT:
|
||||
break
|
||||
|
||||
lineobj.release()
|
||||
chip.close()
|
||||
|
||||
# Data is most likely incomplete
|
||||
if aborted == True:
|
||||
return []
|
||||
elif ctr >= PULSEDATA_MAXCOUNT:
|
||||
print (" * Unable to decode. Please try again *")
|
||||
return []
|
||||
return pulsedata
|
||||
|
||||
|
||||
#########################
|
||||
# Use LIRC
|
||||
def lircMode2Task(irlogfile):
|
||||
os.system("mode2 > "+irlogfile+" 2>&1")
|
||||
|
||||
def startLIRCMode2Logging(irlogfile):
|
||||
# create a new process
|
||||
loggerprocess = Process(target=lircMode2Task,args=(irlogfile,))
|
||||
loggerprocess.start()
|
||||
# mode2 will start new process, terminate current
|
||||
time.sleep(0.1)
|
||||
loggerprocess.kill()
|
||||
return True
|
||||
|
||||
def endLIRCMode2Logging(irlogfile):
|
||||
tmplogfile = irlogfile+".tmp"
|
||||
os.system("ps | grep ode2 > "+tmplogfile+"")
|
||||
|
||||
if os.path.exists(tmplogfile) == True:
|
||||
ctr = 0
|
||||
fp = open(tmplogfile, "r")
|
||||
for curline in fp:
|
||||
if len(curline) > 0:
|
||||
rowdata = curline.split(" ")
|
||||
pid = ""
|
||||
processname = ""
|
||||
colidx = 0
|
||||
while colidx < len(rowdata):
|
||||
if len(rowdata[colidx]) > 0:
|
||||
if pid == "":
|
||||
pid = rowdata[colidx]
|
||||
else:
|
||||
processname = rowdata[colidx]
|
||||
|
||||
colidx = colidx + 1
|
||||
if processname=="mode2\n":
|
||||
os.system("kill -9 "+pid)
|
||||
fp.close()
|
||||
os.remove(tmplogfile)
|
||||
return True
|
||||
|
||||
def getLIRCPulseData():
|
||||
if haslirclib == False:
|
||||
print (" * LIRC Module not found, please reboot and try again *")
|
||||
return []
|
||||
|
||||
irlogfile = "/dev/shm/lircdecoder.log"
|
||||
|
||||
loggerresult = startLIRCMode2Logging(irlogfile)
|
||||
if loggerresult == False:
|
||||
return [(-1, -1)]
|
||||
|
||||
# Wait for log file
|
||||
logsize = 0
|
||||
while logsize == 0:
|
||||
if os.path.exists(irlogfile) == True:
|
||||
logsize = os.path.getsize(irlogfile)
|
||||
if logsize == 0:
|
||||
time.sleep(0.1)
|
||||
|
||||
# Wait for data to start
|
||||
newlogsize = logsize
|
||||
while logsize == newlogsize:
|
||||
time.sleep(0.1)
|
||||
newlogsize = os.path.getsize(irlogfile)
|
||||
|
||||
print(" Thank you")
|
||||
|
||||
# Wait for data to stop
|
||||
while logsize != newlogsize:
|
||||
logsize = newlogsize
|
||||
time.sleep(0.1)
|
||||
newlogsize = os.path.getsize(irlogfile)
|
||||
|
||||
# Finalize File
|
||||
loggerresult = endLIRCMode2Logging(irlogfile)
|
||||
if loggerresult == False:
|
||||
return [(-1, -1)]
|
||||
|
||||
# Decode logfile into Pulse Data
|
||||
pulsedata = []
|
||||
|
||||
terminated = False
|
||||
if os.path.exists(irlogfile) == True:
|
||||
ctr = 0
|
||||
fp = open(irlogfile, "r")
|
||||
for curline in fp:
|
||||
if len(curline) > 0:
|
||||
rowdata = curline.split(" ")
|
||||
if len(rowdata) == 2:
|
||||
duration = int(rowdata[1])
|
||||
value = 0
|
||||
if rowdata[0] == "pulse":
|
||||
value = 1
|
||||
ctr = ctr + 1
|
||||
if value == 1 or ctr > 1:
|
||||
if len(pulsedata) > 0 and duration > PULSELEADER_MINMICROS_NEC:
|
||||
terminated = True
|
||||
break
|
||||
else:
|
||||
pulsedata.append((value, duration))
|
||||
fp.close()
|
||||
os.remove(irlogfile)
|
||||
|
||||
# Check if terminating pulse detected
|
||||
if terminated == False:
|
||||
print (" * Unable to read signal. Please try again *")
|
||||
return []
|
||||
return pulsedata
|
||||
|
||||
|
||||
#########################
|
||||
# Common
|
||||
irconffile = "/etc/lirc/lircd.conf.d/argon.lircd.conf"
|
||||
|
||||
# I2C
|
||||
address = 0x1a # I2C Address
|
||||
addressregister = 0xaa # I2C Address Register
|
||||
|
||||
# Constants
|
||||
PULSETIMEOUTMS = 1000
|
||||
VERIFYTARGET = 3
|
||||
PULSEDATA_MAXCOUNT = 200 # Fail safe
|
||||
|
||||
# NEC Protocol Constants
|
||||
PULSEBIT_MAXMICROS_NEC = 2500
|
||||
PULSEBIT_ZEROMICROS_NEC = 1000
|
||||
|
||||
PULSELEADER_MINMICROS_NEC = 8000
|
||||
PULSELEADER_MAXMICROS_NEC = 10000
|
||||
PULSETAIL_MAXMICROS_NEC = 12000
|
||||
|
||||
# Flags
|
||||
FLAGV1ONLY = False
|
||||
|
||||
try:
|
||||
if os.path.isfile("/etc/argon/flag_v1"):
|
||||
FLAGV1ONLY = True
|
||||
except Exception:
|
||||
FLAGV1ONLY = False
|
||||
|
||||
|
||||
# Standard Methods
|
||||
def getbytestring(pulsedata):
|
||||
outstring = ""
|
||||
for curbyte in pulsedata:
|
||||
tmpstr = hex(curbyte)[2:]
|
||||
while len(tmpstr) < 2:
|
||||
tmpstr = "0" + tmpstr
|
||||
outstring = outstring+tmpstr
|
||||
return outstring
|
||||
|
||||
def displaybyte(pulsedata):
|
||||
print (getbytestring(pulsedata))
|
||||
|
||||
|
||||
def pulse2byteNEC(pulsedata):
|
||||
outdata = []
|
||||
bitdata = 1
|
||||
curbyte = 0
|
||||
bitcount = 0
|
||||
for (mode, duration) in pulsedata:
|
||||
if mode == 1:
|
||||
continue
|
||||
elif duration > PULSEBIT_MAXMICROS_NEC:
|
||||
continue
|
||||
elif duration > PULSEBIT_ZEROMICROS_NEC:
|
||||
curbyte = curbyte*2 + 1
|
||||
else:
|
||||
curbyte = curbyte*2
|
||||
|
||||
bitcount = bitcount + 1
|
||||
if bitcount == 8:
|
||||
outdata.append(curbyte)
|
||||
curbyte = 0
|
||||
bitcount = 0
|
||||
# Shouldn't happen, but just in case
|
||||
if bitcount > 0:
|
||||
outdata.append(curbyte)
|
||||
|
||||
return outdata
|
||||
|
||||
|
||||
def bytecompare(a, b):
|
||||
idx = 0
|
||||
maxidx = len(a)
|
||||
if maxidx != len(b):
|
||||
return 1
|
||||
while idx < maxidx:
|
||||
if a[idx] != b[idx]:
|
||||
return 1
|
||||
idx = idx + 1
|
||||
return 0
|
||||
|
||||
|
||||
# Main Flow
|
||||
mode = "custom"
|
||||
if len(sys.argv) > 1:
|
||||
mode = sys.argv[1]
|
||||
|
||||
powerdata = []
|
||||
buttonlist = ['POWER', 'UP', 'DOWN', 'LEFT', 'RIGHT',
|
||||
'VOLUMEUP', 'VOLUMEDOWN', 'OK', 'HOME', 'MENU'
|
||||
'BACK']
|
||||
|
||||
ircodelist = ['00ff39c6', '00ff53ac', '00ff4bb4', '00ff9966', '00ff837c',
|
||||
'00ff01fe', '00ff817e', '00ff738c', '00ffd32c', '00ffb946',
|
||||
'00ff09f6']
|
||||
|
||||
buttonidx = 0
|
||||
|
||||
if mode == "power":
|
||||
buttonlist = ['POWER']
|
||||
ircodelist = ['']
|
||||
elif mode == "resetpower":
|
||||
# Just Set the power so it won't create/update the conf file
|
||||
buttonlist = ['POWER']
|
||||
mode = "default"
|
||||
elif mode == "custom":
|
||||
buttonlist = ['POWER', 'UP', 'DOWN', 'LEFT', 'RIGHT',
|
||||
'VOLUMEUP', 'VOLUMEDOWN', 'OK', 'HOME', 'MENU'
|
||||
'BACK']
|
||||
ircodelist = ['', '', '', '', '',
|
||||
'', '', '', '', '',
|
||||
'']
|
||||
#buttonlist = ['POWER', 'VOLUMEUP', 'VOLUMEDOWN']
|
||||
#ircodelist = ['', '', '']
|
||||
|
||||
if mode == "default":
|
||||
# To skip the decoding loop
|
||||
buttonidx = len(buttonlist)
|
||||
# Set MCU IR code
|
||||
powerdata = [0x00, 0xff, 0x39, 0xc6]
|
||||
else:
|
||||
print ("************************************************")
|
||||
print ("* WARNING: Current buttons are still active. *")
|
||||
print ("* Please temporarily assign to a *")
|
||||
print ("* different button if you plan to *")
|
||||
print ("* reuse buttons. *")
|
||||
print ("* e.g. Power Button triggers shutdown *")
|
||||
print ("* *")
|
||||
print ("* PROCEED AT YOUR OWN RISK *")
|
||||
print ("* (Press CTRL+C to abort at any time) *")
|
||||
print ("************************************************")
|
||||
|
||||
readaborted = False
|
||||
# decoding loop
|
||||
while buttonidx < len(buttonlist):
|
||||
print ("Press your button for "+buttonlist[buttonidx]+" (CTRL+C to abort)")
|
||||
irprotocol = ""
|
||||
outdata = []
|
||||
verifycount = 0
|
||||
readongoing = True
|
||||
|
||||
# Handles NEC protocol Only
|
||||
while readongoing == True:
|
||||
# Try GPIO-based reading, if it fails, fallback to LIRC
|
||||
pulsedata = getGPIOPulseData()
|
||||
if len(pulsedata) == 1:
|
||||
if pulsedata[0][0] == -2:
|
||||
pulsedata = getLIRCPulseData()
|
||||
|
||||
# Aborted
|
||||
if len(pulsedata) == 1:
|
||||
if pulsedata[0][0] == -1:
|
||||
readongoing = False
|
||||
readaborted = True
|
||||
buttonidx = len(buttonlist)
|
||||
break
|
||||
# Ignore repeat code (NEC)
|
||||
if len(pulsedata) <= 4:
|
||||
continue
|
||||
|
||||
# Get leading signal
|
||||
(mode, duration) = pulsedata[0]
|
||||
|
||||
# Decode IR Protocols
|
||||
# https://www.sbprojects.net/knowledge/ir/index.php
|
||||
|
||||
if duration >= PULSELEADER_MINMICROS_NEC and duration <= PULSELEADER_MAXMICROS_NEC:
|
||||
irprotocol = "NEC"
|
||||
# NEC has 9ms head, +/- 1ms
|
||||
curdata = pulse2byteNEC(pulsedata)
|
||||
if len(curdata) > 0:
|
||||
if verifycount > 0:
|
||||
if bytecompare(outdata, curdata) == 0:
|
||||
verifycount = verifycount + 1
|
||||
else:
|
||||
verifycount = 0
|
||||
else:
|
||||
outdata = curdata
|
||||
verifycount = 1
|
||||
|
||||
if verifycount >= VERIFYTARGET:
|
||||
readongoing = False
|
||||
print ("")
|
||||
elif verifycount == 0:
|
||||
print (" * IR code mismatch, please try again *")
|
||||
elif VERIFYTARGET - verifycount > 1:
|
||||
print (" Press the button "+ str(VERIFYTARGET - verifycount)+ " more times")
|
||||
else:
|
||||
print (" Press the button 1 more time")
|
||||
else:
|
||||
print (" * Decoding error. Please try again *")
|
||||
else:
|
||||
print (" * Unrecognized signal. Please try again *")
|
||||
#curdata = pulse2byteLSB(pulsedata)
|
||||
#displaybyte(curdata)
|
||||
|
||||
# Check for duplicates
|
||||
newircode = getbytestring(outdata)
|
||||
if verifycount > 0:
|
||||
checkidx = 0
|
||||
while checkidx < buttonidx and checkidx < len(buttonlist):
|
||||
if ircodelist[checkidx] == newircode:
|
||||
print (" Button already assigned. Please try again")
|
||||
verifycount = 0
|
||||
break
|
||||
checkidx = checkidx + 1
|
||||
|
||||
# Store code, and power button code if applicable
|
||||
if verifycount > 0:
|
||||
if buttonidx == 0:
|
||||
powerdata = outdata
|
||||
if buttonidx < len(buttonlist):
|
||||
# Abort will cause out of bounds
|
||||
ircodelist[buttonidx] = newircode
|
||||
#print (buttonlist[buttonidx]+": "+ newircode)
|
||||
buttonidx = buttonidx + 1
|
||||
|
||||
if len(powerdata) > 0 and readaborted == False:
|
||||
# Send to device if completed or reset mode
|
||||
#print("Writing " + getbytestring(powerdata))
|
||||
print("Updating Device...")
|
||||
try:
|
||||
bus=smbus.SMBus(1)
|
||||
except Exception:
|
||||
try:
|
||||
# Older version
|
||||
bus=smbus.SMBus(0)
|
||||
except Exception:
|
||||
bus=None
|
||||
|
||||
if bus is None:
|
||||
print("Device Update Failed: Unable to detect i2c")
|
||||
else:
|
||||
# Check for Argon Control Register Support
|
||||
checkircodewrite = False
|
||||
argoncyclereg = 0x80
|
||||
if FLAGV1ONLY == False:
|
||||
oldval = bus.read_byte_data(address, argoncyclereg)
|
||||
newval = oldval + 1
|
||||
if newval >= 100:
|
||||
newval = 98
|
||||
bus.write_byte_data(address,argoncyclereg, newval)
|
||||
time.sleep(1)
|
||||
newval = bus.read_byte_data(address, argoncyclereg)
|
||||
|
||||
if newval != oldval:
|
||||
addressregister = 0x82
|
||||
checkircodewrite = True
|
||||
bus.write_byte_data(address,argoncyclereg, oldval)
|
||||
|
||||
bus.write_i2c_block_data(address, addressregister, powerdata)
|
||||
|
||||
|
||||
if checkircodewrite == True:
|
||||
# Check if data was written for devices that support it
|
||||
print("Verifying ...")
|
||||
time.sleep(2)
|
||||
checkircodedata = bus.read_i2c_block_data(address, addressregister, 4)
|
||||
checkircodecounter = 0
|
||||
while checkircodecounter < 4:
|
||||
# Reuse readaborted flag as indicator if IR code was successfully updated
|
||||
if checkircodedata[checkircodecounter] != powerdata[checkircodecounter]:
|
||||
readaborted = True
|
||||
checkircodecounter = checkircodecounter + 1
|
||||
if readaborted == False:
|
||||
print("Device Update Successful")
|
||||
else:
|
||||
print("Verification Failed")
|
||||
bus.close()
|
||||
|
||||
# Update IR Conf if there are other button
|
||||
if buttonidx > 1 and readaborted == False:
|
||||
print("Updating Remote Control Codes...")
|
||||
fp = open(irconffile, "w")
|
||||
|
||||
# Standard NEC conf header
|
||||
fp.write("#\n")
|
||||
fp.write("# Based on NEC templates at http://lirc.sourceforge.net/remotes/nec/\n")
|
||||
fp.write("# Configured codes based on data gathered\n")
|
||||
fp.write("#\n")
|
||||
fp.write("\n")
|
||||
fp.write("begin remote\n")
|
||||
fp.write(" name argon\n")
|
||||
fp.write(" bits 32\n")
|
||||
fp.write(" flags SPACE_ENC\n")
|
||||
fp.write(" eps 20\n")
|
||||
fp.write(" aeps 200\n")
|
||||
fp.write("\n")
|
||||
fp.write(" header 8800 4400\n")
|
||||
fp.write(" one 550 1650\n")
|
||||
fp.write(" zero 550 550\n")
|
||||
fp.write(" ptrail 550\n")
|
||||
fp.write(" repeat 8800 2200\n")
|
||||
fp.write(" gap 38500\n")
|
||||
fp.write(" toggle_bit 0\n")
|
||||
fp.write("\n")
|
||||
fp.write(" frequency 38000\n")
|
||||
fp.write("\n")
|
||||
fp.write(" begin codes\n")
|
||||
|
||||
# Write Key Codes
|
||||
buttonidx = 1
|
||||
while buttonidx < len(buttonlist):
|
||||
fp.write(" KEY_"+buttonlist[buttonidx]+" 0x"+ircodelist[buttonidx]+"\n")
|
||||
buttonidx = buttonidx + 1
|
||||
fp.write(" end codes\n")
|
||||
fp.write("end remote\n")
|
||||
fp.close()
|
||||
|
||||
|
||||
|
||||
294
source/scripts/argonone-oledconfig.sh
Normal file
294
source/scripts/argonone-oledconfig.sh
Normal file
@@ -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" == "ram" ]
|
||||
then
|
||||
pagename="Available RAM"
|
||||
elif [ "$1" == "temp" ]
|
||||
then
|
||||
pagename="CPU Temperature"
|
||||
elif [ "$1" == "ip" ]
|
||||
then
|
||||
pagename="IP Address"
|
||||
elif [ "$1" == "logo1v5" ]
|
||||
then
|
||||
pagename="Logo:One v5"
|
||||
else
|
||||
pagename="Invalid"
|
||||
fi
|
||||
}
|
||||
|
||||
configure_pagelist () {
|
||||
pagemasterlist="logo1v5 clock cpu storage 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="ip cpu ram"
|
||||
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
|
||||
114
source/scripts/argononeup-lidconfig.sh
Normal file
114
source/scripts/argononeup-lidconfig.sh
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/bin/bash
|
||||
|
||||
tmpfile="/dev/shm/argontmpconf.txt"
|
||||
daemonconfigfile="/etc/argononeupd.conf"
|
||||
|
||||
if [ -f "$daemonconfigfile" ]
|
||||
then
|
||||
. $daemonconfigfile
|
||||
fi
|
||||
|
||||
if [ -z "$lidshutdownsecs" ]
|
||||
then
|
||||
lidshutdownsecs=0
|
||||
fi
|
||||
|
||||
|
||||
mainloopflag=1
|
||||
newmode=0
|
||||
|
||||
|
||||
get_number () {
|
||||
read curnumber
|
||||
if [ -z "$curnumber" ]
|
||||
then
|
||||
echo "-2"
|
||||
return
|
||||
elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]]
|
||||
then
|
||||
if [ $curnumber -lt 0 ]
|
||||
then
|
||||
echo "-1"
|
||||
return
|
||||
fi
|
||||
echo $curnumber
|
||||
return
|
||||
fi
|
||||
echo "-1"
|
||||
return
|
||||
}
|
||||
|
||||
while [ $mainloopflag -eq 1 ]
|
||||
do
|
||||
|
||||
lidshutdownmins=$((lidshutdownsecs / 60))
|
||||
|
||||
|
||||
echo "------------------------------------------"
|
||||
echo " Argon One Up Lid Configuration Tool"
|
||||
echo "------------------------------------------"
|
||||
|
||||
echo
|
||||
echo "Lid Close Behavior:"
|
||||
if [ $lidshutdownsecs -lt 1 ]
|
||||
then
|
||||
echo "(Do Nothing)"
|
||||
else
|
||||
echo "(Shut down after $lidshutdownmins minute(s))"
|
||||
fi
|
||||
echo " 1. Do Nothing"
|
||||
echo " 2. Shutdown"
|
||||
echo
|
||||
echo " 0. Exit"
|
||||
echo "NOTE: You can also edit $daemonconfigfile directly"
|
||||
echo -n "Enter Number (0-2):"
|
||||
newmode=$( get_number )
|
||||
|
||||
if [[ $newmode -eq 0 ]]
|
||||
then
|
||||
mainloopflag=0
|
||||
elif [ $newmode -eq 1 ]
|
||||
then
|
||||
lidshutdownsecs=0
|
||||
elif [ $newmode -eq 2 ]
|
||||
then
|
||||
maxmins=120
|
||||
echo "Please provide number of minutes until shutdown:"
|
||||
echo -n "Enter Number (1-$maxmins):"
|
||||
curval=$( get_number )
|
||||
if [ $curval -gt $maxmins ]
|
||||
then
|
||||
newmode=0
|
||||
echo "Invalid input"
|
||||
elif [ $curval -lt 1 ]
|
||||
then
|
||||
newmode=0
|
||||
echo "Invalid input"
|
||||
else
|
||||
lidshutdownsecs=$((curval * 60))
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $newmode -eq 1 ] || [ $newmode -eq 2 ]
|
||||
then
|
||||
if [ -f "$daemonconfigfile" ]
|
||||
then
|
||||
grep -v 'lidshutdownsecs' "$daemonconfigfile" > $tmpfile
|
||||
else
|
||||
echo '#' > $tmpfile
|
||||
echo '# Argon One Up Configuration' >> $tmpfile
|
||||
echo '#' >> $tmpfile
|
||||
fi
|
||||
echo '# lidshutdownsecs number of seconds till shutdown when lid is closed 0 if do nothing' >> $tmpfile
|
||||
echo "lidshutdownsecs=$lidshutdownsecs" >> $tmpfile
|
||||
|
||||
sudo cp $tmpfile $daemonconfigfile
|
||||
sudo chmod 666 $daemonconfigfile
|
||||
|
||||
echo "Configuration updated."
|
||||
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
|
||||
474
source/scripts/argononeupd.py
Normal file
474
source/scripts/argononeupd.py
Normal file
@@ -0,0 +1,474 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
#
|
||||
# This script monitor battery via ic2 and keyboard events.
|
||||
#
|
||||
# Additional comments are found in each function below
|
||||
#
|
||||
#
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
from threading import Thread
|
||||
from queue import Queue
|
||||
|
||||
sys.path.append("/etc/argon/")
|
||||
from argonregister import *
|
||||
from argonpowerbutton import *
|
||||
|
||||
# Initialize I2C Bus
|
||||
bus = argonregister_initializebusobj()
|
||||
|
||||
# Constants
|
||||
ADDR_BATTERY = 0x64
|
||||
|
||||
UPS_LOGFILE="/dev/shm/upslog.txt"
|
||||
|
||||
|
||||
###################
|
||||
# Utilty Functions
|
||||
###################
|
||||
|
||||
# Debug Logger
|
||||
def debuglog(typestr, logstr):
|
||||
try:
|
||||
DEBUGFILE="/dev/shm/argononeupdebuglog.txt"
|
||||
tmpstrpadding = " "
|
||||
|
||||
with open(DEBUGFILE, "a") as txt_file:
|
||||
txt_file.write("["+time.asctime(time.localtime(time.time()))+"] "+typestr.upper()+" "+logstr.strip().replace("\n","\n"+tmpstrpadding)+"\n")
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# System Notifcation
|
||||
def notifymessage(message, iscritical):
|
||||
if not isinstance(message, str) or len(message.strip()) == 0:
|
||||
return
|
||||
|
||||
wftype="notify"
|
||||
if iscritical:
|
||||
wftype="critical"
|
||||
os.system("export SUDO_UID=1000; wfpanelctl "+wftype+" \""+message+"\"")
|
||||
os.system("export DISPLAY=:0.0; lxpanelctl notify \""+message+"\"")
|
||||
|
||||
|
||||
#############
|
||||
# Battery
|
||||
#############
|
||||
REG_CONTROL = 0x08
|
||||
REG_SOCALERT = 0x0b
|
||||
REG_PROFILE = 0x10
|
||||
REG_ICSTATE = 0xA7
|
||||
|
||||
|
||||
|
||||
def battery_restart():
|
||||
# Set to active mode
|
||||
try:
|
||||
maxretry = 3
|
||||
while maxretry > 0:
|
||||
maxretry = maxretry - 1
|
||||
|
||||
# Restart
|
||||
bus.write_byte_data(ADDR_BATTERY, REG_CONTROL, 0x30)
|
||||
time.sleep(0.5)
|
||||
# Activate
|
||||
bus.write_byte_data(ADDR_BATTERY, REG_CONTROL, 0x00)
|
||||
time.sleep(0.5)
|
||||
|
||||
# Wait for Ready Status
|
||||
maxwaitsecs = 5
|
||||
while maxwaitsecs > 0:
|
||||
tmpval = bus.read_byte_data(ADDR_BATTERY, REG_ICSTATE)
|
||||
if (tmpval&0x0C) != 0:
|
||||
debuglog("battery-activate", "Activated Successfully")
|
||||
return 0
|
||||
time.sleep(1)
|
||||
maxwaitsecs = maxwaitsecs - 1
|
||||
|
||||
|
||||
debuglog("battery-activate", "Failed to activate")
|
||||
return 2
|
||||
except Exception as e:
|
||||
try:
|
||||
debuglog("battery-activateerror", str(e))
|
||||
except:
|
||||
debuglog("battery-activateerror", "Activation Failed")
|
||||
return 1
|
||||
|
||||
|
||||
def battery_getstatus(restartifnotactive):
|
||||
try:
|
||||
tmpval = bus.read_byte_data(ADDR_BATTERY, REG_CONTROL)
|
||||
if tmpval != 0:
|
||||
if restartifnotactive == True:
|
||||
tmpval = battery_restart()
|
||||
|
||||
if tmpval != 0:
|
||||
debuglog("battery-status", "Inactive "+str(tmpval))
|
||||
return 2
|
||||
|
||||
tmpval = bus.read_byte_data(ADDR_BATTERY, REG_SOCALERT)
|
||||
if (tmpval&0x80) == 0:
|
||||
debuglog("battery-status", "Profile not ready "+str(tmpval))
|
||||
return 3
|
||||
|
||||
# OK
|
||||
#debuglog("battery-status", "OK")
|
||||
return 0
|
||||
except Exception as e:
|
||||
try:
|
||||
debuglog("battery-status-error", str(e))
|
||||
except:
|
||||
debuglog("battery-status-error", "Battery Status Failed")
|
||||
|
||||
return 1
|
||||
|
||||
def battery_checkupdateprofile():
|
||||
try:
|
||||
REG_GPIOCONFIG = 0x0A
|
||||
|
||||
PROFILE_DATALIST = [0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA8,0xAA,0xBE,0xC6,0xB8,0xAE,0xC2,0x98,0x82,0xFF,0xFF,0xCA,0x98,0x75,0x63,0x55,0x4E,0x4C,0x49,0x98,0x88,0xDC,0x34,0xDB,0xD3,0xD4,0xD3,0xD0,0xCE,0xCB,0xBB,0xE7,0xA2,0xC2,0xC4,0xAE,0x96,0x89,0x80,0x74,0x67,0x63,0x71,0x8E,0x9F,0x85,0x6F,0x3B,0x20,0x00,0xAB,0x10,0xFF,0xB0,0x73,0x00,0x00,0x00,0x64,0x08,0xD3,0x77,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFA]
|
||||
|
||||
PROFILE_LEN = len(PROFILE_DATALIST)
|
||||
|
||||
# Try to compare profile if battery is active
|
||||
tmpidx = 0
|
||||
|
||||
tmpval = battery_getstatus(True)
|
||||
if tmpval == 0:
|
||||
# Status OK, check profile
|
||||
tmpidx = 0
|
||||
while tmpidx < PROFILE_LEN:
|
||||
tmpval = bus.read_byte_data(ADDR_BATTERY, REG_PROFILE+tmpidx)
|
||||
if tmpval != PROFILE_DATALIST[tmpidx]:
|
||||
debuglog("battery-profile-error", "Mismatch")
|
||||
break
|
||||
tmpidx = tmpidx + 1
|
||||
|
||||
if tmpidx == PROFILE_LEN:
|
||||
# Matched
|
||||
return 0
|
||||
else:
|
||||
debuglog("battery-profile", "Status Error "+str(tmpval)+", will attempt to update")
|
||||
|
||||
# needs update
|
||||
debuglog("battery-profile", "Updating...")
|
||||
|
||||
# Device Sleep state
|
||||
|
||||
# Restart
|
||||
bus.write_byte_data(ADDR_BATTERY, REG_CONTROL, 0x30)
|
||||
time.sleep(0.5)
|
||||
# Sleep
|
||||
bus.write_byte_data(ADDR_BATTERY, REG_CONTROL, 0xF0)
|
||||
time.sleep(0.5)
|
||||
|
||||
# Write Profile
|
||||
tmpidx = 0
|
||||
while tmpidx < PROFILE_LEN:
|
||||
bus.write_byte_data(ADDR_BATTERY, REG_PROFILE+tmpidx, PROFILE_DATALIST[tmpidx])
|
||||
tmpidx = tmpidx + 1
|
||||
|
||||
debuglog("battery-profile", "Profile Updated,Restarting...")
|
||||
|
||||
# Set Update Flag
|
||||
bus.write_byte_data(ADDR_BATTERY, REG_SOCALERT, 0x80)
|
||||
time.sleep(0.5)
|
||||
|
||||
# Close Interrupts
|
||||
bus.write_byte_data(ADDR_BATTERY, REG_GPIOCONFIG, 0)
|
||||
time.sleep(0.5)
|
||||
|
||||
# Restart Battery
|
||||
tmpval = battery_restart()
|
||||
if tmpval == 0:
|
||||
debuglog("battery-profile", "Update Completed")
|
||||
return 0
|
||||
|
||||
debuglog("battery-profile", "Unable to restart")
|
||||
return 3
|
||||
except Exception as e:
|
||||
try:
|
||||
debuglog("battery-profile-error", str(e))
|
||||
except:
|
||||
debuglog("battery-profile-error", "Battery Profile Check/Update Failed")
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
|
||||
def battery_getpercent():
|
||||
# State of Charge (SOC)
|
||||
try:
|
||||
SOC_HIGH_REG = 0x04
|
||||
|
||||
socpercent = bus.read_byte_data(ADDR_BATTERY, SOC_HIGH_REG)
|
||||
if socpercent > 100:
|
||||
return 100
|
||||
elif socpercent > 0:
|
||||
return socpercent
|
||||
|
||||
# Support Fraction percent
|
||||
#SOC_LOW_REG = 0x05
|
||||
#soc_low = bus.read_byte_data(ADDR_BATTERY, SOC_LOW_REG)
|
||||
#socpercentfloat = socpercent + (soc_low / 256.0)
|
||||
#if socpercentfloat > 100.0:
|
||||
# return 100.0
|
||||
#elif socpercentfloat > 0.0:
|
||||
# return socpercentfloat
|
||||
|
||||
except Exception as e:
|
||||
try:
|
||||
debuglog("battery-percenterror", str(e))
|
||||
except:
|
||||
debuglog("battery-percenterror", "Read Battery Failed")
|
||||
|
||||
return -1
|
||||
|
||||
|
||||
def battery_isplugged():
|
||||
# State of Charge (SOC)
|
||||
try:
|
||||
CURRENT_HIGH_REG = 0x0E
|
||||
|
||||
current_high = bus.read_byte_data(ADDR_BATTERY, CURRENT_HIGH_REG)
|
||||
|
||||
if (current_high & 0x80) > 0:
|
||||
return 1
|
||||
|
||||
#CURRENT_LOW_REG = 0x0F
|
||||
#R_SENSE = 10.0
|
||||
#current_low = bus.read_byte_data(ADDR_BATTERY, CURRENT_LOW_REG)
|
||||
#raw_current = int.from_bytes([current_high, current_low], byteorder='big', signed=True)
|
||||
#current = (52.4 * raw_current) / (32768 * R_SENSE)
|
||||
|
||||
return 0
|
||||
except Exception as e:
|
||||
try:
|
||||
debuglog("battery-chargingerror", str(e))
|
||||
except:
|
||||
debuglog("battery-chargingerror", "Read Charging Failed")
|
||||
|
||||
return -1
|
||||
|
||||
def battery_loadlogdata():
|
||||
# status, version, time, schedule
|
||||
outobj = {}
|
||||
try:
|
||||
fp = open(UPS_LOGFILE, "r")
|
||||
logdata = fp.read()
|
||||
alllines = logdata.split("\n")
|
||||
ctr = 0
|
||||
while ctr < len(alllines):
|
||||
tmpval = alllines[ctr].strip()
|
||||
curinfo = tmpval.split(":")
|
||||
if len(curinfo) > 1:
|
||||
tmpattrib = curinfo[0].lower().split(" ")
|
||||
# The rest are assumed to be value
|
||||
outobj[tmpattrib[0]] = tmpval[(len(curinfo[0])+1):].strip()
|
||||
ctr = ctr + 1
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return outobj
|
||||
|
||||
def battery_check(readq):
|
||||
CMDSTARTBYTE=0xfe
|
||||
CMDCONTROLBYTECOUNT=3
|
||||
CHECKSTATUSLOOPFREQ=50
|
||||
|
||||
CMDsendrequest = [ 0xfe, 0, 0, 0xfe, 0xfe, 0, 0, 0xfe, 0, 0, 0]
|
||||
|
||||
lastcmdtime=""
|
||||
loopCtr = CHECKSTATUSLOOPFREQ
|
||||
sendcmdid = -1
|
||||
|
||||
debuglog("battery", "Starting")
|
||||
|
||||
updatedesktopicon("Argon ONE UP", "/etc/argon/argon40.png")
|
||||
|
||||
maxretry = 5
|
||||
while maxretry > 0:
|
||||
try:
|
||||
if battery_checkupdateprofile() == 0:
|
||||
break
|
||||
except Exception as mainerr:
|
||||
try:
|
||||
debuglog("battery-mainerror", str(mainerr))
|
||||
except:
|
||||
debuglog("battery-mainerror", "Error")
|
||||
# Give time before retry
|
||||
time.sleep(10)
|
||||
maxretry = maxretry - 1
|
||||
|
||||
while maxretry > 0: # Outer loop; maxretry never decrements so infinite
|
||||
qdata = ""
|
||||
if readq.empty() == False:
|
||||
qdata = readq.get()
|
||||
|
||||
if battery_getstatus(True) != 0:
|
||||
# Give time before retry
|
||||
time.sleep(3)
|
||||
continue
|
||||
|
||||
prevnotifymsg = ""
|
||||
previconfile = ""
|
||||
statusstr = ""
|
||||
|
||||
shutdowntriggered=False
|
||||
needsupdate=False
|
||||
device_battery=0
|
||||
device_charging=0
|
||||
|
||||
while True: # Command loop
|
||||
try:
|
||||
if sendcmdid < 0:
|
||||
cmddatastr = ""
|
||||
|
||||
if cmddatastr == "":
|
||||
if loopCtr >= CHECKSTATUSLOOPFREQ:
|
||||
# Check Battery Status
|
||||
sendcmdid = 0
|
||||
loopCtr = 0
|
||||
else:
|
||||
loopCtr = loopCtr + 1
|
||||
if (loopCtr&1) == 0:
|
||||
sendcmdid = 0 # Check Battery Status
|
||||
|
||||
if sendcmdid == 0:
|
||||
tmp_battery = battery_getpercent()
|
||||
tmp_charging = battery_isplugged()
|
||||
|
||||
if tmp_charging < 0 or tmp_battery < 0:
|
||||
# communication error, retain old value
|
||||
tmp_charging = device_charging
|
||||
tmp_battery = device_battery
|
||||
|
||||
if tmp_charging != device_charging or tmp_battery!=device_battery:
|
||||
device_battery=tmp_battery
|
||||
device_charging=tmp_charging
|
||||
tmpiconfile = "/etc/argon/ups/"
|
||||
needsupdate=True
|
||||
curnotifymsg = ""
|
||||
curnotifycritical = False
|
||||
|
||||
if device_battery>99:
|
||||
# Prevents switching issue
|
||||
statusstr = "Charged"
|
||||
curnotifymsg = statusstr
|
||||
tmpiconfile = tmpiconfile+"charge_"+str(device_battery)
|
||||
elif device_charging == 0:
|
||||
statusstr = "Charging"
|
||||
curnotifymsg = statusstr
|
||||
tmpiconfile = tmpiconfile+"charge_"+str(device_battery)
|
||||
else:
|
||||
statusstr = "Battery"
|
||||
tmpiconfile = tmpiconfile+"discharge_"+str(device_battery)
|
||||
|
||||
if device_battery > 50:
|
||||
curnotifymsg="Battery Mode"
|
||||
elif device_battery > 20:
|
||||
curnotifymsg="50%% Battery"
|
||||
elif device_battery > 10:
|
||||
curnotifymsg="20%% Battery"
|
||||
elif device_battery > 5:
|
||||
#curnotifymsg="Low Battery"
|
||||
curnotifymsg="Low Battery: The device may power off automatically soon."
|
||||
curnotifycritical=True
|
||||
else:
|
||||
curnotifymsg="CRITICAL BATTERY: Shutting Down in 1 minute"
|
||||
curnotifycritical=True
|
||||
|
||||
tmpiconfile = tmpiconfile + ".png"
|
||||
statusstr = statusstr + " " + str(device_battery)+"%"
|
||||
|
||||
# Add/update desktop icons too; add check to minimize write
|
||||
if previconfile != tmpiconfile:
|
||||
updatedesktopicon(statusstr, tmpiconfile)
|
||||
previconfile = tmpiconfile
|
||||
|
||||
# Send notification if necessary
|
||||
if prevnotifymsg != curnotifymsg:
|
||||
notifymessage(curnotifymsg, curnotifycritical)
|
||||
if shutdowntriggered==False and device_battery <= 5 and device_charging != 0:
|
||||
shutdowntriggered=True
|
||||
os.system("shutdown +1 """+curnotifymsg+".""")
|
||||
debuglog("battery-shutdown", "Shutdown in 1 minute")
|
||||
|
||||
if shutdowntriggered==True and (device_charging == 0 or device_battery>=10):
|
||||
shutdowntriggered=False
|
||||
os.system("shutdown -c ""Charging, shutdown cancelled.""")
|
||||
debuglog("battery-shutdown", "Abort")
|
||||
|
||||
prevnotifymsg = curnotifymsg
|
||||
|
||||
|
||||
sendcmdid=-1
|
||||
|
||||
if needsupdate==True:
|
||||
# Log File
|
||||
otherstr = ""
|
||||
with open(UPS_LOGFILE, "w") as txt_file:
|
||||
txt_file.write("Status as of: "+time.asctime(time.localtime(time.time()))+"\n Power:"+statusstr+"\n"+otherstr)
|
||||
|
||||
needsupdate=False
|
||||
|
||||
except Exception as e:
|
||||
try:
|
||||
debuglog("battery-error", str(e))
|
||||
except:
|
||||
debuglog("battery-error", "Error")
|
||||
break
|
||||
time.sleep(3)
|
||||
|
||||
def updatedesktopicon(statusstr, tmpiconfile):
|
||||
try:
|
||||
icontitle = "Argon ONE UP"
|
||||
tmp = os.popen("find /home -maxdepth 1 -type d").read()
|
||||
alllines = tmp.split("\n")
|
||||
for curfolder in alllines:
|
||||
if curfolder == "/home" or curfolder == "":
|
||||
continue
|
||||
#debuglog("desktop-update-path", curfolder)
|
||||
#debuglog("desktop-update-text", statusstr)
|
||||
#debuglog("desktop-update-icon", tmpiconfile)
|
||||
with open(curfolder+"/Desktop/argononeup.desktop", "w") as txt_file:
|
||||
txt_file.write("[Desktop Entry]\nName="+icontitle+"\nComment="+statusstr+"\nIcon="+tmpiconfile+"\nExec=lxterminal --working-directory="+curfolder+"/ -t \"Argon ONE UP\" -e \"/etc/argon/argon-config\"\nType=Application\nEncoding=UTF-8\nTerminal=false\nCategories=None;\n")
|
||||
except Exception as desktope:
|
||||
#pass
|
||||
try:
|
||||
debuglog("desktop-update-error", str(desktope))
|
||||
except:
|
||||
debuglog("desktop-update-error", "Error")
|
||||
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
cmd = sys.argv[1].upper()
|
||||
if cmd == "GETBATTERY":
|
||||
outobj = battery_loadlogdata()
|
||||
try:
|
||||
print(outobj["power"])
|
||||
except:
|
||||
print("Error retrieving battery status")
|
||||
elif cmd == "RESETBATTERY":
|
||||
battery_checkupdateprofile()
|
||||
|
||||
elif cmd == "SERVICE":
|
||||
# Starts sudo level services
|
||||
try:
|
||||
ipcq = Queue()
|
||||
if len(sys.argv) > 2:
|
||||
cmd = sys.argv[2].upper()
|
||||
t1 = Thread(target = battery_check, args =(ipcq, ))
|
||||
t2 = Thread(target = argonpowerbutton_monitorlid, args =(ipcq, ))
|
||||
|
||||
t1.start()
|
||||
t2.start()
|
||||
|
||||
ipcq.join()
|
||||
except Exception:
|
||||
sys.exit(1)
|
||||
10
source/scripts/argononeupd.service
Normal file
10
source/scripts/argononeupd.service
Normal file
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Argon ONE UP Service
|
||||
After=multi-user.target
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
RemainAfterExit=true
|
||||
ExecStart=/usr/bin/python3 /etc/argon/argononeupd.py SERVICE
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
10
source/scripts/argononeupduser.service
Normal file
10
source/scripts/argononeupduser.service
Normal file
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Argon ONE UP Service
|
||||
After=multi-user.target
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
RemainAfterExit=true
|
||||
ExecStart=/usr/bin/python3 /etc/argon/argonkeyboard.py SERVICE
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
106
source/scripts/argononeupsd.py
Normal file
106
source/scripts/argononeupsd.py
Normal file
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import time
|
||||
import os
|
||||
|
||||
UPS_LOGFILE="/dev/shm/upslog.txt"
|
||||
#UPS_DEVFILE="/dev/argonbatteryicon"
|
||||
|
||||
def notifymessage(message, iscritical):
|
||||
wftype="notify"
|
||||
if iscritical:
|
||||
wftype="critical"
|
||||
os.system("export SUDO_UID=1000; wfpanelctl "+wftype+" \""+message+"\"")
|
||||
os.system("export DISPLAY=:0.0; lxpanelctl notify \""+message+"\"")
|
||||
|
||||
try:
|
||||
outobj = {}
|
||||
#os.system("insmod /etc/argon/ups/argonbatteryicon.ko")
|
||||
prevnotifymsg=""
|
||||
|
||||
tmp_battery = 100
|
||||
tmp_charging = 1
|
||||
|
||||
device_battery = -1
|
||||
device_charging = -1
|
||||
|
||||
while True:
|
||||
try:
|
||||
# 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("%",""))
|
||||
except:
|
||||
tmp_charging = device_charging
|
||||
tmp_battery = device_battery
|
||||
|
||||
# Update module data if changed
|
||||
if tmp_charging != device_charging or tmp_battery!=device_battery:
|
||||
device_charging = tmp_charging
|
||||
device_battery = tmp_battery
|
||||
|
||||
# No longer using default battery indicator
|
||||
#try:
|
||||
# with open(UPS_DEVFILE, 'w') as f:
|
||||
# f.write("capacity = "+str(device_battery)+"\ncharging = "+str(device_charging)+"\n")
|
||||
#except Exception as e:
|
||||
# pass
|
||||
|
||||
curnotifymsg = ""
|
||||
curnotifycritical=False
|
||||
|
||||
if tmp_charging:
|
||||
if "Shutting Down" in prevnotifymsg:
|
||||
os.system("shutdown -c ""Charging, shutdown cancelled.""")
|
||||
|
||||
if tmp_battery > 99:
|
||||
curnotifymsg="Fully Charged"
|
||||
elif tmp_charging:
|
||||
curnotifymsg="Charging"
|
||||
else:
|
||||
if tmp_battery > 50:
|
||||
curnotifymsg="Battery Mode"
|
||||
elif tmp_battery > 20:
|
||||
curnotifymsg="50%% Battery"
|
||||
elif tmp_battery > 10:
|
||||
curnotifymsg="20%% Battery"
|
||||
elif tmp_battery > 5:
|
||||
#curnotifymsg="Low Battery"
|
||||
curnotifymsg="Low Battery: The device may power off automatically soon."
|
||||
curnotifycritical=True
|
||||
else:
|
||||
curnotifymsg="CRITICAL BATTERY: Shutting Down in 1 minute"
|
||||
curnotifycritical=True
|
||||
|
||||
|
||||
if prevnotifymsg != curnotifymsg:
|
||||
notifymessage(curnotifymsg, curnotifycritical)
|
||||
if tmp_battery <= 5 and tmp_charging != 0:
|
||||
os.system("shutdown +1 """+curnotifymsg+".""")
|
||||
|
||||
prevnotifymsg = curnotifymsg
|
||||
|
||||
except OSError:
|
||||
pass
|
||||
time.sleep(60)
|
||||
except:
|
||||
pass
|
||||
|
||||
14
source/scripts/argononeupsd.service
Normal file
14
source/scripts/argononeupsd.service
Normal file
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Argon Industria UPS Battery Service
|
||||
DefaultDependencies=no
|
||||
|
||||
[Install]
|
||||
WantedBy=sysinit.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
KillSignal=SIGINT
|
||||
TimeoutStopSec=8
|
||||
Restart=on-failure
|
||||
WorkingDirectory=/etc/argon/ups/
|
||||
ExecStart=/usr/bin/python3 /etc/argon/argononeupsd.py
|
||||
568
source/scripts/argonupsrtcd.py
Normal file
568
source/scripts/argonupsrtcd.py
Normal file
@@ -0,0 +1,568 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import json
|
||||
|
||||
import sys
|
||||
import datetime
|
||||
import math
|
||||
|
||||
import os
|
||||
import time
|
||||
import serial
|
||||
|
||||
from threading import Thread
|
||||
from queue import Queue
|
||||
|
||||
sys.path.append("/etc/argon/")
|
||||
import argonrtc
|
||||
|
||||
|
||||
#################
|
||||
# Common/Helpers
|
||||
#################
|
||||
#UPS_SERIALPORT="/dev/ttyUSB0"
|
||||
UPS_SERIALPORT="/dev/ttyACM0"
|
||||
UPS_LOGFILE="/dev/shm/upslog.txt"
|
||||
UPS_CMDFILE="/dev/shm/upscmd.txt"
|
||||
|
||||
RTC_CONFIGFILE = "/etc/argonupsrtc.conf"
|
||||
|
||||
|
||||
#############
|
||||
# RTC
|
||||
#############
|
||||
|
||||
def hexAsDec(hexval):
|
||||
return (hexval&0xF) + 10*((hexval>>4)&0xf)
|
||||
|
||||
def decAsHex(decval):
|
||||
return (decval%10) + (math.floor(decval/10)<<4)
|
||||
|
||||
# Returns RTC timestamp as datetime object
|
||||
def getDatetimeObj(dataobj, datakey):
|
||||
try:
|
||||
datetimearray = dataobj[datakey].split(" ")
|
||||
if len(datetimearray)>1:
|
||||
datearray = datetimearray[0].split("/")
|
||||
timearray = datetimearray[1].split(":")
|
||||
if len(datearray) == 3 and len(timearray) > 1:
|
||||
year = int(datearray[2])
|
||||
month = int(datearray[0])
|
||||
caldate = int(datearray[1])
|
||||
hour = int(timearray[0])
|
||||
minute = int(timearray[1])
|
||||
second = 0
|
||||
if len(timearray) > 2:
|
||||
second = int(timearray[2])
|
||||
return datetime.datetime(year, month, caldate, hour, minute, second)+argonrtc.getLocaltimeOffset()
|
||||
except:
|
||||
pass
|
||||
|
||||
return datetime.datetime(1999, 1, 1, 0, 0, 0)
|
||||
|
||||
|
||||
def getRTCpoweronschedule():
|
||||
outobj = ups_sendcmd("7")
|
||||
return getDatetimeObj(outobj, "schedule")
|
||||
|
||||
|
||||
def getRTCdatetime():
|
||||
outobj = ups_sendcmd("5")
|
||||
return getDatetimeObj(outobj, "time")
|
||||
|
||||
|
||||
# set RTC time using datetime object (Local time)
|
||||
def setRTCdatetime():
|
||||
# Set local time to UTC
|
||||
outobj = ups_sendcmd("3")
|
||||
return getDatetimeObj(outobj, "time")
|
||||
|
||||
|
||||
# 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
|
||||
return nextcommandtime
|
||||
|
||||
# Convert to RTC timezone
|
||||
alarmtime = nextcommandtime - argonrtc.getLocaltimeOffset()
|
||||
|
||||
outobj = ups_sendcmd("6 "+alarmtime.strftime("%Y %m %d %H %M"))
|
||||
return getDatetimeObj(outobj, "schedule")
|
||||
|
||||
|
||||
#############
|
||||
# Status
|
||||
#############
|
||||
|
||||
def ups_debuglog(typestr, logstr):
|
||||
try:
|
||||
UPS_DEBUGFILE="/dev/shm/upsdebuglog.txt"
|
||||
|
||||
tmpstrpadding = " "
|
||||
|
||||
with open(UPS_DEBUGFILE, "a") as txt_file:
|
||||
txt_file.write("["+datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")+"] "+typestr.upper()+" "+logstr.strip().replace("\n","\n"+tmpstrpadding)+"\n")
|
||||
except:
|
||||
pass
|
||||
|
||||
def ups_sendcmd(cmdstr):
|
||||
# status, version, time, schedule
|
||||
ups_debuglog("sendcmd", cmdstr)
|
||||
try:
|
||||
outstr = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
|
||||
|
||||
with open(UPS_CMDFILE, "w") as txt_file:
|
||||
txt_file.write(datetime.datetime.now().strftime("%Y%m%d%H%M%S")+"\n"+cmdstr+"\n")
|
||||
time.sleep(3)
|
||||
except:
|
||||
pass
|
||||
|
||||
outobj = ups_loadlogdata()
|
||||
try:
|
||||
ups_debuglog("sendcmd-response", json.dumps(outobj))
|
||||
except:
|
||||
pass
|
||||
|
||||
return outobj
|
||||
|
||||
def ups_loadlogdata():
|
||||
# status, version, time, schedule
|
||||
outobj = {}
|
||||
try:
|
||||
fp = open(UPS_LOGFILE, "r")
|
||||
logdata = fp.read()
|
||||
alllines = logdata.split("\n")
|
||||
ctr = 0
|
||||
while ctr < len(alllines):
|
||||
tmpval = alllines[ctr].strip()
|
||||
curinfo = tmpval.split(":")
|
||||
if len(curinfo) > 1:
|
||||
tmpattrib = curinfo[0].lower().split(" ")
|
||||
# The rest are assumed to be value
|
||||
outobj[tmpattrib[0]] = tmpval[(len(curinfo[0])+1):].strip()
|
||||
ctr = ctr + 1
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return outobj
|
||||
|
||||
def ups_check(readq):
|
||||
CMDSTARTBYTE=0xfe
|
||||
CMDCONTROLBYTECOUNT=3
|
||||
CHECKSTATUSLOOPFREQ=50
|
||||
|
||||
CMDsendrequest = [ 0xfe, 0, 0, 0xfe, 0xfe, 0, 0, 0xfe, 0, 0, 0]
|
||||
|
||||
lastcmdtime=""
|
||||
loopCtr = CHECKSTATUSLOOPFREQ
|
||||
sendcmdid = -1
|
||||
|
||||
ups_debuglog("serial", "Starting "+UPS_SERIALPORT)
|
||||
|
||||
updatedesktopicon("Argon UPS", "Argon UPS", "/etc/argon/ups/loading_0.png")
|
||||
|
||||
while True: # Outer loop to reconnect to device
|
||||
|
||||
qdata = ""
|
||||
if readq.empty() == False:
|
||||
qdata = readq.get()
|
||||
|
||||
try:
|
||||
ser = serial.Serial(UPS_SERIALPORT, 115200, timeout = 1)
|
||||
ser.close()
|
||||
ser.open()
|
||||
except Exception as mainerr:
|
||||
try:
|
||||
ups_debuglog("serial-mainerror", str(mainerr))
|
||||
except:
|
||||
ups_debuglog("serial-mainerror", "Error")
|
||||
# Give time before retry
|
||||
time.sleep(10)
|
||||
continue
|
||||
|
||||
|
||||
previconfile = ""
|
||||
statusstr = ""
|
||||
device_battery=0
|
||||
device_charging=0
|
||||
device_chargecurrent=-1
|
||||
device_version=-1
|
||||
device_rtctime= [-1, -1, -1, -1, -1, -1]
|
||||
device_powerontime= [-1, -1, -1, -1, -1]
|
||||
|
||||
while True: # Command loop
|
||||
try:
|
||||
if sendcmdid < 0:
|
||||
cmddatastr = ""
|
||||
try:
|
||||
fp = open(UPS_CMDFILE, "r")
|
||||
cmdlog = fp.read()
|
||||
alllines = cmdlog.split("\n")
|
||||
if len(alllines) > 1:
|
||||
if lastcmdtime != alllines[0]:
|
||||
lastcmdtime=alllines[0]
|
||||
cmddatastr=alllines[1]
|
||||
tmpcmdarray = cmddatastr.split(" ")
|
||||
sendcmdid = int(tmpcmdarray[0])
|
||||
if sendcmdid == 3:
|
||||
# Get/rebuild time here to minimize delay/time gap
|
||||
newrtcdatetime = datetime.datetime.now() - argonrtc.getLocaltimeOffset()
|
||||
cmddatastr = ("3 "+newrtcdatetime.strftime("%Y %m %d %H %M %S"))
|
||||
tmpcmdarray = cmddatastr.split(" ")
|
||||
if len(tmpcmdarray) != 7:
|
||||
cmddatastr = ""
|
||||
sendcmdid = 0
|
||||
elif sendcmdid == 6:
|
||||
if len(tmpcmdarray) != 6:
|
||||
cmddatastr = ""
|
||||
sendcmdid = 0
|
||||
except OSError:
|
||||
cmddatastr = ""
|
||||
|
||||
if cmddatastr == "":
|
||||
if loopCtr >= CHECKSTATUSLOOPFREQ:
|
||||
# Check Battery Status
|
||||
sendcmdid = 0
|
||||
loopCtr = 0
|
||||
else:
|
||||
loopCtr = loopCtr + 1
|
||||
if loopCtr == 2:
|
||||
sendcmdid = 5 # Get RTC Time
|
||||
elif loopCtr == 3:
|
||||
sendcmdid = 7 # Get Power on Time
|
||||
elif loopCtr == 4:
|
||||
sendcmdid = 4 # Get Version
|
||||
elif loopCtr == 5:
|
||||
sendcmdid = 2 # Get Charge Current
|
||||
elif (loopCtr&1) == 0:
|
||||
sendcmdid = 0 # Check Battery Status
|
||||
|
||||
if sendcmdid >= 0:
|
||||
sendSize = 0
|
||||
cmdSize = 0
|
||||
if len(cmddatastr) > 0:
|
||||
# set RTC Time (3, 6 bytes)
|
||||
# set Power of Time (6, 5 bytes)
|
||||
tmpcmdarray = cmddatastr.split(" ")
|
||||
CMDsendrequest[1] = len(tmpcmdarray) - 1 # Length
|
||||
CMDsendrequest[2] = sendcmdid
|
||||
|
||||
cmdSize = CMDsendrequest[1] + 4
|
||||
|
||||
# Copy payload
|
||||
tmpdataidx = cmdSize - 1 # Start at end
|
||||
while tmpdataidx > 3:
|
||||
tmpdataidx = tmpdataidx - 1
|
||||
if tmpdataidx == 3 and (sendcmdid == 3 or sendcmdid == 6):
|
||||
tmpval = int(tmpcmdarray[tmpdataidx-2])
|
||||
if tmpval >= 2000:
|
||||
tmpval = tmpval - 2000
|
||||
else:
|
||||
tmpval = 0
|
||||
CMDsendrequest[tmpdataidx] = decAsHex(tmpval)
|
||||
else:
|
||||
CMDsendrequest[tmpdataidx] = decAsHex(int(tmpcmdarray[tmpdataidx-2]))
|
||||
|
||||
datasum = 0
|
||||
tmpdataidx = cmdSize - 1
|
||||
while tmpdataidx > 0:
|
||||
tmpdataidx = tmpdataidx - 1
|
||||
datasum = (datasum+CMDsendrequest[tmpdataidx]) & 0xff
|
||||
|
||||
CMDsendrequest[cmdSize-1] = datasum
|
||||
sendSize = ser.write(serial.to_bytes(CMDsendrequest[0:cmdSize]))
|
||||
|
||||
ups_debuglog("serial-out-cmd", serial.to_bytes(CMDsendrequest[0:cmdSize]).hex(" "))
|
||||
|
||||
else:
|
||||
# Default Get/Read command
|
||||
CMDsendrequest[1] = 0 # Length
|
||||
CMDsendrequest[2] = sendcmdid
|
||||
CMDsendrequest[3] = (sendcmdid+CMDsendrequest[0]) & 0xff
|
||||
sendSize = ser.write(serial.to_bytes(CMDsendrequest[0:4]))
|
||||
cmdSize = CMDsendrequest[1] + 4
|
||||
|
||||
#ups_debuglog("serial-out-def", serial.to_bytes(CMDsendrequest[0:4]).hex(" "))
|
||||
|
||||
if cmdSize > 0:
|
||||
sendcmdid=-1
|
||||
if sendSize == cmdSize:
|
||||
# Give time to respond
|
||||
time.sleep(1)
|
||||
else:
|
||||
break
|
||||
|
||||
# read incoming data
|
||||
readOut = ser.read()
|
||||
|
||||
if len(readOut) == 0:
|
||||
continue
|
||||
|
||||
readdatalen = 1
|
||||
while True:
|
||||
tmpreadlen = ser.inWaiting() # Check remaining byte size
|
||||
if tmpreadlen < 1:
|
||||
break
|
||||
readOut += ser.read(tmpreadlen)
|
||||
readdatalen += tmpreadlen
|
||||
|
||||
readintarray = [tmpint for tmpint in readOut]
|
||||
|
||||
if len(cmddatastr) > 0:
|
||||
ups_debuglog("serial-in ", readOut.hex(" "))
|
||||
cmddatastr = ""
|
||||
# Parse command stream
|
||||
tmpidx = 0
|
||||
while tmpidx < readdatalen:
|
||||
if readintarray[tmpidx] == CMDSTARTBYTE and tmpidx + CMDCONTROLBYTECOUNT < readdatalen:
|
||||
# Cmd format: Min 4 bytes
|
||||
# tmpidx tmpidx+1 tmpidx+2
|
||||
# 0xfe (byte count) (cmd ID) (payload; byte count) (datasum)
|
||||
|
||||
tmpdatalen = readintarray[tmpidx+1]
|
||||
tmpcmd = readintarray[tmpidx+2]
|
||||
if tmpidx + CMDCONTROLBYTECOUNT + tmpdatalen < readdatalen:
|
||||
# Validate datasum
|
||||
datasum = 0
|
||||
tmpdataidx = tmpidx + tmpdatalen + CMDCONTROLBYTECOUNT
|
||||
while tmpdataidx > tmpidx:
|
||||
tmpdataidx = tmpdataidx - 1
|
||||
datasum = (datasum+readintarray[tmpdataidx]) & 0xff
|
||||
if datasum != readintarray[tmpidx + tmpdatalen + CMDCONTROLBYTECOUNT]:
|
||||
# Invalid sum
|
||||
pass
|
||||
else:
|
||||
needsupdate=False
|
||||
if tmpcmd == 0:
|
||||
# Check State
|
||||
if tmpdatalen >= 2:
|
||||
needsupdate=True
|
||||
tmp_battery = readintarray[tmpidx+CMDCONTROLBYTECOUNT]
|
||||
if tmp_battery>100:
|
||||
tmp_battery=100
|
||||
elif tmp_battery<1:
|
||||
tmp_battery=0
|
||||
tmp_charging = readintarray[tmpidx+CMDCONTROLBYTECOUNT+1]
|
||||
#ups_debuglog("battery-data", str(tmp_charging)+" "+str(tmp_battery))
|
||||
|
||||
if tmp_charging != device_charging or tmp_battery!=device_battery:
|
||||
device_battery=tmp_battery
|
||||
device_charging=tmp_charging
|
||||
tmpiconfile = "/etc/argon/ups/"
|
||||
|
||||
icontitle = "Argon UPS"
|
||||
if device_charging == 0:
|
||||
if device_battery==100:
|
||||
statusstr = "Charged"
|
||||
#tmpiconfile = tmpiconfile+"battery_plug"
|
||||
else:
|
||||
#icontitle = str(device_battery)+"%"+" Full"
|
||||
statusstr = "Charging"
|
||||
#tmpiconfile = tmpiconfile+"battery_charging"
|
||||
tmpiconfile = tmpiconfile+"charge_"+str(device_battery)
|
||||
else:
|
||||
#icontitle = str(device_battery)+"%"+" Left"
|
||||
statusstr = "Battery"
|
||||
tmp_battery = round(tmp_battery/20)
|
||||
if tmp_battery > 4:
|
||||
tmp_battery = 4
|
||||
#tmpiconfile = tmpiconfile+"battery_"+str(tmp_battery)
|
||||
tmpiconfile = tmpiconfile+"discharge_"+str(device_battery)
|
||||
tmpiconfile = tmpiconfile + ".png"
|
||||
|
||||
statusstr = statusstr + " " + str(device_battery)+"%"
|
||||
|
||||
#ups_debuglog("battery-info", statusstr)
|
||||
|
||||
# Add/update desktop icons too; add check to minimize write
|
||||
if previconfile != tmpiconfile:
|
||||
updatedesktopicon(icontitle, statusstr, tmpiconfile)
|
||||
previconfile = tmpiconfile
|
||||
|
||||
elif tmpcmd == 2:
|
||||
# Charge Current
|
||||
if tmpdatalen >= 2:
|
||||
device_chargecurrent = ((readintarray[tmpidx+CMDCONTROLBYTECOUNT])<<8) | readintarray[tmpidx+CMDCONTROLBYTECOUNT+1]
|
||||
elif tmpcmd == 4:
|
||||
# Version
|
||||
if tmpdatalen >= 1:
|
||||
needsupdate=True
|
||||
device_version = readintarray[tmpidx+CMDCONTROLBYTECOUNT]
|
||||
elif tmpcmd == 5:
|
||||
# RTC Time
|
||||
if tmpdatalen >= 6:
|
||||
needsupdate=True
|
||||
tmpdataidx = 0
|
||||
while tmpdataidx < 6:
|
||||
device_rtctime[tmpdataidx] = hexAsDec(readintarray[tmpidx+CMDCONTROLBYTECOUNT+tmpdataidx])
|
||||
tmpdataidx = tmpdataidx + 1
|
||||
elif tmpcmd == 7:
|
||||
# Power On Time
|
||||
if tmpdatalen >= 5:
|
||||
needsupdate=True
|
||||
tmpdataidx = 0
|
||||
while tmpdataidx < 5:
|
||||
device_powerontime[tmpdataidx] = hexAsDec(readintarray[tmpidx+CMDCONTROLBYTECOUNT+tmpdataidx])
|
||||
tmpdataidx = tmpdataidx + 1
|
||||
elif tmpcmd == 8:
|
||||
# Send Acknowledge
|
||||
sendcmdid = tmpcmd
|
||||
elif tmpcmd == 3:
|
||||
# New RTC Time set
|
||||
sendcmdid = 5
|
||||
elif tmpcmd == 6:
|
||||
# New Power On Time set
|
||||
sendcmdid = 7
|
||||
|
||||
if needsupdate==True:
|
||||
# Log File
|
||||
otherstr = ""
|
||||
if device_version >= 0:
|
||||
otherstr = otherstr + " Version:"+str(device_version)+"\n"
|
||||
if device_rtctime[0] >= 0:
|
||||
otherstr = otherstr + " Time:"+str(device_rtctime[1])+"/"+str(device_rtctime[2])+"/"+str(device_rtctime[0]+2000)+" "+str(device_rtctime[3])+":"+str(device_rtctime[4])+":"+str(device_rtctime[5])+"\n"
|
||||
if device_powerontime[1] > 0:
|
||||
otherstr = otherstr + " Schedule:"+str(device_powerontime[1])+"/"+str(device_powerontime[2])+"/"+str(device_powerontime[0]+2000)+" "+str(device_powerontime[3])+":"+str(device_powerontime[4])+"\n"
|
||||
with open(UPS_LOGFILE, "w") as txt_file:
|
||||
txt_file.write("Status as of: "+time.asctime(time.localtime(time.time()))+"\n Power:"+statusstr+"\n"+otherstr)
|
||||
#ups_debuglog("status-update", "\n Power:"+statusstr+"\n"+otherstr)
|
||||
# Point to datasum, so next loop iteration will be correct
|
||||
tmpidx = tmpidx + tmpdatalen + CMDCONTROLBYTECOUNT
|
||||
tmpidx = tmpidx + 1
|
||||
except Exception as e:
|
||||
try:
|
||||
ups_debuglog("serial-error", str(e))
|
||||
except:
|
||||
ups_debuglog("serial-error", "Error")
|
||||
break
|
||||
|
||||
def updatedesktopicon(icontitle, statusstr, tmpiconfile):
|
||||
try:
|
||||
tmp = os.popen("find /home -maxdepth 1 -type d").read()
|
||||
alllines = tmp.split("\n")
|
||||
for curfolder in alllines:
|
||||
if curfolder == "/home" or curfolder == "":
|
||||
continue
|
||||
#ups_debuglog("desktop-update-path", curfolder)
|
||||
#ups_debuglog("desktop-update-text", statusstr)
|
||||
#ups_debuglog("desktop-update-icon", tmpiconfile)
|
||||
with open(curfolder+"/Desktop/argonone-ups.desktop", "w") as txt_file:
|
||||
txt_file.write("[Desktop Entry]\nName="+icontitle+"\nComment="+statusstr+"\nIcon="+tmpiconfile+"\nExec=lxterminal --working-directory="+curfolder+"/ -t \"Argon UPS\" -e \"/etc/argon/argonone-upsconfig.sh argonupsrtc\"\nType=Application\nEncoding=UTF-8\nTerminal=false\nCategories=None;\n")
|
||||
except Exception as desktope:
|
||||
#pass
|
||||
try:
|
||||
ups_debuglog("desktop-update-error", str(desktope))
|
||||
except:
|
||||
ups_debuglog("desktop-update-error", "Error")
|
||||
|
||||
|
||||
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()
|
||||
if cmd == "GETBATTERY":
|
||||
#outobj = ups_sendcmd("0")
|
||||
outobj = ups_loadlogdata()
|
||||
try:
|
||||
print(outobj["power"])
|
||||
except:
|
||||
print("Error retrieving battery status")
|
||||
|
||||
elif cmd == "GETRTCSCHEDULE":
|
||||
tmptime = getRTCpoweronschedule()
|
||||
if tmptime.year > 1999:
|
||||
print("Alarm Setting:", tmptime)
|
||||
else:
|
||||
print("Alarm Setting: None")
|
||||
|
||||
elif cmd == "GETRTCTIME":
|
||||
tmptime = getRTCdatetime()
|
||||
if tmptime.year > 1999:
|
||||
print("RTC Time:", tmptime)
|
||||
else:
|
||||
print("Error reading RTC Time")
|
||||
|
||||
elif cmd == "UPDATERTCTIME":
|
||||
tmptime = setRTCdatetime()
|
||||
if tmptime.year > 1999:
|
||||
print("RTC Time:", tmptime)
|
||||
else:
|
||||
print("Error reading RTC Time")
|
||||
|
||||
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":
|
||||
ipcq = Queue()
|
||||
|
||||
tmprtctime = getRTCdatetime()
|
||||
if tmprtctime.year >= 2000:
|
||||
argonrtc.updateSystemTime(tmprtctime)
|
||||
commandschedulelist = argonrtc.formCommandScheduleList(argonrtc.loadConfigList(RTC_CONFIGFILE))
|
||||
nextrtcalarmtime = setNextAlarm(commandschedulelist, datetime.datetime.now())
|
||||
|
||||
t1 = Thread(target = ups_check, args =(ipcq, ))
|
||||
t1.start()
|
||||
|
||||
serviceloop = True
|
||||
while serviceloop==True:
|
||||
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)
|
||||
|
||||
ipcq.join()
|
||||
|
||||
|
||||
elif False:
|
||||
print("System Time: ", datetime.datetime.now())
|
||||
print("RTC Time: ", getRTCdatetime())
|
||||
10
source/scripts/argonupsrtcd.service
Normal file
10
source/scripts/argonupsrtcd.service
Normal file
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Argon UPS RTC Service
|
||||
After=multi-user.target
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
RemainAfterExit=true
|
||||
ExecStart=/usr/bin/python3 /etc/argon/argonupsrtcd.py SERVICE
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user