Compare commits

..

5 Commits

Author SHA1 Message Date
Oliver Kunze
9b506fd5f0 Update README.md 2025-06-11 14:30:11 +02:00
okunze
755a2ca262 Automated Change by GitHub Action 2025-06-11 12:21:48 +00:00
Oliver Kunze
1a7ab2e005 Update Check for updates.yml 2025-06-11 14:20:52 +02:00
Oliver Kunze
d3055fdb9c Update Check for updates.yml 2025-06-11 14:17:48 +02:00
Oliver Kunze
59472bd224 Update Check for updates.yml
- Update actions
- Download all mentioned files
2025-06-11 14:14:00 +02:00
48 changed files with 6383 additions and 32 deletions

View File

@@ -15,32 +15,210 @@ jobs:
steps:
# https://github.com/marketplace/actions/checkout
- name: Checkout
uses: actions/checkout@v3.5.3
- name: Create download folder
uses: actions/checkout@v4.2.2
- name: Create ./download folder
run: mkdir -p ./download
- name: Create ./download/oled folder
run: mkdir -p ./download/oled
- name: Create ./download/scripts folder
run: mkdir -p ./download/scripts
- name: Create ./download/tools folder
run: mkdir -p ./download/tools
# https://github.com/marketplace/actions/github-action-for-wget
- name: Github Action for wget (argon1.sh)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/argon1.sh -P ./download
- name: Github Action for wget (setntpserver.sh)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/tools/setntpserver.sh -P ./download/tools
- name: Github Action for wget (argon-rpi-eeprom-config-psu.py)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argon-rpi-eeprom-config-psu.py -P ./download/scripts
- name: Github Action for wget (argonone-upsconfig.sh)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argonone-upsconfig.sh -P ./download/scripts
- name: Github Action for wget (argonone-fanconfig.sh)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argonone-fanconfig.sh -P ./download/scripts
- name: Github Action for wget (argononed.py)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argononed.py -P ./download/scripts
- name: Github Action for wget (argononeoledd.service)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argononeoledd.service -P ./download/scripts
- name: Github Action for wget (argononed.service)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argononed.service -P ./download/scripts
- name: Github Action for wget (argonone-irconfig.sh)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argonone-irconfig.sh -P ./download/scripts
- name: Github Action for wget (argon-blstrdac.sh)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argon-blstrdac.sh -P ./download/scripts
- name: Github Action for wget (argonstatus.py)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argonstatus.py -P ./download/scripts
- name: Github Action for wget (argondashboard.py)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argondashboard.py -P ./download/scripts
- name: Github Action for wget (argon-status.sh)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argon-status.sh -P ./download/scripts
- name: Github Action for wget (argon-versioninfo.sh)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argon-versioninfo.sh -P ./download/scripts
- name: Github Action for wget (argonsysinfo.py)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argonsysinfo.py -P ./download/scripts
- name: Github Action for wget (argonregister-v1.py)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argonregister-v1.py -P ./download/scripts
- name: Github Action for wget (argonregister.py)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argonregister.py -P ./download/scripts
- name: Github Action for wget (argonpowerbutton-libgpiod.py)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argonpowerbutton-libgpiod.py -P ./download/scripts
- name: Github Action for wget (argonpowerbutton-rpigpio.py)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argonpowerbutton-rpigpio.py -P ./download/scripts
- name: Github Action for wget (argon-unitconfig.sh)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argon-unitconfig.sh -P ./download/scripts
- name: Github Action for wget (argoneon-rtcconfig.sh)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argoneon-rtcconfig.sh -P ./download/scripts
- name: Github Action for wget (argonrtc.py)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argonrtc.py -P ./download/scripts
- name: Github Action for wget (argoneond.py)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argoneond.py -P ./download/scripts
- name: Github Action for wget (argoneond.service)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argoneond.service -P ./download/scripts
- name: Github Action for wget (argoneonoled.py)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argoneonoled.py -P ./download/scripts
- name: Github Action for wget (argononeoled.py)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argononeoled.py -P ./download/scripts
- name: Github Action for wget (argoneon-oledconfig.sh)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argoneon-oledconfig.sh -P ./download/scripts
- name: Github Action for wget (font8x6.bin)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/oled/font8x6.bin -P ./download/oled
- name: Github Action for wget (font16x12.bin)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/oled/font16x12.bin -P ./download/oled
- name: Github Action for wget (font32x24.bin)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/oled/font32x24.bin -P ./download/oled
- name: Github Action for wget (font64x48)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/oled/font64x48.bin -P ./download/oled
- name: Github Action for wget (font16x8.bin)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/oled/font16x8.bin -P ./download/oled
- name: Github Action for wget (font24x16.bin)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/oled/font24x16.bin -P ./download/oled
- name: Github Action for wget (font48x32.bin)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/oled/font48x32.bin -P ./download/oled
- name: Github Action for wget (bgdefault.bin)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/oled/bgdefault.bin -P ./download/oled
- name: Github Action for wget (bgram.bin)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/oled/bgram.bin -P ./download/oled
- name: Github Action for wget (bgip.bin)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/oled/bgip.bin -P ./download/oled
- name: Github Action for wget (bgtemp.bin)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/oled/bgtemp.bin -P ./download/oled
- name: Github Action for wget (bgcpu.bin)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/oled/bgcpu.bin -P ./download/oled
- name: Github Action for wget (bgraid.bin)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/oled/bgraid.bin -P ./download/oled
- name: Github Action for wget (bgstorage.bin)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/oled/bgstorage.bin -P ./download/oled
- name: Github Action for wget (bgtime.bin)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/oled/bgtime.bin -P ./download/oled
- name: Github Action for wget (logo1v5.bin)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/oled/logo1v5.bin -P ./download/oled
- name: Github Action for wget (argon-uninstall.sh)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argon-uninstall.sh -P ./download/scripts
- name: Github Action for wget (argon-shutdown.sh)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/scripts/argon-shutdown.sh -P ./download/scripts
- name: Github Action for wget (ar1config.png)
uses: wei/wget@v1.1.1
with:
args: http://download.argon40.com/ar1config.png -P ./download
- name: Github Action for wget (ar1uninstall.png)
- name: Github Action for wget (argoneon.png)
uses: wei/wget@v1.1.1
with:
args: http://download.argon40.com/ar1uninstall.png -P ./download
- name: Github Action for wget (argonone-irconfig.sh)
uses: wei/wget@v1.1.1
with:
args: https://download.argon40.com/argonone-irconfig.sh -P ./download
args: http://download.argon40.com/argoneon.png -P ./download
- name: Delete ./source
run: rm -rf ./source
- name: Rename ./download to ./source
run: mv ./download ./source
# https://github.com/marketplace/actions/git-auto-commit
- name: Git Auto Commit
uses: stefanzweifel/git-auto-commit-action@v4.16.0
uses: stefanzweifel/git-auto-commit-action@v6.0.1
with:
commit_message: Automated Change by GitHub Action

View File

@@ -1,8 +1,8 @@
# Argon ONE (V2) Pi 4 Script
# Argon ONE Script
I have been using the [Argon ONE (V2) Case for Raspberry Pi 4](https://www.argon40.com/collections/raspberry-pi-cases "Argon ONE (V2) Case for Raspberry Pi 4") for a long time and I am very happy with it.
I have been using the [Argon ONE Cases for Raspberry Pi](https://www.argon40.com/collections/raspberry-pi-cases "Argon ONE Cases for Raspberry Pi") for a long time and I am very happy with them.
To be able to use the case to the full extent, it is recommended to install the [script](https://download.argon40.com/argon1.sh "https://download.argon40.com/argon1.sh") offered by [Argon 40](https://www.argon40.com "https://www.argon40.com").
To be able to use the cases to the full extent, it is recommended to install the [script](https://download.argon40.com/argon1.sh "https://download.argon40.com/argon1.sh") offered by [Argon 40](https://www.argon40.com "https://www.argon40.com").
I have saved a copy here along with the instructions to have a copy in case [Argon 40](https://www.argon40.com "https://www.argon40.com") no longer offers it themselves.
@@ -13,19 +13,19 @@ You can find them here:
* Argon 40 Website: [https://www.argon40.com](https://www.argon40.com "https://www.argon40.com")
* Argon 40 Github: [https://github.com/Argon40Tech](https://github.com/Argon40Tech "https://github.com/Argon40Tech")
## How to install Argon ONE (V2) Pi 4 Power Button & Fan Control
## How to install Argon ONE Pi Power Button & Fan Control
### Prerequisites
* [Raspberry Pi 4 Model B (2GB, 4GB or 8GB version)](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/ "Raspberry Pi 4 Model B")
* [Raspberry Pi](https://www.raspberrypi.org/products/ "Raspberry Pi")
* [Raspberry Pi OS (previously called Raspbian)](https://www.raspberrypi.org/downloads/ "Raspberry Pi OS") installed on microSD card
* [Argon ONE (V2) Case for Raspberry Pi 4](https://www.argon40.com/collections/raspberry-pi-cases "Argon ONE (V2) Case for Raspberry Pi 4")
* [Argon ONE Cases for Raspberry Pi](https://www.argon40.com/collections/raspberry-pi-cases "Argon ONE Cases for Raspberry Pi")
### Installing
1. Connect to the internet.
2. Open "Terminal" in Raspbian.
3. Type the text below in the "Terminal" to initiate installation of Argon ONE (V2) Pi 4 script.
3. Type the text below in the "Terminal" to initiate installation of Argon ONE Pi script.
```
curl https://download.argon40.com/argon1.sh | bash
@@ -35,9 +35,9 @@ You can find them here:
## Usage Instructions
### Argon ONE (V2) Pi 4 Power Button Functions
### Argon ONE Pi Power Button Functions
ARGON ONE (V2) PI 4 STATE | ACTION | FUNCTION
ARGON ONE PI STATE | ACTION | FUNCTION
:------------------: | :----: | :------:
OFF | Short Press | Turn ON
ON | Long Press (>= 3 s) | Soft Shutdown and Power Cut
@@ -45,8 +45,8 @@ ON | Short Press (< 3 s) | Nothing
ON | Double Tap | Reboot
ON | Long Press (>= 5 s) | Forced Shutdown
### Argon ONE (V2) Pi 4 Fan Speed
Upon installation of the Argon ONE (V2) Pi 4 script by default, the settings of the Argon ONE (V2) Pi 4 cooling system are as follows:
### Argon ONE Pi Fan Speed
Upon installation of the Argon ONE Pi script by default, the settings of the Argon ONE Pi cooling system are as follows:
CPU TEMP | FAN POWER
:------: | :-------:
@@ -54,7 +54,7 @@ CPU TEMP | FAN POWER
60 C | 55%
65 C | 100%
However, you may change or configure the FAN to your desired settings by clicking the Argon ONE (V2) Pi 4 Config icon on your Desktop.
However, you may change or configure the FAN to your desired settings by clicking the Argon ONE Pi Config icon on your Desktop.
Or via "Terminal" by typing and following the specified format:
@@ -62,9 +62,9 @@ Or via "Terminal" by typing and following the specified format:
argonone-config
```
## Uninstalling Argon ONE (V2) Pi 4 Script
## Uninstalling Argon ONE Pi Script
To uninstall the Argon ONE (V2) Pi 4 script you may do so by clicking the Argon One (V2) Pi 4 Uninstall icon on your Desktop.
To uninstall the Argon ONE Pi script you may do so by clicking the Argon One Pi Uninstall icon on your Desktop.
You may also remove the script via "Terminal" by typing.
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

BIN
source/argoneon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
source/oled/bgcpu.bin Normal file

Binary file not shown.

BIN
source/oled/bgdefault.bin Normal file

Binary file not shown.

BIN
source/oled/bgip.bin Normal file

Binary file not shown.

BIN
source/oled/bgraid.bin Normal file

Binary file not shown.

BIN
source/oled/bgram.bin Normal file

Binary file not shown.

BIN
source/oled/bgstorage.bin Normal file

Binary file not shown.

BIN
source/oled/bgtemp.bin Normal file

Binary file not shown.

BIN
source/oled/bgtime.bin Normal file

Binary file not shown.

BIN
source/oled/font16x12.bin Normal file

Binary file not shown.

BIN
source/oled/font16x8.bin Normal file

Binary file not shown.

BIN
source/oled/font24x16.bin Normal file

Binary file not shown.

BIN
source/oled/font32x24.bin Normal file

Binary file not shown.

BIN
source/oled/font48x32.bin Normal file

Binary file not shown.

BIN
source/oled/font64x48.bin Normal file

Binary file not shown.

BIN
source/oled/font8x6.bin Normal file

Binary file not shown.

BIN
source/oled/logo1v5.bin Normal file

Binary file not shown.

View File

@@ -0,0 +1,194 @@
#!/bin/bash
if [ -e /boot/firmware/config.txt ] ; then
FIRMWARE=/firmware
else
FIRMWARE=
fi
CONFIG=/boot${FIRMWARE}/config.txt
# Check if Raspbian
CHECKPLATFORM="Others"
if [ -f "/etc/os-release" ]
then
source /etc/os-release
if [ "$ID" = "raspbian" ]
then
CHECKPLATFORM="Raspbian"
elif [ "$ID" = "debian" ]
then
# For backwards compatibility, continue using raspbian
CHECKPLATFORM="Raspbian"
fi
fi
echo "------------------------------------"
echo " Argon BLSTR DAC Configuration Tool"
echo "------------------------------------"
get_number () {
read curnumber
if [ -z "$curnumber" ]
then
echo "-2"
return
elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]]
then
if [ $curnumber -lt 0 ]
then
echo "-1"
return
elif [ $curnumber -gt 100 ]
then
echo "-1"
return
fi
echo $curnumber
return
fi
echo "-1"
return
}
irexecrcfile=/etc/lirc/irexec.lircrc
irexecshfile=/etc/argon/argonirexec
irdecodefile=/etc/argon/argonirdecoder
kodiuserdatafolder="$HOME/.kodi/userdata"
kodilircmapfile="$kodiuserdatafolder/Lircmap.xml"
remotemode=""
needinstallation=1
CONFIGSETTING="dtoverlay=hifiberry-dacplus,slave"
if grep -q -E "$CONFIGSETTING" $CONFIG
then
# Already installed
needinstallation=0
fi
loopflag=1
while [ $loopflag -eq 1 ]
do
echo
echo "Select option:"
if [ $needinstallation -eq 1 ]
then
echo " 1. Enable BLSTR DAC"
echo " 2. Cancel"
echo -n "Enter Number (1-2):"
else
echo " 1. Select audio configuration"
echo " 2. Disable BLSTR DAC"
echo " 3. Cancel"
echo -n "Enter Number (1-3):"
fi
newmode=$( get_number )
if [[ $newmode -ge 1 && $newmode -le 3 ]]
then
if [[ $needinstallation -eq 1 && $newmode -ge 3 ]]
then
# Invalid option
loopflag=1
# Uninstall
else
loopflag=0
if [ $needinstallation -eq 1 ]
then
if [ $newmode -eq 2 ]
then
# Cancel
newmode=4
fi
else
if [ $newmode -eq 1 ]
then
# Audio Conf
newmode=3
fi
fi
fi
fi
done
needrestart=0
echo
if [ $newmode -eq 2 ]
then
# Uninstall
blstrdactmpconfigfile=/dev/shm/argonblstrdacconfig.txt
cat $CONFIG | grep -v "$CONFIGSETTING" > $blstrdactmpconfigfile
cat $blstrdactmpconfigfile | sudo tee $CONFIG 1> /dev/null
sudo rm $blstrdactmpconfigfile
echo "Uninstall Completed"
echo
needrestart=1
elif [ $newmode -eq 3 ]
then
# Audio Conf
loopflag=1
while [ $loopflag -eq 1 ]
do
echo
echo "Select audio configuration:"
echo " 1. PulseAudio"
echo " 2. Pipewire"
echo " 3. Cancel"
echo -n "Enter Number (1-3):"
newmode=$( get_number )
if [[ $newmode -ge 1 && $newmode -le 3 ]]
then
loopflag=0
fi
done
if [[ $newmode -ge 1 && $newmode -le 2 ]]
then
sudo raspi-config nonint do_audioconf $newmode
else
echo "Cancelled"
fi
elif [ $newmode -eq 1 ]
then
# Install
echo "$CONFIGSETTING" | sudo tee -a $CONFIG 1> /dev/null
#sudo raspi-config nonint do_audioconf 1
#systemctl --global -q disable pipewire-pulse
#systemctl --global -q disable wireplumber
#systemctl --global -q enable pulseaudio
#if [ -e /etc/alsa/conf.d/99-pipewire-default.conf ] ; then
# rm /etc/alsa/conf.d/99-pipewire-default.conf
#fi
echo "Please run configuration and choose different audio configuration if there are problems"
needrestart=1
else
echo "Cancelled"
#exit
fi
echo
#echo "Thank you."
if [ $needrestart -eq 1 ]
then
echo "Changes should take after reboot."
fi

View File

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

View File

@@ -0,0 +1,22 @@
#!/bin/bash
pythonbin=/usr/bin/python3
argononefanscript=/etc/argon/argononed.py
argoneonrtcscript=/etc/argon/argoneond.py
argonirconfigscript=/etc/argon/argonone-ir
if [ ! -z "$1" ]
then
$pythonbin $argononefanscript FANOFF
if [ "$1" = "poweroff" ] || [ "$1" = "halt" ]
then
if [ -f $argonirconfigscript ]
then
if [ -f $argoneonrtcscript ]
then
$pythonbin $argoneonrtcscript SHUTDOWN
fi
$pythonbin $argononefanscript SHUTDOWN
fi
fi
fi

View File

@@ -0,0 +1,106 @@
#!/bin/bash
get_number () {
read curnumber
if [ -z "$curnumber" ]
then
echo "-2"
return
elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]]
then
if [ $curnumber -lt 0 ]
then
echo "-1"
return
elif [ $curnumber -gt 100 ]
then
echo "-1"
return
fi
echo $curnumber
return
fi
echo "-1"
return
}
INSTALLATIONFOLDER=/etc/argon
pythonbin="sudo /usr/bin/python3"
argonstatusscript=$INSTALLATIONFOLDER/argonstatus.py
argondashboardscript=$INSTALLATIONFOLDER/argondashboard.py
argononefanscript=$INSTALLATIONFOLDER/argononed.py
argoneonrtcscript=$INSTALLATIONFOLDER/argoneond.py
echo "--------------------------"
echo " Argon System Information"
echo "--------------------------"
loopflag=1
while [ $loopflag -eq 1 ]
do
echo
echo " 1. Temperatures"
echo " 2. CPU Utilization"
echo " 3. Storage Capacity"
echo " 4. RAM"
echo " 5. IP Address"
lastoption=5
if [ -f $argononefanscript ]
then
echo " 6. Fan Speed"
lastoption=6
fi
if [ -f "$argoneonrtcscript" ]
then
echo " 7. RTC Schedules"
echo " 8. RAID"
lastoption=8
fi
lastoption=$((lastoption + 1))
echo " ${lastoption}. Dashboard"
echo
echo " 0. Back"
echo -n "Enter Number (0-${lastoption}):"
newmode=$( get_number )
if [ $newmode -eq 0 ]
then
loopflag=0
elif [ $newmode -gt 0 ] && [ $newmode -le $lastoption ]
then
echo "--------------------------"
if [ $newmode -eq $lastoption ]
then
$pythonbin $argondashboardscript
elif [ $newmode -eq 1 ]
then
$pythonbin $argonstatusscript "temperature"
elif [ $newmode -eq 2 ]
then
$pythonbin $argonstatusscript "cpu usage"
elif [ $newmode -eq 3 ]
then
$pythonbin $argonstatusscript "storage"
elif [ $newmode -eq 4 ]
then
$pythonbin $argonstatusscript "ram"
elif [ $newmode -eq 5 ]
then
$pythonbin $argonstatusscript "ip"
elif [ $newmode -eq 6 ]
then
$pythonbin $argonstatusscript "temperature" "fan configuration" "fan speed"
elif [ $newmode -eq 7 ]
then
$pythonbin $argoneonrtcscript GETSCHEDULELIST
elif [ $newmode -eq 8 ]
then
$pythonbin $argonstatusscript "raid"
fi
echo "--------------------------"
fi
done

View File

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

View File

@@ -0,0 +1,105 @@
#!/bin/bash
unitconfigfile=/etc/argonunits.conf
get_number () {
read curnumber
if [ -z "$curnumber" ]
then
echo "-2"
return
elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]]
then
if [ $curnumber -lt 0 ]
then
echo "-1"
return
elif [ $curnumber -gt 100 ]
then
echo "-1"
return
fi
echo $curnumber
return
fi
echo "-1"
return
}
saveconfig () {
echo "#" > $unitconfigfile
echo "# Argon Unit Configuration" >> $unitconfigfile
echo "#" >> $unitconfigfile
echo "temperature=$1" >> $unitconfigfile
}
updateconfig=1
unitloopflag=1
while [ $unitloopflag -eq 1 ]
do
if [ $updateconfig -eq 1 ]
then
. $unitconfigfile
fi
updateconfig=0
if [ -z "$temperature" ]
then
temperature="C"
updateconfig=1
fi
# Write default values to config file, daemon already uses default so no need to restart service
if [ $updateconfig -eq 1 ]
then
saveconfig $temperature
updateconfig=0
fi
echo "-----------------------------"
echo "Argon Display Units"
echo "-----------------------------"
echo "Choose from the list:"
echo " 1. Temperature: $temperature"
echo
echo " 0. Back"
echo -n "Enter Number (0-1):"
newmode=$( get_number )
if [ $newmode -eq 0 ]
then
unitloopflag=0
elif [ $newmode -eq 1 ]
then
echo
echo "-----------------------------"
echo "Temperature Display"
echo "-----------------------------"
echo "Choose from the list:"
echo " 1. Celsius"
echo " 2. Fahrenheit"
echo
echo " 0. Cancel"
echo -n "Enter Number (0-2):"
cmdmode=$( get_number )
if [ $cmdmode -eq 1 ]
then
temperature="C"
updateconfig=1
elif [ $cmdmode -eq 2 ]
then
temperature="F"
updateconfig=1
fi
fi
if [ $updateconfig -eq 1 ]
then
saveconfig $temperature
sudo systemctl restart argononed.service
fi
done
echo

View File

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

View File

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

View 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" == "raid" ]
then
pagename="RAID Information"
elif [ "$1" == "ram" ]
then
pagename="Available RAM"
elif [ "$1" == "temp" ]
then
pagename="CPU/HDD Temperature"
elif [ "$1" == "ip" ]
then
pagename="IP Address"
else
pagename="Invalid"
fi
}
configure_pagelist () {
pagemasterlist="clock cpu storage raid ram temp ip"
newscreenlist="$1"
pageloopflag=1
while [ $pageloopflag -eq 1 ]
do
echo "--------------------------------"
echo " OLED Pages "
echo "--------------------------------"
i=1
for curpage in $newscreenlist
do
get_pagename $curpage
echo " $i. Remove $pagename"
i=$((i+1))
done
if [ $i -eq 1 ]
then
echo " No page configured"
fi
echo
echo " $i. Add Page"
echo
echo " 0. Done"
echo -n "Enter Number (0-$i):"
cmdmode=$( get_number )
if [ $cmdmode -eq 0 ]
then
pageloopflag=0
elif [[ $cmdmode -eq $i ]]
then
echo "--------------------------------"
echo " Choose Page to Add"
echo "--------------------------------"
echo
i=1
for curpage in $pagemasterlist
do
get_pagename $curpage
echo " $i. $pagename"
i=$((i+1))
done
echo
echo " 0. Cancel"
echo -n "Enter Number (0-$i):"
pagenum=$( get_number )
if [[ $pagenum -ge 1 && $pagenum -le $i ]]
then
i=1
for curpage in $pagemasterlist
do
if [ $i -eq $pagenum ]
then
if [ "$newscreenlist" == "" ]
then
newscreenlist="$curpage"
else
newscreenlist="$newscreenlist $curpage"
fi
fi
i=$((i+1))
done
fi
elif [[ $cmdmode -ge 1 && $cmdmode -lt $i ]]
then
tmpscreenlist=""
i=1
for curpage in $newscreenlist
do
if [ ! $i -eq $cmdmode ]
then
tmpscreenlist="$tmpscreenlist $curpage"
fi
i=$((i+1))
done
if [ "$tmpscreenlist" == "" ]
then
newscreenlist="$tmpscreenlist"
else
# Remove leading space
newscreenlist="${tmpscreenlist:1}"
fi
fi
done
}
saveconfig () {
echo "#" > $oledconfigfile
echo "# Argon OLED Configuration" >> $oledconfigfile
echo "#" >> $oledconfigfile
echo "enabled=$1" >> $oledconfigfile
echo "switchduration=$2" >> $oledconfigfile
echo "screensaver=$3" >> $oledconfigfile
echo "screenlist=\"$4\"" >> $oledconfigfile
}
updateconfig=1
oledloopflag=1
while [ $oledloopflag -eq 1 ]
do
if [ $updateconfig -eq 1 ]
then
. $oledconfigfile
fi
updateconfig=0
if [ -z "$enabled" ]
then
enabled="Y"
updateconfig=1
fi
if [ -z "$screenlist" ]
then
screenlist="clock ip"
updateconfig=1
fi
if [ -z "$screensaver" ]
then
screensaver=120
updateconfig=1
fi
if [ -z "$switchduration" ]
then
switchduration=0
updateconfig=1
fi
# Write default values to config file, daemon already uses default so no need to restart service
if [ $updateconfig -eq 1 ]
then
saveconfig $enabled $switchduration $screensaver "$screenlist"
updateconfig=0
fi
displaystring=": Manually"
if [ $switchduration -gt 1 ]
then
displaystring="Every $switchduration secs"
fi
echo "-----------------------------"
echo "Argon OLED Configuration Tool"
echo "-----------------------------"
echo "Choose from the list:"
echo " 1. Switch Page $displaystring"
echo " 2. Configure Pages"
echo " 3. Turn OFF OLED Screen when unchanged after $screensaver secs"
echo " 4. Enable OLED Pages: $enabled"
echo
echo " 0. Back"
echo -n "Enter Number (0-3):"
newmode=$( get_number )
if [ $newmode -eq 0 ]
then
oledloopflag=0
elif [ $newmode -eq 1 ]
then
echo
echo -n "Enter # of Seconds (10-60, Manual if 0):"
cmdmode=$( get_number )
if [ $cmdmode -eq 0 ]
then
switchduration=0
updateconfig=1
elif [[ $cmdmode -ge 10 && $cmdmode -le 60 ]]
then
updateconfig=1
switchduration=$cmdmode
else
echo
echo "Invalid duration"
echo
fi
elif [ $newmode -eq 3 ]
then
echo
echo -n "Enter # of Seconds (60 or above, Manual if 0):"
cmdmode=$( get_number )
if [ $cmdmode -eq 0 ]
then
screensaver=0
updateconfig=1
elif [ $cmdmode -ge 60 ]
then
updateconfig=1
screensaver=$cmdmode
else
echo
echo "Invalid duration"
echo
fi
elif [ $newmode -eq 2 ]
then
configure_pagelist "$screenlist"
if [ ! "$screenlist" == "$newscreenlist" ]
then
screenlist="$newscreenlist"
updateconfig=1
fi
elif [ $newmode -eq 4 ]
then
echo
echo -n "Enable OLED Pages (Y/n)?:"
read -n 1 confirm
tmpenabled="$enabled"
if [[ "$confirm" == "n" || "$confirm" == "N" ]]
then
tmpenabled="N"
elif [[ "$confirm" == "y" || "$confirm" == "Y" ]]
then
tmpenabled="Y"
else
echo "Invalid response"
fi
if [ ! "$enabled" == "$tmpenabled" ]
then
enabled="$tmpenabled"
updateconfig=1
fi
fi
if [ $updateconfig -eq 1 ]
then
saveconfig $enabled $switchduration $screensaver "$screenlist"
sudo systemctl restart argononed.service
fi
done
echo

View File

@@ -0,0 +1,421 @@
#!/bin/bash
if [ -z "$1" ]
then
rtcdaemonname=argoneond
rtcconfigfile=/etc/argoneonrtc.conf
else
rtcdaemonname=${1}d
rtcconfigfile=/etc/${1}.conf
fi
pythonbin=/usr/bin/python3
argonrtcscript=/etc/argon/$rtcdaemonname.py
CHECKPLATFORM="Others"
# Check if Raspbian
grep -q -F 'Raspbian' /etc/os-release &> /dev/null
if [ $? -eq 0 ]
then
CHECKPLATFORM="Raspbian"
else
# Ubuntu needs elevated access for SMBus
pythonbin="sudo /usr/bin/python3"
fi
get_number () {
read curnumber
if [ -z "$curnumber" ]
then
echo "-2"
return
elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]]
then
if [ $curnumber -lt 0 ]
then
echo "-1"
return
elif [ $curnumber -gt 100 ]
then
echo "-1"
return
fi
echo $curnumber
return
fi
echo "-1"
return
}
configure_schedule () {
scheduleloopflag=1
while [ $scheduleloopflag -eq 1 ]
do
echo "--------------------------------"
echo " Configure Schedule "
echo "--------------------------------"
echo " 1. Add Schedule"
echo " or"
echo " Remove Schedule"
$pythonbin $argonrtcscript GETSCHEDULELIST
echo
echo " 99. Main Menu"
echo " 0. Back"
#echo "NOTE: You can also edit $rtcconfigfile directly"
echo -n "Enter Number:"
newmode=$( get_number )
if [ $newmode -eq 0 ]
then
scheduleloopflag=0
elif [ $newmode -eq 99 ]
then
scheduleloopflag=0
rtcloopflag=2
elif [ $newmode -eq 1 ]
then
configure_newschedule
elif [ $newmode -gt 1 ]
then
echo "CONFIRM SCHEDULE REMOVAL"
$pythonbin $argonrtcscript SHOWSCHEDULE $newmode
echo -n "Press Y to remove schedule #$newmode:"
read -n 1 confirm
if [ "$confirm" = "y" ]
then
confirm="Y"
fi
if [ "$confirm" = "Y" ]
then
$pythonbin $argonrtcscript REMOVESCHEDULE $newmode
sudo systemctl restart $rtcdaemonname.service
fi
echo ""
fi
done
}
configure_newschedule () {
cmdmode=1
hour=8
minute=0
minuteprefix=":0"
dayidx=0
repeat=1
subloopflag=1
while [ $subloopflag -eq 1 ]
do
minuteprefix=":0"
if [ $minute -ge 10 ]
then
minuteprefix=":"
fi
typestr="Shutdown"
if [ $cmdmode -eq 1 ]
then
typestr="Startup"
fi
daystr="Daily"
if [ $dayidx -eq 1 ]
then
daystr="Mon"
elif [ $dayidx -eq 2 ]
then
daystr="Tue"
elif [ $dayidx -eq 3 ]
then
daystr="Wed"
elif [ $dayidx -eq 4 ]
then
daystr="Thu"
elif [ $dayidx -eq 5 ]
then
daystr="Fri"
elif [ $dayidx -eq 6 ]
then
daystr="Sat"
elif [ $dayidx -eq 7 ]
then
daystr="Sun"
fi
repeatstr="Yes"
if [ $repeat -eq 0 ]
then
repeatstr="Once"
if [ $dayidx -eq 0 ]
then
daystr="Next Occurence"
fi
fi
echo "--------------------------------"
echo " Configure Schedule"
echo "--------------------------------"
echo " 1. Type: $typestr"
echo " 2. Set Time: $hour$minuteprefix$minute"
echo " 3. Repeating: $repeatstr"
echo " 4. Day: $daystr"
echo
echo " 5. Add Schedule"
echo
echo " 0. Cancel"
echo -n "Enter Number (0-5):"
setmode=$( get_number )
if [ $setmode -eq 0 ]
then
subloopflag=0
elif [ $setmode -eq 1 ]
then
echo "--------------------------------"
echo " Schedule Type "
echo "--------------------------------"
echo " 1. Startup"
echo " 2. Shutdown"
echo
echo -n "Enter Number (1-2):"
tmpval=$( get_number )
if [ $tmpval -eq 1 ]
then
cmdmode=1
elif [ $tmpval -eq 2 ]
then
cmdmode=0
else
echo "Invalid Option"
fi
elif [ $setmode -eq 2 ]
then
echo -n "Enter Hour (0-23):"
tmphour=$( get_number )
echo -n "Enter Minute (0-59):"
tmpminute=$( get_number )
if [[ $tmpminute -ge 0 && $tmpminute -le 59 && $tmphour -ge 0 && $tmphour -le 23 ]]
then
minute=$tmpminute
hour=$tmphour
else
echo "Invalid value(s)"
fi
elif [ $setmode -eq 3 ]
then
echo -n "Repeat schedule (Y/n)?:"
read -n 1 confirm
if [ "$confirm" = "y" ]
then
repeat=1
else
repeat=0
fi
elif [ $setmode -eq 4 ]
then
echo "Select Day of the Week:"
echo " 0. Daily"
echo " 1. Monday"
echo " 2. Tuesday"
echo " 3. Wednesday"
echo " 4. Thursday"
echo " 5. Friday"
echo " 6. Saturday"
echo " 7. Sunday"
echo -n "Enter Number (0-7):"
tmpval=$( get_number )
if [[ $tmpval -ge 0 && $tmpval -le 7 ]]
then
dayidx=$tmpval
else
echo "Invalid Option"
fi
elif [ $setmode -eq 5 ]
then
if [ $dayidx -eq 0 ]
then
cronweekday="*"
elif [ $dayidx -eq 7 ]
then
cronweekday="7"
else
cronweekday=$dayidx
fi
cmdcode="off"
if [ $cmdmode -eq 1 ]
then
cmdcode="on"
fi
echo "$minute $hour * * $cronweekday $cmdcode" >> $rtcconfigfile
sudo systemctl restart $rtcdaemonname.service
subloopflag=0
fi
done
}
configure_newcron () {
subloopflag=1
while [ $subloopflag -eq 1 ]
do
echo "--------------------------------"
echo " Schedule Type "
echo "--------------------------------"
echo " 1. Startup"
echo " 2. Shutdown"
echo
echo " 0. Cancel"
echo -n "Enter Number (0-2):"
cmdmode=$( get_number )
if [ $cmdmode -eq 0 ]
then
subloopflag=0
elif [[ $cmdmode -ge 1 && $cmdmode -le 2 ]]
then
cmdcode="on"
echo "--------------------------------"
if [ $cmdmode -eq 1 ]
then
echo " Schedule Startup"
else
echo " Schedule Shutdown"
cmdcode="off"
fi
echo "--------------------------------"
echo "Select Schedule:"
echo " 1. Hourly"
echo " 2. Daily"
echo " 3. Weekly"
echo " 4. Monthly"
echo
echo " 0. Back"
echo -n "Enter Number (0-4):"
newmode=$( get_number )
if [[ $newmode -ge 1 && $newmode -le 4 ]]
then
echo ""
if [ $cmdmode -eq 1 ]
then
echo "New Startup Schedule"
else
echo "New Shutdown Schedule"
fi
if [ $newmode -eq 1 ]
then
echo -n "Enter Minute (0-59):"
minute=$( get_number )
if [[ $minute -ge 0 && $minute -le 59 ]]
then
echo "$minute * * * * $cmdcode" >> $rtcconfigfile
sudo systemctl restart $rtcdaemonname.service
subloopflag=0
else
echo "Invalid value"
fi
elif [ $newmode -eq 2 ]
then
echo -n "Enter Hour (0-23):"
hour=$( get_number )
echo -n "Enter Minute (0-59):"
minute=$( get_number )
if [[ $minute -ge 0 && $minute -le 59 && $hour -ge 0 && $hour -le 23 ]]
then
echo "$minute $hour * * * $cmdcode" >> $rtcconfigfile
sudo systemctl restart $rtcdaemonname.service
subloopflag=0
else
echo "Invalid value(s)"
fi
elif [ $newmode -eq 3 ]
then
echo "Select Day of the Week:"
echo " 0. Sunday"
echo " 1. Monday"
echo " 2. Tuesday"
echo " 3. Wednesday"
echo " 4. Thursday"
echo " 5. Friday"
echo " 6. Saturday"
echo -n "Enter Number (0-6):"
weekday=$( get_number )
echo -n "Enter Hour (0-23):"
hour=$( get_number )
echo -n "Enter Minute (0-59):"
minute=$( get_number )
if [[ $minute -ge 0 && $minute -le 59 && $hour -ge 0 && $hour -le 23 && $weekday -ge 0 && $weekday -le 6 ]]
then
echo "$minute $hour * * $weekday $cmdcode" >> $rtcconfigfile
sudo systemctl restart $rtcdaemonname.service
subloopflag=0
else
echo "Invalid value(s)"
fi
elif [ $newmode -eq 4 ]
then
echo -n "Enter Date (1-31):"
monthday=$( get_number )
if [[ $monthday -ge 29 ]]
then
echo "WARNING: This schedule will not trigger for certain months"
fi
echo -n "Enter Hour (0-23):"
hour=$( get_number )
echo -n "Enter Minute (0-59):"
minute=$( get_number )
if [[ $minute -ge 0 && $minute -le 59 && $hour -ge 0 && $hour -le 23 && $monthday -ge 1 && $monthday -le 31 ]]
then
echo "$minute $hour $monthday * * $cmdcode" >> $rtcconfigfile
sudo systemctl restart $rtcdaemonname.service
subloopflag=0
else
echo "Invalid value(s)"
fi
fi
fi
fi
done
}
rtcloopflag=1
while [ $rtcloopflag -eq 1 ]
do
echo "----------------------------"
echo "Argon RTC Configuration Tool"
echo "----------------------------"
$pythonbin $argonrtcscript GETRTCTIME
echo "Choose from the list:"
echo " 1. Update RTC Time"
echo " 2. Configure Startup/Shutdown Schedules"
echo
echo " 0. Exit"
echo -n "Enter Number (0-2):"
newmode=$( get_number )
if [ $newmode -eq 0 ]
then
rtcloopflag=0
elif [[ $newmode -ge 1 && $newmode -le 2 ]]
then
if [ $newmode -eq 1 ]
then
echo "Matching RTC Time to System Time..."
$pythonbin $argonrtcscript UPDATERTCTIME
elif [ $newmode -eq 2 ]
then
configure_schedule
fi
fi
done
echo

487
source/scripts/argoneond.py Normal file
View File

@@ -0,0 +1,487 @@
#!/usr/bin/python3
import sys
import datetime
import math
import os
import time
sys.path.append("/etc/argon/")
from argonregister import argonregister_initializebusobj
import argonrtc
# Initialize I2C Bus
bus = argonregister_initializebusobj()
ADDR_RTC=0x51
#################
# Common/Helpers
#################
RTC_CONFIGFILE = "/etc/argoneonrtc.conf"
RTC_ALARM_BIT = 0x8
RTC_TIMER_BIT = 0x4
# PCF8563 number system Binary Coded Decimal (BCD)
# BCD to Decimal
def numBCDtoDEC(val):
return (val & 0xf)+(((val >> 4) & 0xf)*10)
# Decimal to BCD
def numDECtoBCD(val):
return (math.floor(val/10)<<4) + (val % 10)
# Check if Event Bit is raised
def hasRTCEventFlag(flagbit):
if bus is None:
return False
bus.write_byte(ADDR_RTC,1)
out = bus.read_byte_data(ADDR_RTC, 1)
return (out & flagbit) != 0
# Clear Event Bit if raised
def clearRTCEventFlag(flagbit):
if bus is None:
return False
out = bus.read_byte_data(ADDR_RTC, 1)
if (out & flagbit) != 0:
# Unset only if fired
bus.write_byte_data(ADDR_RTC, 1, out&(0xff-flagbit))
return True
return False
# Enable Event Flag
def setRTCEventFlag(flagbit, enabled):
if bus is None:
return
# 0x10 = TI_TP flag, 0 by default
ti_tp_flag = 0x10
# flagbit=0x4 for timer flag, 0x1 for enable timer flag
# flagbit=0x8 for alarm flag, 0x2 for enable alarm flag
enableflagbit = flagbit>>2
disableflagbit = 0
if enabled == False:
disableflagbit = enableflagbit
enableflagbit = 0
out = bus.read_byte_data(ADDR_RTC, 1)
bus.write_byte_data(ADDR_RTC, 1, (out&(0xff-flagbit-disableflagbit - ti_tp_flag))|enableflagbit)
#########
# Describe Methods
#########
# Describe Timer Setting
def describeTimer(showsetting):
if bus is None:
return "Error"
out = bus.read_byte_data(ADDR_RTC, 14)
tmp = out & 3
if tmp == 3:
outstr = " Minute(s)"
elif tmp == 2:
outstr = " Second(s)"
elif tmp == 1:
outstr = "/64th Second"
elif tmp == 0:
outstr = "/4096th Second"
if (out & 0x80) != 0:
out = bus.read_byte_data(ADDR_RTC, 15)
return "Every "+(numBCDtoDEC(out)+1)+outstr
elif showsetting == True:
return "Disabled (Interval every 1"+outstr+")"
# Setting might matter to save resources
return "None"
# Describe Alarm Setting
def describeAlarm():
if bus is None:
return "Error"
minute = -1
hour = -1
caldate = -1
weekday = -1
out = bus.read_byte_data(ADDR_RTC, 9)
if (out & 0x80) == 0:
minute = numBCDtoDEC(out & 0x7f)
out = bus.read_byte_data(ADDR_RTC, 10)
if (out & 0x80) == 0:
hour = numBCDtoDEC(out & 0x3f)
out = bus.read_byte_data(ADDR_RTC, 11)
if (out & 0x80) == 0:
caldate = numBCDtoDEC(out & 0x3f)
out = bus.read_byte_data(ADDR_RTC, 12)
if (out & 0x80) == 0:
weekday = numBCDtoDEC(out & 0x7)
if weekday < 0 and caldate < 0 and hour < 0 and minute < 0:
return "None"
# Convert from UTC
utcschedule = argonrtc.describeSchedule([-1], [weekday], [caldate], [hour], [minute])
weekday, caldate, hour, minute = argonrtc.convertAlarmTimezone(weekday, caldate, hour, minute, False)
return argonrtc.describeSchedule([-1], [weekday], [caldate], [hour], [minute]) + " Local (RTC Schedule: "+utcschedule+" UTC)"
# Describe Control Flags
def describeControlRegisters():
if bus is None:
print("Error")
return
out = bus.read_byte_data(ADDR_RTC, 1)
print("\n***************")
print("Control Status 2")
print("\tTI_TP Flag:", ((out & 0x10) != 0))
print("\tAlarm Flag:", ((out & RTC_ALARM_BIT) != 0),"( Enabled =", (out & (RTC_ALARM_BIT>>2)) != 0, ")")
print("\tTimer Flag:", ((out & RTC_TIMER_BIT) != 0),"( Enabled =", (out & (RTC_TIMER_BIT>>2)) != 0, ")")
print("Alarm Setting:")
print("\t"+describeAlarm())
print("Timer Setting:")
print("\t"+describeTimer(True))
print("***************\n")
#########
# Alarm
#########
# Check if RTC Alarm Flag is ON
def hasRTCAlarmFlag():
return hasRTCEventFlag(RTC_ALARM_BIT)
# Clear RTC Alarm Flag
def clearRTCAlarmFlag():
return clearRTCEventFlag(RTC_ALARM_BIT)
# Enables RTC Alarm Register
def enableAlarm(registeraddr, value, mask):
if bus is None:
return
# 0x00 is Enabled
bus.write_byte_data(ADDR_RTC, registeraddr, (numDECtoBCD(value)&mask))
# Disables RTC Alarm Register
def disableAlarm(registeraddr):
if bus is None:
return
# 0x80 is disabled
bus.write_byte_data(ADDR_RTC, registeraddr, 0x80)
# Removes all alarm settings
def removeRTCAlarm():
setRTCEventFlag(RTC_ALARM_BIT, False)
disableAlarm(9)
disableAlarm(10)
disableAlarm(11)
disableAlarm(12)
# Set RTC Alarm (Negative values ignored)
def setRTCAlarm(enableflag, weekday, caldate, hour, minute):
weekday, caldate, hour, minute = argonrtc.getRTCAlarm(weekday, caldate, hour, minute)
if caldate < 1 and weekday < 0 and hour < 0 and minute < 0:
return -1
clearRTCAlarmFlag()
setRTCEventFlag(RTC_ALARM_BIT, enableflag)
if minute >= 0:
enableAlarm(9, minute, 0x7f)
else:
disableAlarm(9)
if hour >= 0:
enableAlarm(10, hour, 0x7f)
else:
disableAlarm(10)
if caldate >= 0:
enableAlarm(11, caldate, 0x7f)
else:
disableAlarm(11)
if weekday >= 0:
# 0 - Sun (datetime 0 - Mon)
if weekday > 5:
weekday = 0
else:
weekday = weekday + 1
enableAlarm(12, weekday, 0x7f)
else:
disableAlarm(12)
return 0
# Set RTC Hourly Alarm
def setRTCAlarmHourly(enableflag, minute):
return setRTCAlarm(enableflag, -1, -1, -1, minute)
# Set RTC Daily Alarm
def setRTCAlarmDaily(enableflag, hour, minute):
return setRTCAlarm(enableflag, -1, -1, hour, minute)
# Set RTC Weekly Alarm
def setRTCAlarmWeekly(enableflag, dayofweek, hour, minute):
return setRTCAlarm(enableflag, dayofweek, -1, hour, minute)
# Set RTC Monthly Alarm
def setRTCAlarmMonthly(enableflag, caldate, hour, minute):
return setRTCAlarm(enableflag, -1, caldate, hour, minute)
#########
# Timer
#########
# Check if RTC Timer Flag is ON
def hasRTCTimerFlag():
return hasRTCEventFlag(RTC_TIMER_BIT)
# Clear RTC Timer Flag
def clearRTCTimerFlag():
return clearRTCEventFlag(RTC_TIMER_BIT)
# Remove RTC Timer Setting
def removeRTCTimer():
if bus is None:
return
setRTCEventFlag(RTC_TIMER_BIT, False)
# Timer disable and Set Timer frequency to lowest (0x3=1 per minute)
bus.write_byte_data(ADDR_RTC, 14, 3)
bus.write_byte_data(ADDR_RTC, 15, 0)
# Set RTC Timer Interval
def setRTCTimerInterval(enableflag, value, inSeconds = False):
if bus is None:
return -1
if value > 255 or value < 1:
return -1
clearRTCTimerFlag()
setRTCEventFlag(RTC_TIMER_BIT, enableflag)
# 0x80 Timer Enabled, mode: 0x3=1/Min, 0x2=1/Sec, 0x1=Per 64th Sec, 0=Per 4096th Sec
timerconfigFlag = 0x83
if inSeconds == True:
timerconfigFlag = 0x82
bus.write_byte_data(ADDR_RTC, 14, timerconfigFlag)
bus.write_byte_data(ADDR_RTC, 15, numDECtoBCD(value&0xff))
return 0
#############
# Date/Time
#############
# Returns RTC timestamp as datetime object
def getRTCdatetime():
if bus is None:
return datetime.datetime(2000, 1, 1, 0, 0, 0)
# Data Sheet Recommends to read this manner (instead of from registers)
bus.write_byte(ADDR_RTC,2)
out = bus.read_byte(ADDR_RTC)
out = numBCDtoDEC(out & 0x7f)
second = out
#warningflag = (out & 0x80)>>7
out = bus.read_byte(ADDR_RTC)
minute = numBCDtoDEC(out & 0x7f)
out = bus.read_byte(ADDR_RTC)
hour = numBCDtoDEC(out & 0x3f)
out = bus.read_byte(ADDR_RTC)
caldate = numBCDtoDEC(out & 0x3f)
out = bus.read_byte(ADDR_RTC)
#weekDay = numBCDtoDEC(out & 7)
out = bus.read_byte(ADDR_RTC)
month = numBCDtoDEC(out & 0x1f)
out = bus.read_byte(ADDR_RTC)
year = numBCDtoDEC(out)
#print({"year":year, "month": month, "date": caldate, "hour": hour, "minute": minute, "second": second})
if month == 0:
# Reset, uninitialized RTC
month = 1
# Timezone is GMT/UTC +0
# Year is from 2000
try:
return datetime.datetime(year+2000, month, caldate, hour, minute, second)+argonrtc.getLocaltimeOffset()
except:
return datetime.datetime(2000, 1, 1, 0, 0, 0)
# set RTC time using datetime object (Local time)
def setRTCdatetime(localdatetime):
if bus is None:
return
# Set local time to UTC
localdatetime = localdatetime - argonrtc.getLocaltimeOffset()
# python Sunday = 6, RTC Sunday = 0
weekDay = localdatetime.weekday()
if weekDay == 6:
weekDay = 0
else:
weekDay = weekDay + 1
# Write to respective registers
bus.write_byte_data(ADDR_RTC,2,numDECtoBCD(localdatetime.second))
bus.write_byte_data(ADDR_RTC,3,numDECtoBCD(localdatetime.minute))
bus.write_byte_data(ADDR_RTC,4,numDECtoBCD(localdatetime.hour))
bus.write_byte_data(ADDR_RTC,5,numDECtoBCD(localdatetime.day))
bus.write_byte_data(ADDR_RTC,6,numDECtoBCD(weekDay))
bus.write_byte_data(ADDR_RTC,7,numDECtoBCD(localdatetime.month))
# Year is from 2000
bus.write_byte_data(ADDR_RTC,8,numDECtoBCD(localdatetime.year-2000))
#########
# Config
#########
# Set Next Alarm on RTC
def setNextAlarm(commandschedulelist, prevdatetime):
nextcommandtime, weekday, caldate, hour, minute = argonrtc.getNextAlarm(commandschedulelist, prevdatetime)
if prevdatetime >= nextcommandtime:
return prevdatetime
if weekday < 0 and caldate < 0 and hour < 0 and minute < 0:
# No schedule
# nextcommandtime is current time, which will be replaced/checked next iteration
removeRTCAlarm()
return nextcommandtime
setRTCAlarm(True, nextcommandtime.weekday(), nextcommandtime.day, nextcommandtime.hour, nextcommandtime.minute)
return nextcommandtime
def allowshutdown():
uptime = 0.0
errorflag = False
try:
cpuctr = 0
tempfp = open("/proc/uptime", "r")
alllines = tempfp.readlines()
for temp in alllines:
infolist = temp.split(" ")
if len(infolist) > 1:
uptime = float(infolist[0])
break
tempfp.close()
except IOError:
errorflag = True
# 120=2mins minimum up time
return uptime > 120
######
if len(sys.argv) > 1:
cmd = sys.argv[1].upper()
# Enable Alarm/Timer Flags
enableflag = True
if cmd == "CLEAN":
removeRTCAlarm()
removeRTCTimer()
elif cmd == "SHUTDOWN":
clearRTCAlarmFlag()
clearRTCTimerFlag()
elif cmd == "GETRTCSCHEDULE":
print("Alarm Setting:")
print("\t"+describeAlarm())
#print("Timer Setting:")
#print("\t"+describeTimer(True))
elif cmd == "GETRTCTIME":
print("RTC Time:", getRTCdatetime())
elif cmd == "UPDATERTCTIME":
setRTCdatetime(datetime.datetime.now())
print("RTC Time:", getRTCdatetime())
elif cmd == "GETSCHEDULELIST":
argonrtc.describeConfigList(RTC_CONFIGFILE)
elif cmd == "SHOWSCHEDULE":
if len(sys.argv) > 2:
if sys.argv[2].isdigit():
# Display starts at 2, maps to 0-based index
configidx = int(sys.argv[2])-2
configlist = argonrtc.loadConfigList(RTC_CONFIGFILE)
if len(configlist) > configidx:
print (" ",argonrtc.describeConfigListEntry(configlist[configidx]))
else:
print(" Invalid Schedule")
elif cmd == "REMOVESCHEDULE":
if len(sys.argv) > 2:
if sys.argv[2].isdigit():
# Display starts at 2, maps to 0-based index
configidx = int(sys.argv[2])-2
argonrtc.removeConfigEntry(RTC_CONFIGFILE, configidx)
elif cmd == "SERVICE":
argonrtc.updateSystemTime(getRTCdatetime())
commandschedulelist = argonrtc.formCommandScheduleList(argonrtc.loadConfigList(RTC_CONFIGFILE))
nextrtcalarmtime = setNextAlarm(commandschedulelist, datetime.datetime.now())
serviceloop = True
while serviceloop==True:
clearRTCAlarmFlag()
clearRTCTimerFlag()
tmpcurrenttime = datetime.datetime.now()
if nextrtcalarmtime <= tmpcurrenttime:
# Update RTC Alarm to next iteration
nextrtcalarmtime = setNextAlarm(commandschedulelist, nextrtcalarmtime)
if len(argonrtc.getCommandForTime(commandschedulelist, tmpcurrenttime, "off")) > 0:
# Shutdown detected, issue command then end service loop
if allowshutdown():
os.system("shutdown now -h")
serviceloop = False
# Don't break to sleep while command executes (prevents service to restart)
time.sleep(60)
elif False:
print("System Time: ", datetime.datetime.now())
print("RTC Time: ", getRTCdatetime())
describeControlRegisters()

View File

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

View File

@@ -0,0 +1,345 @@
#!/usr/bin/python3
import sys
import datetime
import math
import os
import time
# Initialize I2C Bus
import smbus
try:
bus=smbus.SMBus(1)
except Exception:
try:
# Older version
bus=smbus.SMBus(0)
except Exception:
print("Unable to detect i2c")
bus=None
OLED_WD=128
OLED_HT=64
OLED_SLAVEADDRESS=0x6a
ADDR_OLED=0x3c
OLED_NUMFONTCHAR=256
OLED_BUFFERIZE = ((OLED_WD*OLED_HT)>>3)
oled_imagebuffer = [0] * OLED_BUFFERIZE
def oled_getmaxY():
return OLED_HT
def oled_getmaxX():
return OLED_WD
def oled_loadbg(bgname):
if bgname == "bgblack":
oled_clearbuffer()
return
elif bgname == "bgwhite":
oled_clearbuffer(1)
return
try:
file = open("/etc/argon/oled/"+bgname+".bin", "rb")
bgbytes = list(file.read())
file.close()
ctr = len(bgbytes)
if ctr == OLED_BUFFERIZE:
oled_imagebuffer[:] = bgbytes
elif ctr > OLED_BUFFERIZE:
oled_imagebuffer[:] = bgbytes[0:OLED_BUFFERIZE]
else:
oled_imagebuffer[0:ctr] = bgbytes
# Clear the rest of the buffer
while ctr < OLED_BUFFERIZE:
oled_imagebuffer[ctr] = 0
ctr=ctr+1
except FileNotFoundError:
oled_clearbuffer()
def oled_clearbuffer(value = 0):
if value != 0:
value = 0xff
ctr = 0
while ctr < OLED_BUFFERIZE:
oled_imagebuffer[ctr] = value
ctr=ctr+1
def oled_writebyterow(x,y,bytevalue, mode = 0):
bufferoffset = OLED_WD*(y>>3) + x
if mode == 0:
oled_imagebuffer[bufferoffset] = bytevalue
elif mode == 1:
oled_imagebuffer[bufferoffset] = bytevalue^oled_imagebuffer[bufferoffset]
else:
oled_imagebuffer[bufferoffset] = bytevalue|oled_imagebuffer[bufferoffset]
def oled_writebuffer(x,y,value, mode = 0):
yoffset = y>>3
yshift = y&0x7
ybit = (1<<yshift)
ymask = 0xFF^ybit
if value != 0:
value = ybit
bufferoffset = OLED_WD*yoffset + x
curval = oled_imagebuffer[bufferoffset]
if mode & 1:
oled_imagebuffer[bufferoffset] = curval^value
else:
oled_imagebuffer[bufferoffset] = curval&ymask|value
def oled_fill(value):
oled_clearbuffer(value)
oled_flushimage()
def oled_flushimage(hidescreen = True):
if hidescreen == True:
# Reset/Hide screen
oled_power(False)
xctr = 0
while xctr < OLED_WD:
yctr = 0
while yctr < OLED_HT:
oled_flushblock(xctr, yctr)
yctr = yctr + 8
xctr = xctr + 32
if hidescreen == True:
# Display
oled_power(True)
def oled_flushblock(xoffset, yoffset):
yoffset = yoffset>>3
blocksize = 32
if bus is None:
return
try:
# Set COM-H Addressing
bus.write_byte_data(ADDR_OLED, 0, 0x20)
bus.write_byte_data(ADDR_OLED, 0, 0x1)
# Set Column range
bus.write_byte_data(ADDR_OLED, 0, 0x21)
bus.write_byte_data(ADDR_OLED, 0, xoffset)
bus.write_byte_data(ADDR_OLED, 0, xoffset+blocksize-1)
# Set Row Range
bus.write_byte_data(ADDR_OLED, 0, 0x22)
bus.write_byte_data(ADDR_OLED, 0, yoffset)
bus.write_byte_data(ADDR_OLED, 0, yoffset)
# Set Display Start Line
bus.write_byte_data(ADDR_OLED, 0, 0x40)
bufferoffset = OLED_WD*yoffset + xoffset
# Write Out Buffer
bus.write_i2c_block_data(ADDR_OLED, OLED_SLAVEADDRESS, oled_imagebuffer[bufferoffset:(bufferoffset+blocksize)])
except:
return
def oled_drawfilledrectangle(x, y, wd, ht, mode = 0):
ymax = y + ht
cury = y&0xF8
xmax = x + wd
curx = x
if ((y & 0x7)) != 0:
yshift = y&0x7
bytevalue = (0xFF<<yshift)&0xFF
# If 8 no additional masking needed
if ymax-cury < 8:
yshift = 8-((ymax-cury)&0x7)
bytevalue = bytevalue & (0xFF>>yshift)
while curx < xmax:
oled_writebyterow(curx,cury,bytevalue, mode)
curx = curx + 1
cury = cury + 8
# Draw 8 rows at a time when possible
while cury + 8 < ymax:
curx = x
while curx < xmax:
oled_writebyterow(curx,cury,0xFF, mode)
curx = curx + 1
cury = cury + 8
if cury < ymax:
yshift = 8-((ymax-cury)&0x7)
bytevalue = (0xFF>>yshift)
curx = x
while curx < xmax:
oled_writebyterow(curx,cury,bytevalue, mode)
curx = curx + 1
def oled_writetextaligned(textdata, x, y, boxwidth, alignmode, charwd = 6, mode = 0):
leftoffset = 0
if alignmode == 1:
# Centered
leftoffset = (boxwidth-len(textdata)*charwd)>>1
elif alignmode == 2:
# Right aligned
leftoffset = (boxwidth-len(textdata)*charwd)
oled_writetext(textdata, x+leftoffset, y, charwd, mode)
def oled_writetext(textdata, x, y, charwd = 6, mode = 0):
if charwd < 6:
charwd = 6
charht = int((charwd<<3)/6)
if charht & 0x7:
charht = (charht&0xF8) + 8
try:
file = open("/etc/argon/oled/font"+str(charht)+"x"+str(charwd)+".bin", "rb")
fontbytes = list(file.read())
file.close()
except FileNotFoundError:
try:
# Default to smallest
file = open("/etc/argon/oled/font8x6.bin", "rb")
fontbytes = list(file.read())
file.close()
except FileNotFoundError:
return
if ((y & 0x7)) == 0:
# Use optimized loading
oled_fastwritetext(textdata, x, y, charht, charwd, fontbytes, mode)
return
numfontrow = charht>>3
ctr = 0
while ctr < len(textdata):
fontoffset = ord(textdata[ctr])*charwd
fontcol = 0
while fontcol < charwd and x < OLED_WD:
fontrow = 0
row = y
while fontrow < numfontrow and row < OLED_HT and x >= 0:
curbit = 0x80
curbyte = (fontbytes[fontoffset + fontcol + (OLED_NUMFONTCHAR*charwd*fontrow)])
subrow = 0
while subrow < 8 and row < OLED_HT:
value = 0
if (curbyte&curbit) != 0:
value = 1
oled_writebuffer(x,row,value, mode)
curbit = curbit >> 1
row = row + 1
subrow = subrow + 1
fontrow = fontrow + 1
fontcol = fontcol + 1
x = x + 1
ctr = ctr + 1
def oled_fastwritetext(textdata, x, y, charht, charwd, fontbytes, mode = 0):
numfontrow = charht>>3
ctr = 0
while ctr < len(textdata):
fontoffset = ord(textdata[ctr])*charwd
fontcol = 0
while fontcol < charwd and x < OLED_WD:
fontrow = 0
row = y&0xF8
while fontrow < numfontrow and row < OLED_HT and x >= 0:
curbyte = (fontbytes[fontoffset + fontcol + (OLED_NUMFONTCHAR*charwd*fontrow)])
oled_writebyterow(x,row,curbyte, mode)
fontrow = fontrow + 1
row = row + 8
fontcol = fontcol + 1
x = x + 1
ctr = ctr + 1
return
def oled_power(turnon = True):
cmd = 0xAE
if turnon == True:
cmd = cmd|1
if bus is None:
return
try:
bus.write_byte_data(ADDR_OLED, 0, cmd)
except:
return
def oled_inverse(enable = True):
cmd = 0xA6
if enable == True:
cmd = cmd|1
if bus is None:
return
try:
bus.write_byte_data(ADDR_OLED, 0, cmd)
except:
return
def oled_fullwhite(enable = True):
cmd = 0xA4
if enable == True:
cmd = cmd|1
if bus is None:
return
try:
bus.write_byte_data(ADDR_OLED, 0, cmd)
except:
return
def oled_reset():
if bus is None:
return
try:
# Set COM-H Addressing
bus.write_byte_data(ADDR_OLED, 0, 0x20)
bus.write_byte_data(ADDR_OLED, 0, 0x1)
# Set Column range
bus.write_byte_data(ADDR_OLED, 0, 0x21)
bus.write_byte_data(ADDR_OLED, 0, 0)
bus.write_byte_data(ADDR_OLED, 0, OLED_WD-1)
# Set Row Range
bus.write_byte_data(ADDR_OLED, 0, 0x22)
bus.write_byte_data(ADDR_OLED, 0, 0)
bus.write_byte_data(ADDR_OLED, 0, (OLED_HT>>3)-1)
# Set Page Addressing
bus.write_byte_data(ADDR_OLED, 0, 0x20)
bus.write_byte_data(ADDR_OLED, 0, 0x2)
# Set GDDRAM Address
bus.write_byte_data(ADDR_OLED, 0, 0xB0)
# Set Display Start Line
bus.write_byte_data(ADDR_OLED, 0, 0x40)
except:
return

View File

@@ -0,0 +1,254 @@
#!/bin/bash
daemonconfigfile=/etc/argononed.conf
unitconfigfile=/etc/argonunits.conf
fanmode="CPU"
if [ "$1" == "hdd" ]
then
daemonconfigfile=/etc/argononed-hdd.conf
fanmode="HDD"
fi
if [ -f "$unitconfigfile" ]
then
. $unitconfigfile
fi
if [ -z "$temperature" ]
then
temperature="C"
fi
echo "------------------------------------------"
echo " Argon Fan Speed Configuration Tool ($fanmode)"
echo "------------------------------------------"
echo "WARNING: This will remove existing configuration."
echo -n "Press Y to continue:"
read -n 1 confirm
echo
fanloopflag=1
newmode=0
if [ "$confirm" = "y" ]
then
confirm="Y"
fi
if [ "$confirm" != "Y" ]
then
fanloopflag=0
echo "Cancelled."
else
echo "Thank you."
fi
get_number () {
read curnumber
if [ -z "$curnumber" ]
then
echo "-2"
return
elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]]
then
if [ $curnumber -lt 0 ]
then
echo "-1"
return
elif [ $curnumber -gt 212 ]
then
# 212F = 100C
echo "-1"
return
fi
echo $curnumber
return
fi
echo "-1"
return
}
while [ $fanloopflag -eq 1 ]
do
echo
echo "Select fan mode:"
echo " 1. Always on"
if [ "$fanmode" == "HDD" ]
then
if [ "$temperature" == "C" ]
then
echo " 2. Adjust to temperatures (35C, 40C, and 45C)"
else
echo " 2. Adjust to temperatures (95F, 104F, and 113F)"
fi
else
if [ "$temperature" == "C" ]
then
echo " 2. Adjust to temperatures (55C, 60C, and 65C)"
else
echo " 2. Adjust to temperatures (130F, 140F, and 150F)"
fi
fi
echo " 3. Customize temperature cut-offs"
echo
echo " 0. Exit"
echo "NOTE: You can also edit $daemonconfigfile directly"
echo -n "Enter Number (0-3):"
newmode=$( get_number )
if [[ $newmode -eq 0 ]]
then
fanloopflag=0
elif [ $newmode -eq 1 ]
then
echo "#" > $daemonconfigfile
echo "# Argon Fan Speed Configuration $fanmode" >> $daemonconfigfile
echo "#" >> $daemonconfigfile
echo "# Min Temp=Fan Speed" >> $daemonconfigfile
errorfanflag=1
while [ $errorfanflag -eq 1 ]
do
echo -n "Please provide fan speed (30-100 only):"
curfan=$( get_number )
if [ $curfan -ge 30 ]
then
errorfanflag=0
elif [ $curfan -gt 100 ]
then
errorfanflag=0
fi
done
echo "1="$curfan >> $daemonconfigfile
sudo systemctl restart argononed.service
echo "Fan always on."
elif [ $newmode -eq 2 ]
then
echo "#" > $daemonconfigfile
echo "# Argon Fan Speed Configuration $fanmode" >> $daemonconfigfile
echo "#" >> $daemonconfigfile
echo "# Min Temp=Fan Speed" >> $daemonconfigfile
echo "Please provide fan speeds for the following temperatures:"
curtemp=55
maxtemp=70
if [ "$fanmode" == "HDD" ]
then
curtemp=30
maxtemp=60
fi
while [ $curtemp -lt $maxtemp ]
do
errorfanflag=1
while [ $errorfanflag -eq 1 ]
do
displaytemp=$curtemp
if [ "$temperature" == "F" ]
then
# Convert C to F
displaytemp=$((($curtemp*9/5)+32))
fi
echo -n ""$displaytemp"$temperature (30-100 only):"
curfan=$( get_number )
if [ $curfan -ge 30 ]
then
errorfanflag=0
elif [ $curfan -gt 100 ]
then
errorfanflag=0
fi
done
echo $curtemp"="$curfan >> $daemonconfigfile
curtemp=$((curtemp+5))
done
sudo systemctl restart argononed.service
echo "Configuration updated."
elif [ $newmode -eq 3 ]
then
echo "Please provide fan speeds and temperature pairs"
echo
subloopflag=1
paircounter=0
while [ $subloopflag -eq 1 ]
do
errortempflag=1
errorfanflag=1
echo "(You may set a blank value to end configuration)"
while [ $errortempflag -eq 1 ]
do
echo -n "Provide minimum temperature of $fanmode (in $temperature) then [ENTER]:"
curtemp=$( get_number )
if [ $curtemp -ge 0 ]
then
errortempflag=0
elif [ $curtemp -eq -2 ]
then
# Blank
errortempflag=0
errorfanflag=0
subloopflag=0
fi
done
while [ $errorfanflag -eq 1 ]
do
echo -n "Provide fan speed for "$curtemp"$temperature (30-100) then [ENTER]:"
curfan=$( get_number )
if [ $curfan -ge 30 ]
then
errorfanflag=0
elif [ $curfan -gt 100 ]
then
errorfanflag=0
elif [ $curfan -eq -2 ]
then
# Blank
errortempflag=0
errorfanflag=0
subloopflag=0
fi
done
if [ $subloopflag -eq 1 ]
then
if [ $paircounter -eq 0 ]
then
echo "#" > $daemonconfigfile
echo "# Argon Fan Configuration" >> $daemonconfigfile
echo "#" >> $daemonconfigfile
echo "# Min Temp=Fan Speed" >> $daemonconfigfile
fi
displaytemp=$curtemp
paircounter=$((paircounter+1))
if [ "$temperature" == "F" ]
then
# Convert to F to C
curtemp=$((($curtemp-32)*5/9))
fi
echo $curtemp"="$curfan >> $daemonconfigfile
echo "* Fan speed will be set to "$curfan" once $fanmode temperature reaches "$displaytemp"$temperature"
echo
fi
done
echo
if [ $paircounter -gt 0 ]
then
echo "Thank you! We saved "$paircounter" pairs."
sudo systemctl restart argononed.service
echo "Changes should take effect now."
else
echo "Cancelled, no data saved."
fi
fi
done
echo

View File

@@ -1,5 +1,6 @@
#!/bin/bash
if [ -e /boot/firmware/config.txt ] ; then
FIRMWARE=/firmware
else
@@ -24,9 +25,10 @@ then
fi
fi
echo "--------------------------------"
echo "Argon One IR Configuration Tool"
echo "--------------------------------"
echo "-----------------------------"
echo " Argon IR Configuration Tool"
echo "------------------------------"
echo "WARNING: This only supports NEC"
echo " protocol only."
echo -n "Press Y to continue:"
@@ -70,8 +72,8 @@ get_number () {
}
irexecrcfile=/etc/lirc/irexec.lircrc
irexecshfile=/usr/bin/argonirexec
irdecodefile=/usr/bin/argonirdecoder
irexecshfile=/etc/argon/argonirexec
irdecodefile=/etc/argon/argonirdecoder
kodiuserdatafolder="$HOME/.kodi/userdata"
kodilircmapfile="$kodiuserdatafolder/Lircmap.xml"
remotemode=""
@@ -176,9 +178,9 @@ then
fi
elif [ $newmode -eq 2 ]
then
echo "--------------------------------"
echo "Argon One IR Configuration Tool"
echo "--------------------------------"
echo "-----------------------------"
echo " Argon IR Configuration Tool"
echo "-----------------------------"
echo "WARNING: This will install LIRC"
echo " and related libraries."
echo -n "Press Y to agree:"
@@ -320,7 +322,10 @@ then
echo ' <down>KEY_DOWN</down>' | tee -a $kodilircmapfile 1> /dev/null
echo ' <select>KEY_OK</select>' | tee -a $kodilircmapfile 1> /dev/null
echo ' <start>KEY_HOME</start>' | tee -a $kodilircmapfile 1> /dev/null
echo ' <rootmenu>KEY_MENUBACK</rootmenu>' | tee -a $kodilircmapfile 1> /dev/null
# 20240611: User reported mapping is incorrect
#echo ' <rootmenu>KEY_MENUBACK</rootmenu>' | tee -a $kodilircmapfile 1> /dev/null
echo ' <rootmenu>KEY_MENU</rootmenu>' | tee -a $kodilircmapfile 1> /dev/null
echo ' <back>KEY_BACK</back>' | tee -a $kodilircmapfile 1> /dev/null
echo ' <volumeplus>KEY_VOLUMEUP</volumeplus>' | tee -a $kodilircmapfile 1> /dev/null
echo ' <volumeminus>KEY_VOLUMEDOWN</volumeminus>' | tee -a $kodilircmapfile 1> /dev/null
echo ' </remote>' | tee -a $kodilircmapfile 1> /dev/null

View File

@@ -0,0 +1,305 @@
#!/bin/bash
if [ -e /boot/firmware/config.txt ] ; then
FIRMWARE=/firmware
else
FIRMWARE=
fi
CONFIG=/boot${FIRMWARE}/config.txt
CHECKGPIOMODE="libgpiod" # gpiod or rpigpio
# Check if Raspbian, Ubuntu, others
CHECKPLATFORM="Others"
CHECKPLATFORMVERSION=""
CHECKPLATFORMVERSIONNUM=""
if [ -f "/etc/os-release" ]
then
source /etc/os-release
if [ "$ID" = "raspbian" ]
then
CHECKPLATFORM="Raspbian"
CHECKPLATFORMVERSION=$VERSION_ID
elif [ "$ID" = "debian" ]
then
# For backwards compatibility, continue using raspbian
CHECKPLATFORM="Raspbian"
CHECKPLATFORMVERSION=$VERSION_ID
elif [ "$ID" = "ubuntu" ]
then
CHECKPLATFORM="Ubuntu"
CHECKPLATFORMVERSION=$VERSION_ID
fi
echo ${CHECKPLATFORMVERSION} | grep -e "\." > /dev/null
if [ $? -eq 0 ]
then
CHECKPLATFORMVERSIONNUM=`cut -d "." -f2 <<< $CHECKPLATFORMVERSION `
CHECKPLATFORMVERSION=`cut -d "." -f1 <<< $CHECKPLATFORMVERSION `
fi
fi
pythonbin=/usr/bin/python3
# Files
ARGONDOWNLOADSERVER=https://download.argon40.com
INSTALLATIONFOLDER=/etc/argon
basename="argononeups"
daemonname=$basename"d"
daemonupsservice=/lib/systemd/system/$daemonname.service
upsdaemonscript=$INSTALLATIONFOLDER/$daemonname.py
rtcdaemonname="argonupsrtcd"
daemonrtcservice=/lib/systemd/system/$rtcdaemonname.service
rtcdaemonscript=$INSTALLATIONFOLDER/$rtcdaemonname.py
requireinstall=0
newmode=0
echo "-----------------------------------"
echo " Argon Industria UPS Configuration"
echo "-----------------------------------"
if [ ! -f "$upsdaemonscript" ]
then
echo "Install Argon Industria UPS Tools"
echo -n "Press Y to continue:"
read -n 1 confirm
echo
if [ "$confirm" = "y" ]
then
confirm="Y"
fi
if [ "$confirm" != "Y" ]
then
echo "Cancelled"
exit
fi
requireinstall=1
newmode=3 # Reinstall
fi
get_number () {
read curnumber
if [ -z "$curnumber" ]
then
echo "-2"
return
elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]]
then
if [ $curnumber -lt 0 ]
then
echo "-1"
return
elif [ $curnumber -gt 100 ]
then
echo "-1"
return
fi
echo $curnumber
return
fi
echo "-1"
return
}
UPSCMDFILE="/dev/shm/upscmd.txt"
UPSSTATUSFILE="/dev/shm/upslog.txt"
rtcconfigscript=$INSTALLATIONFOLDER/argonups-rtcconfig.sh
if [ -f "$UPSSTATUSFILE" ]
then
# cat $UPSSTATUSFILE
sudo $pythonbin $rtcdaemonscript GETBATTERY
fi
loopflag=1
while [ $loopflag -eq 1 ]
do
if [ $requireinstall -eq 0 ]
then
echo
echo "Select option:"
echo " 1. UPS Battery Status"
echo " 2. Configure RTC and/or Schedule"
echo " 3. Reinstall UPS Tools"
echo " 4. Uninstall UPS Tools"
echo ""
echo " 0. Back"
echo -n "Enter Number (0-4):"
newmode=$( get_number )
fi
if [[ $newmode -ge 0 && $newmode -le 4 ]]
then
if [ $newmode -eq 1 ]
then
sudo $pythonbin $rtcdaemonscript GETBATTERY
#if [ -f "$UPSSTATUSFILE" ]
#then
# cat $UPSSTATUSFILE
#else
# echo "Unable to retrieve status"
#fi
elif [ $newmode -eq 2 ]
then
$rtcconfigscript "argonupsrtc"
#TMPTIMESTR=`date +"%Y%d%m%H%M%S"`
#TMPDATASTR=`date +"%Y %m %d %H %M %S"`
#echo "$TMPTIMESTR" > $UPSCMDFILE
#echo "3 $TMPDATASTR" >> $UPSCMDFILE
elif [ $newmode -eq 3 ]
then
# Start installation
if [ ! -d "$INSTALLATIONFOLDER/ups" ]
then
sudo mkdir $INSTALLATIONFOLDER/ups
fi
rtcconfigfile=/etc/argonupsrtc.conf
# Generate default RTC config file if non-existent
if [ ! -f $rtcconfigfile ]; then
sudo touch $rtcconfigfile
sudo chmod 666 $rtcconfigfile
echo '#' >> $rtcconfigfile
echo '# Argon RTC Configuration' >> $rtcconfigfile
echo '#' >> $rtcconfigfile
fi
for iconfile in battery_0 battery_2 battery_4 battery_charging battery_unknown battery_1 battery_3 battery_alert battery_plug
do
sudo wget $ARGONDOWNLOADSERVER/ups/${iconfile}.png -O $INSTALLATIONFOLDER/ups/${iconfile}.png --quiet
done
sudo wget $ARGONDOWNLOADSERVER/ups/upsimg.tar.gz -O $INSTALLATIONFOLDER/ups/upsimg.tar.gz --quiet
sudo tar xfz $INSTALLATIONFOLDER/ups/upsimg.tar.gz -C $INSTALLATIONFOLDER/ups/
sudo rm -Rf $INSTALLATIONFOLDER/ups/upsimg.tar.gz
# Desktop Icon
destfoldername=$USERNAME
if [ -z "$destfoldername" ]
then
destfoldername=$USER
fi
if [ -z "$destfoldername" ]
then
destfoldername="pi"
fi
shortcutfile="/home/$destfoldername/Desktop/argonone-ups.desktop"
if [ -d "/home/$destfoldername/Desktop" ]
then
terminalcmd="lxterminal --working-directory=/home/$destfoldername/ -t"
if [ -f "/home/$destfoldername/.twisteros.twid" ]
then
terminalcmd="xfce4-terminal --default-working-directory=/home/$destfoldername/ -T"
fi
echo "[Desktop Entry]" > $shortcutfile
echo "Name=Argon UPS" >> $shortcutfile
echo "Comment=Argon UPS" >> $shortcutfile
echo "Icon=/etc/argon/ups/loading_0.png" >> $shortcutfile
echo 'Exec='$terminalcmd' "Argon UPS" -e "'$rtcconfigscript' argonupsrtc"' >> $shortcutfile
echo "Type=Application" >> $shortcutfile
echo "Encoding=UTF-8" >> $shortcutfile
echo "Terminal=false" >> $shortcutfile
echo "Categories=None;" >> $shortcutfile
chmod 755 $shortcutfile
fi
# Stopped using default battery indicator
## Build Kernel Module
#sourcecodefolder=$INSTALLATIONFOLDER/tmp
#buildfolder=$sourcecodefolder/build
#if [ -d $sourcecodefolder ]
#then
# sudo rm -rf $sourcecodefolder
#fi
#if [ "$CHECKPLATFORM" = "Ubuntu" ]
#then
# sudo apt-get install build-essential
#fi
#sudo mkdir -p $buildfolder
#sudo chmod -R 755 $buildfolder
#FILELIST="COPYING Makefile argonbatteryicon.c"
#for fname in $FILELIST
#do
# sudo wget $ARGONDOWNLOADSERVER/modules/argonbatteryicon/$fname -O $buildfolder/#$fname --quiet
#done
## Start Build
#cd $buildfolder/
#sudo make
#sudo cp "$buildfolder/argonbatteryicon.ko" "$INSTALLATIONFOLDER/ups/"
## Cleanup
#cd $INSTALLATIONFOLDER/
#sudo rm -Rf "$sourcecodefolder"
sudo wget $ARGONDOWNLOADSERVER/scripts/argononeupsd.py -O "$upsdaemonscript" --quiet
sudo wget $ARGONDOWNLOADSERVER/scripts/argononeupsd.service -O "$daemonupsservice" --quiet
sudo chmod 666 $daemonupsservice
#echo "User=$destfoldername" >> "$daemonupsservice"
#echo "Group=$destfoldername" >> "$daemonupsservice"
sudo chmod 644 $daemonupsservice
sudo wget $ARGONDOWNLOADSERVER/scripts/argoneon-rtcconfig.sh -O $rtcconfigscript --quiet
sudo chmod 755 $rtcconfigscript
sudo wget $ARGONDOWNLOADSERVER/scripts/argonrtc.py -O $INSTALLATIONFOLDER/argonrtc.py --quiet
sudo wget $ARGONDOWNLOADSERVER/scripts/argonupsrtcd.py -O "$rtcdaemonscript" --quiet
sudo wget $ARGONDOWNLOADSERVER/scripts/argonupsrtcd.service -O "$daemonrtcservice" --quiet
sudo chmod 644 $daemonrtcservice
if [ $requireinstall -eq 1 ]
then
requireinstall=0
sudo systemctl enable "$daemonname.service"
sudo systemctl start "$daemonname.service"
sudo systemctl enable "$rtcdaemonname.service"
sudo systemctl start "$rtcdaemonname.service"
else
sudo systemctl restart "$daemonname.service"
sudo systemctl restart "$rtcdaemonname.service"
loopflag=0
fi
# Serial I/O is here
sudo systemctl restart argononed.service
elif [ $newmode -eq 4 ]
then
sudo systemctl stop "$daemonname.service"
sudo systemctl disable "$daemonname.service"
sudo rm $daemonupsservice
sudo rm $upsdaemonscript
sudo systemctl stop "$rtcdaemonname.service"
sudo systemctl disable "$rtcdaemonname.service"
sudo rm $daemonrtcservice
sudo rm $rtcdaemonscript
sudo rm -R -f $INSTALLATIONFOLDER/ups
echo "Uninstall Completed"
loopflag=0
else
echo "Cancelled"
loopflag=0
fi
fi
done

600
source/scripts/argononed.py Normal file
View File

@@ -0,0 +1,600 @@
#!/usr/bin/python3
#
# This script set fan speed and monitor power button events.
#
# Fan Speed is set by sending 0 to 100 to the MCU (Micro Controller Unit)
# The values will be interpreted as the percentage of fan speed, 100% being maximum
#
# Power button events are sent as a pulse signal to BCM Pin 4 (BOARD P7)
# A pulse width of 20-30ms indicates reboot request (double-tap)
# A pulse width of 40-50ms indicates shutdown request (hold and release after 3 secs)
#
# Additional comments are found in each function below
#
# Standard Deployment/Triggers:
# * Raspbian, OSMC: Runs as service via /lib/systemd/system/argononed.service
# * lakka, libreelec: Runs as service via /storage/.config/system.d/argononed.service
# * recalbox: Runs as service via /etc/init.d/
#
import sys
import os
import time
from threading import Thread
from queue import Queue
sys.path.append("/etc/argon/")
from argonsysinfo import *
from argonregister import *
from argonpowerbutton import *
# Initialize I2C Bus
bus = argonregister_initializebusobj()
OLED_ENABLED=False
if os.path.exists("/etc/argon/argoneonoled.py"):
import datetime
from argoneonoled import *
OLED_ENABLED=True
OLED_CONFIGFILE = "/etc/argoneonoled.conf"
UNIT_CONFIGFILE = "/etc/argonunits.conf"
# This function converts the corresponding fanspeed for the given temperature
# The configuration data is a list of strings in the form "<temperature>=<speed>"
def get_fanspeed(tempval, configlist):
for curconfig in configlist:
curpair = curconfig.split("=")
tempcfg = float(curpair[0])
fancfg = int(float(curpair[1]))
if tempval >= tempcfg:
if fancfg < 1:
return 0
elif fancfg < 25:
return 25
return fancfg
return 0
# This function retrieves the fanspeed configuration list from a file, arranged by temperature
# It ignores lines beginning with "#" and checks if the line is a valid temperature-speed pair
# The temperature values are formatted to uniform length, so the lines can be sorted properly
def load_config(fname):
newconfig = []
try:
with open(fname, "r") as fp:
for curline in fp:
if not curline:
continue
tmpline = curline.strip()
if not tmpline:
continue
if tmpline[0] == "#":
continue
tmppair = tmpline.split("=")
if len(tmppair) != 2:
continue
tempval = 0
fanval = 0
try:
tempval = float(tmppair[0])
if tempval < 0 or tempval > 100:
continue
except:
continue
try:
fanval = int(float(tmppair[1]))
if fanval < 0 or fanval > 100:
continue
except:
continue
newconfig.append( "{:5.1f}={}".format(tempval,fanval))
if len(newconfig) > 0:
newconfig.sort(reverse=True)
except:
return []
return newconfig
# Load OLED Config file
def load_oledconfig(fname):
output={}
screenduration=-1
screenlist=[]
try:
with open(fname, "r") as fp:
for curline in fp:
if not curline:
continue
tmpline = curline.strip()
if not tmpline:
continue
if tmpline[0] == "#":
continue
tmppair = tmpline.split("=")
if len(tmppair) != 2:
continue
if tmppair[0] == "switchduration":
output['screenduration']=int(tmppair[1])
elif tmppair[0] == "screensaver":
output['screensaver']=int(tmppair[1])
elif tmppair[0] == "screenlist":
output['screenlist']=tmppair[1].replace("\"", "").split(" ")
elif tmppair[0] == "enabled":
output['enabled']=tmppair[1].replace("\"", "")
except:
return {}
return output
# Load Unit Config file
def load_unitconfig(fname):
output={"temperature": "C"}
try:
with open(fname, "r") as fp:
for curline in fp:
if not curline:
continue
tmpline = curline.strip()
if not tmpline:
continue
if tmpline[0] == "#":
continue
tmppair = tmpline.split("=")
if len(tmppair) != 2:
continue
if tmppair[0] == "temperature":
output['temperature']=tmppair[1].replace("\"", "")
except:
return {}
return output
def load_fancpuconfig():
fanconfig = ["65=100", "60=55", "55=30"]
tmpconfig = load_config("/etc/argononed.conf")
if len(tmpconfig) > 0:
fanconfig = tmpconfig
return fanconfig
def load_fanhddconfig():
fanhddconfig = ["50=100", "40=55", "30=30"]
fanhddconfigfile = "/etc/argononed-hdd.conf"
if os.path.isfile(fanhddconfigfile):
tmpconfig = load_config(fanhddconfigfile)
if len(tmpconfig) > 0:
fanhddconfig = tmpconfig
else:
fanhddconfig = []
return fanhddconfig
# This function is the thread that monitors temperature and sets the fan speed
# The value is fed to get_fanspeed to get the new fan speed
# To prevent unnecessary fluctuations, lowering fan speed is delayed by 30 seconds
#
# Location of config file varies based on OS
#
def temp_check():
INITIALSPEEDVAL = 200 # ensures fan speed gets set during initialization (e.g. change settings)
argonregsupport = argonregister_checksupport(bus)
fanconfig = load_fancpuconfig()
fanhddconfig = load_fanhddconfig()
prevspeed=INITIALSPEEDVAL
while True:
# Speed based on CPU Temp
val = argonsysinfo_getcputemp()
newspeed = get_fanspeed(val, fanconfig)
# Speed based on HDD Temp
val = argonsysinfo_getmaxhddtemp()
tmpspeed = get_fanspeed(val, fanhddconfig)
# Use faster fan speed
if tmpspeed > newspeed:
newspeed = tmpspeed
if prevspeed == newspeed:
time.sleep(30)
continue
elif newspeed < prevspeed and prevspeed != INITIALSPEEDVAL:
# Pause 30s before speed reduction to prevent fluctuations
time.sleep(30)
prevspeed = newspeed
try:
if newspeed > 0:
# Spin up to prevent issues on older units
argonregister_setfanspeed(bus, 100, argonregsupport)
# Set fan speed has sleep
argonregister_setfanspeed(bus, newspeed, argonregsupport)
time.sleep(30)
except IOError:
time.sleep(60)
#
# This function is the thread that updates OLED
#
def display_loop(readq):
weekdaynamelist = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
monthlist = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"]
oledscreenwidth = oled_getmaxX()
fontwdSml = 6 # Maps to 6x8
fontwdReg = 8 # Maps to 8x16
stdleftoffset = 54
temperature="C"
tmpconfig=load_unitconfig(UNIT_CONFIGFILE)
if "temperature" in tmpconfig:
temperature = tmpconfig["temperature"]
screensavermode = False
screensaversec = 120
screensaverctr = 0
screenenabled = ["clock", "ip"]
prevscreen = ""
curscreen = ""
screenid = 0
screenjogtime = 0
screenjogflag = 0 # start with screenid 0
cpuusagelist = []
curlist = []
tmpconfig=load_oledconfig(OLED_CONFIGFILE)
if "screensaver" in tmpconfig:
screensaversec = tmpconfig["screensaver"]
if "screenduration" in tmpconfig:
screenjogtime = tmpconfig["screenduration"]
if "screenlist" in tmpconfig:
screenenabled = tmpconfig["screenlist"]
if "enabled" in tmpconfig:
if tmpconfig["enabled"] == "N":
screenenabled = []
while len(screenenabled) > 0:
if len(curlist) == 0 and screenjogflag == 1:
# Reset Screen Saver
screensavermode = False
screensaverctr = 0
# Update screen info
screenid = screenid + screenjogflag
if screenid >= len(screenenabled):
screenid = 0
prevscreen = curscreen
curscreen = screenenabled[screenid]
if screenjogtime == 0:
# Resets jogflag (if switched manually)
screenjogflag = 0
else:
screenjogflag = 1
needsUpdate = False
if curscreen == "cpu":
# CPU Usage
if len(curlist) == 0:
try:
if len(cpuusagelist) == 0:
cpuusagelist = argonsysinfo_listcpuusage()
curlist = cpuusagelist
except:
curlist = []
if len(curlist) > 0:
oled_loadbg("bgcpu")
# Display List
yoffset = 0
tmpmax = 4
while tmpmax > 0 and len(curlist) > 0:
curline = ""
tmpitem = curlist.pop(0)
curline = tmpitem["title"]+": "+str(tmpitem["value"])+"%"
oled_writetext(curline, stdleftoffset, yoffset, fontwdSml)
oled_drawfilledrectangle(stdleftoffset, yoffset+12, int((oledscreenwidth-stdleftoffset-4)*tmpitem["value"]/100), 2)
tmpmax = tmpmax - 1
yoffset = yoffset + 16
needsUpdate = True
else:
# Next page due to error/no data
screenjogflag = 1
elif curscreen == "storage":
# Storage Info
if len(curlist) == 0:
try:
tmpobj = argonsysinfo_listhddusage()
for curdev in tmpobj:
curlist.append({"title": curdev, "value": argonsysinfo_kbstr(tmpobj[curdev]['total']), "usage": int(100*tmpobj[curdev]['used']/tmpobj[curdev]['total']) })
#curlist = argonsysinfo_liststoragetotal()
except:
curlist = []
if len(curlist) > 0:
oled_loadbg("bgstorage")
yoffset = 16
tmpmax = 3
while tmpmax > 0 and len(curlist) > 0:
tmpitem = curlist.pop(0)
# Right column first, safer to overwrite white space
oled_writetextaligned(tmpitem["value"], 77, yoffset, oledscreenwidth-77, 2, fontwdSml)
oled_writetextaligned(str(tmpitem["usage"])+"%", 50, yoffset, 74-50, 2, fontwdSml)
tmpname = tmpitem["title"]
if len(tmpname) > 8:
tmpname = tmpname[0:8]
oled_writetext(tmpname, 0, yoffset, fontwdSml)
tmpmax = tmpmax - 1
yoffset = yoffset + 16
needsUpdate = True
else:
# Next page due to error/no data
screenjogflag = 1
elif curscreen == "raid":
# Raid Info
if len(curlist) == 0:
try:
tmpobj = argonsysinfo_listraid()
curlist = tmpobj['raidlist']
except:
curlist = []
if len(curlist) > 0:
oled_loadbg("bgraid")
tmpitem = curlist.pop(0)
oled_writetextaligned(tmpitem["title"], 0, 0, stdleftoffset, 1, fontwdSml)
oled_writetextaligned(tmpitem["value"], 0, 8, stdleftoffset, 1, fontwdSml)
oled_writetextaligned(argonsysinfo_kbstr(tmpitem["info"]["size"]), 0, 56, stdleftoffset, 1, fontwdSml)
if len(tmpitem['info']['state']) > 0:
oled_writetext( tmpitem['info']['state'], stdleftoffset, 8, fontwdSml )
if len(tmpitem['info']['rebuildstat']) > 0:
oled_writetext("Rebuild:" + tmpitem['info']['rebuildstat'], stdleftoffset, 16, fontwdSml)
# TODO: May need to use different method for each raid type (i.e. check raidlist['raidlist'][raidctr]['value'])
#oled_writetext("Used:"+str(int(100*tmpitem["info"]["used"]/tmpitem["info"]["size"]))+"%", stdleftoffset, 24, fontwdSml)
oled_writetext("Active:"+str(int(tmpitem["info"]["active"]))+"/"+str(int(tmpitem["info"]["devices"])), stdleftoffset, 32, fontwdSml)
oled_writetext("Working:"+str(int(tmpitem["info"]["working"]))+"/"+str(int(tmpitem["info"]["devices"])), stdleftoffset, 40, fontwdSml)
oled_writetext("Failed:"+str(int(tmpitem["info"]["failed"]))+"/"+str(int(tmpitem["info"]["devices"])), stdleftoffset, 48, fontwdSml)
needsUpdate = True
else:
# Next page due to error/no data
screenjogflag = 1
elif curscreen == "ram":
# RAM
try:
oled_loadbg("bgram")
tmpraminfo = argonsysinfo_getram()
oled_writetextaligned(tmpraminfo[0], stdleftoffset, 8, oledscreenwidth-stdleftoffset, 1, fontwdReg)
oled_writetextaligned("of", stdleftoffset, 24, oledscreenwidth-stdleftoffset, 1, fontwdReg)
oled_writetextaligned(tmpraminfo[1], stdleftoffset, 40, oledscreenwidth-stdleftoffset, 1, fontwdReg)
needsUpdate = True
except:
needsUpdate = False
# Next page due to error/no data
screenjogflag = 1
elif curscreen == "temp":
# Temp
try:
oled_loadbg("bgtemp")
hddtempctr = 0
maxcval = 0
mincval = 200
# Get min/max of hdd temp
hddtempobj = argonsysinfo_gethddtemp()
for curdev in hddtempobj:
if hddtempobj[curdev] < mincval:
mincval = hddtempobj[curdev]
if hddtempobj[curdev] > maxcval:
maxcval = hddtempobj[curdev]
hddtempctr = hddtempctr + 1
cpucval = argonsysinfo_getcputemp()
if hddtempctr > 0:
alltempobj = {"cpu": cpucval,"hdd min": mincval, "hdd max": maxcval}
# Update max C val to CPU Temp if necessary
if maxcval < cpucval:
maxcval = cpucval
displayrowht = 8
displayrow = 8
for curdev in alltempobj:
if temperature == "C":
# Celsius
tmpstr = str(alltempobj[curdev])
if len(tmpstr) > 4:
tmpstr = tmpstr[0:4]
else:
# Fahrenheit
tmpstr = str(32+9*(alltempobj[curdev])/5)
if len(tmpstr) > 5:
tmpstr = tmpstr[0:5]
if len(curdev) <= 3:
oled_writetext(curdev.upper()+": "+ tmpstr+ chr(167) +temperature, stdleftoffset, displayrow, fontwdSml)
else:
oled_writetext(curdev.upper()+":", stdleftoffset, displayrow, fontwdSml)
oled_writetext(" "+ tmpstr+ chr(167) +temperature, stdleftoffset, displayrow+displayrowht, fontwdSml)
displayrow = displayrow + displayrowht*2
else:
maxcval = cpucval
if temperature == "C":
# Celsius
tmpstr = str(cpucval)
if len(tmpstr) > 4:
tmpstr = tmpstr[0:4]
else:
# Fahrenheit
tmpstr = str(32+9*(cpucval)/5)
if len(tmpstr) > 5:
tmpstr = tmpstr[0:5]
oled_writetextaligned(tmpstr+ chr(167) +temperature, stdleftoffset, 24, oledscreenwidth-stdleftoffset, 1, fontwdReg)
# Temperature Bar: 40C is min, 80C is max
maxht = 21
barht = int(maxht*(maxcval-40)/40)
if barht > maxht:
barht = maxht
elif barht < 1:
barht = 1
oled_drawfilledrectangle(24, 20+(maxht-barht), 3, barht, 2)
needsUpdate = True
except:
needsUpdate = False
# Next page due to error/no data
screenjogflag = 1
elif curscreen == "ip":
# IP Address
try:
oled_loadbg("bgip")
oled_writetextaligned(argonsysinfo_getip(), 0, 8, oledscreenwidth, 1, fontwdReg)
needsUpdate = True
except:
needsUpdate = False
# Next page due to error/no data
screenjogflag = 1
elif curscreen == "logo1v5":
# Logo
try:
oled_loadbg("logo1v5")
needsUpdate = True
except:
needsUpdate = False
# Next page due to error/no data
screenjogflag = 1
else:
try:
oled_loadbg("bgtime")
# Date and Time HH:MM
curtime = datetime.datetime.now()
# Month/Day
outstr = str(curtime.day).strip()
if len(outstr) < 2:
outstr = " "+outstr
outstr = monthlist[curtime.month-1]+outstr
oled_writetextaligned(outstr, stdleftoffset, 8, oledscreenwidth-stdleftoffset, 1, fontwdReg)
# Day of Week
oled_writetextaligned(weekdaynamelist[curtime.weekday()], stdleftoffset, 24, oledscreenwidth-stdleftoffset, 1, fontwdReg)
# Time
outstr = str(curtime.minute).strip()
if len(outstr) < 2:
outstr = "0"+outstr
outstr = str(curtime.hour)+":"+outstr
if len(outstr) < 5:
outstr = "0"+outstr
oled_writetextaligned(outstr, stdleftoffset, 40, oledscreenwidth-stdleftoffset, 1, fontwdReg)
needsUpdate = True
except:
needsUpdate = False
# Next page due to error/no data
screenjogflag = 1
if needsUpdate == True:
if screensavermode == False:
# Update screen if not screen saver mode
oled_power(True)
oled_flushimage(prevscreen != curscreen)
oled_reset()
timeoutcounter = 0
while timeoutcounter<screenjogtime or screenjogtime == 0:
qdata = ""
if readq.empty() == False:
qdata = readq.get()
if qdata == "OLEDSWITCH":
# Trigger screen switch
screenjogflag = 1
# Reset Screen Saver
screensavermode = False
screensaverctr = 0
break
elif qdata == "OLEDSTOP":
# End OLED Thread
display_defaultimg()
return
else:
screensaverctr = screensaverctr + 1
if screensaversec <= screensaverctr and screensavermode == False:
screensavermode = True
oled_fill(0)
oled_reset()
oled_power(False)
if timeoutcounter == 0:
# Use 1 sec sleep get CPU usage
cpuusagelist = argonsysinfo_listcpuusage(1)
else:
time.sleep(1)
timeoutcounter = timeoutcounter + 1
if timeoutcounter >= 60 and screensavermode == False:
# Refresh data every minute, unless screensaver got triggered
screenjogflag = 0
break
display_defaultimg()
def display_defaultimg():
# Load default image
#oled_power(True)
#oled_loadbg("bgdefault")
#oled_flushimage()
oled_fill(0)
oled_reset()
if len(sys.argv) > 1:
cmd = sys.argv[1].upper()
if cmd == "SHUTDOWN":
# Signal poweroff
argonregister_signalpoweroff(bus)
elif cmd == "FANOFF":
# Turn off fan
argonregister_setfanspeed(bus,0)
if OLED_ENABLED == True:
display_defaultimg()
elif cmd == "SERVICE":
# Starts the power button and temperature monitor threads
try:
ipcq = Queue()
if len(sys.argv) > 2:
cmd = sys.argv[2].upper()
if cmd == "OLEDSWITCH":
t1 = Thread(target = argonpowerbutton_monitorswitch, args =(ipcq, ))
else:
t1 = Thread(target = argonpowerbutton_monitor, args =(ipcq, ))
t2 = Thread(target = temp_check)
if OLED_ENABLED == True:
t3 = Thread(target = display_loop, args =(ipcq, ))
t1.start()
t2.start()
if OLED_ENABLED == True:
t3.start()
ipcq.join()
except Exception:
sys.exit(1)

View File

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

View File

@@ -0,0 +1,333 @@
#!/usr/bin/python3
from luma.core.interface.serial import i2c
from luma.oled.device import ssd1306
from PIL import Image
import sys
import datetime
import math
import os
import time
# Initialize I2C Bus
import smbus
oledport=1
try:
bus=smbus.SMBus(1)
except Exception:
try:
oledport=0
# Older version
bus=smbus.SMBus(0)
except Exception:
print("Unable to detect i2c")
bus=None
ADDR_OLED=0x3c
OLED_WD=1
OLED_HT=1
oled_device=None
try:
oled_device=ssd1306(i2c(port=oledport, address=ADDR_OLED))
OLED_WD=oled_device.bounding_box[2]+1
OLED_HT=oled_device.bounding_box[3]+1
except Exception:
print("Unable to initialize OLED")
bus=None
OLED_NUMFONTCHAR=256
OLED_BUFFERIZE = ((OLED_WD*OLED_HT)>>3)
oled_imagebuffer = [0] * OLED_BUFFERIZE
def oled_getmaxY():
return OLED_HT
def oled_getmaxX():
return OLED_WD
def oled_loadbg(bgname):
if bgname == "bgblack":
oled_clearbuffer()
return
elif bgname == "bgwhite":
oled_clearbuffer(1)
return
try:
file = open("/etc/argon/oled/"+bgname+".bin", "rb")
bgbytes = list(file.read())
file.close()
ctr = len(bgbytes)
if ctr == OLED_BUFFERIZE:
oled_imagebuffer[:] = bgbytes
elif ctr > OLED_BUFFERIZE:
oled_imagebuffer[:] = bgbytes[0:OLED_BUFFERIZE]
else:
oled_imagebuffer[0:ctr] = bgbytes
# Clear the rest of the buffer
while ctr < OLED_BUFFERIZE:
oled_imagebuffer[ctr] = 0
ctr=ctr+1
except FileNotFoundError:
oled_clearbuffer()
def oled_clearbuffer(value = 0):
if value != 0:
value = 0xff
ctr = 0
while ctr < OLED_BUFFERIZE:
oled_imagebuffer[ctr] = value
ctr=ctr+1
def oled_writebyterow(x,y,bytevalue, mode = 0):
bufferoffset = OLED_WD*(y>>3) + x
if mode == 0:
oled_imagebuffer[bufferoffset] = bytevalue
elif mode == 1:
oled_imagebuffer[bufferoffset] = bytevalue^oled_imagebuffer[bufferoffset]
else:
oled_imagebuffer[bufferoffset] = bytevalue|oled_imagebuffer[bufferoffset]
def oled_writebuffer(x,y,value, mode = 0):
yoffset = y>>3
yshift = y&0x7
ybit = (1<<yshift)
ymask = 0xFF^ybit
if value != 0:
value = ybit
bufferoffset = OLED_WD*yoffset + x
curval = oled_imagebuffer[bufferoffset]
if mode & 1:
oled_imagebuffer[bufferoffset] = curval^value
else:
oled_imagebuffer[bufferoffset] = curval&ymask|value
def oled_fill(value):
oled_clearbuffer(value)
oled_flushimage()
def oled_flushimage(hidescreen = True):
if bus is None:
return
if hidescreen == True:
# Reset/Hide screen
oled_power(False)
tmplist = [0]*OLED_BUFFERIZE
# Each byte = 1 col x 8 rows
ymask = 0
yidx = 0
xmask = 0
xidx = 0
outbyte = 0
srcidx = 0
outoffsetidx = 0
outyoffset = 0
xoffset = 0
while srcidx < OLED_BUFFERIZE:
# OLED_WDx8 pixels at a time, y in reverse bit order
outyoffset = 0
yidx = 0
ymask = 1
while yidx < 8:
outoffsetidx = 0
outbyte = 0
xmask = 0x80
xidx = 0
xoffset = 0
while xoffset < OLED_WD:
if oled_imagebuffer[srcidx+xoffset] & ymask:
outbyte = outbyte | xmask
xmask = xmask >> 1
xidx = xidx + 1
if xidx >= 8:
tmplist[srcidx+outoffsetidx + outyoffset] = outbyte
xmask = 0x80
xidx = 0
outbyte = 0
outoffsetidx = outoffsetidx + 1
xoffset = xoffset + 1
outyoffset = outyoffset + (OLED_WD>>3)
yidx = yidx + 1
ymask = ymask << 1
srcidx = srcidx + OLED_WD
oled_device.display(Image.frombytes("1", [OLED_WD, OLED_HT], bytes(tmplist)))
if hidescreen == True:
# Display
oled_power(True)
def oled_drawfilledrectangle(x, y, wd, ht, mode = 0):
ymax = y + ht
cury = y&0xF8
xmax = x + wd
curx = x
if ((y & 0x7)) != 0:
yshift = y&0x7
bytevalue = (0xFF<<yshift)&0xFF
# If 8 no additional masking needed
if ymax-cury < 8:
yshift = 8-((ymax-cury)&0x7)
bytevalue = bytevalue & (0xFF>>yshift)
while curx < xmax:
oled_writebyterow(curx,cury,bytevalue, mode)
curx = curx + 1
cury = cury + 8
# Draw 8 rows at a time when possible
while cury + 8 < ymax:
curx = x
while curx < xmax:
oled_writebyterow(curx,cury,0xFF, mode)
curx = curx + 1
cury = cury + 8
if cury < ymax:
yshift = 8-((ymax-cury)&0x7)
bytevalue = (0xFF>>yshift)
curx = x
while curx < xmax:
oled_writebyterow(curx,cury,bytevalue, mode)
curx = curx + 1
def oled_writetextaligned(textdata, x, y, boxwidth, alignmode, charwd = 6, mode = 0):
leftoffset = 0
if alignmode == 1:
# Centered
leftoffset = (boxwidth-len(textdata)*charwd)>>1
elif alignmode == 2:
# Right aligned
leftoffset = (boxwidth-len(textdata)*charwd)
oled_writetext(textdata, x+leftoffset, y, charwd, mode)
def oled_writetext(textdata, x, y, charwd = 6, mode = 0):
if charwd < 6:
charwd = 6
charht = int((charwd<<3)/6)
if charht & 0x7:
charht = (charht&0xF8) + 8
try:
file = open("/etc/argon/oled/font"+str(charht)+"x"+str(charwd)+".bin", "rb")
fontbytes = list(file.read())
file.close()
except FileNotFoundError:
try:
# Default to smallest
file = open("/etc/argon/oled/font8x6.bin", "rb")
fontbytes = list(file.read())
file.close()
except FileNotFoundError:
return
if ((y & 0x7)) == 0:
# Use optimized loading
oled_fastwritetext(textdata, x, y, charht, charwd, fontbytes, mode)
return
numfontrow = charht>>3
ctr = 0
while ctr < len(textdata):
fontoffset = ord(textdata[ctr])*charwd
fontcol = 0
while fontcol < charwd and x < OLED_WD:
fontrow = 0
row = y
while fontrow < numfontrow and row < OLED_HT and x >= 0:
curbit = 0x80
curbyte = (fontbytes[fontoffset + fontcol + (OLED_NUMFONTCHAR*charwd*fontrow)])
subrow = 0
while subrow < 8 and row < OLED_HT:
value = 0
if (curbyte&curbit) != 0:
value = 1
oled_writebuffer(x,row,value, mode)
curbit = curbit >> 1
row = row + 1
subrow = subrow + 1
fontrow = fontrow + 1
fontcol = fontcol + 1
x = x + 1
ctr = ctr + 1
def oled_fastwritetext(textdata, x, y, charht, charwd, fontbytes, mode = 0):
numfontrow = charht>>3
ctr = 0
while ctr < len(textdata):
fontoffset = ord(textdata[ctr])*charwd
fontcol = 0
while fontcol < charwd and x < OLED_WD:
fontrow = 0
row = y&0xF8
while fontrow < numfontrow and row < OLED_HT and x >= 0:
curbyte = (fontbytes[fontoffset + fontcol + (OLED_NUMFONTCHAR*charwd*fontrow)])
oled_writebyterow(x,row,curbyte, mode)
fontrow = fontrow + 1
row = row + 8
fontcol = fontcol + 1
x = x + 1
ctr = ctr + 1
return
def oled_power(turnon = True):
if bus is None:
return
try:
if turnon == True:
oled_device.show()
else:
oled_device.hide()
except:
return
def oled_inverse(enable = True):
# Not supported?
return
def oled_fullwhite(enable = True):
# Not supported?
return
def oled_reset():
return

View File

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

View File

@@ -0,0 +1,89 @@
# For Libreelec/Lakka, note that we need to add system paths
# import sys
# sys.path.append('/storage/.kodi/addons/virtual.rpi-tools/lib')
import gpiod
import os
import time
# This function is the thread that monitors activity in our shutdown pin
# The pulse width is measured, and the corresponding shell command will be issued
def argonpowerbutton_monitor(writeq):
try:
# Reference https://github.com/brgl/libgpiod/blob/master/bindings/python/examples/gpiomon.py
# Pin Assignments
LINE_SHUTDOWN=4
try:
# Pi5 mapping
chip = gpiod.Chip('4')
except Exception as gpioerr:
# Old mapping
chip = gpiod.Chip('0')
lineobj = chip.get_line(LINE_SHUTDOWN)
lineobj.request(consumer="argon", type=gpiod.LINE_REQ_EV_BOTH_EDGES)
while True:
hasevent = lineobj.event_wait(10)
if hasevent:
pulsetime = 1
eventdata = lineobj.event_read()
if eventdata.type == gpiod.LineEvent.RISING_EDGE:
# Time pulse data
while lineobj.get_value() == 1:
time.sleep(0.01)
pulsetime += 1
if pulsetime >=2 and pulsetime <=3:
# Testing
#writeq.put("OLEDSWITCH")
writeq.put("OLEDSTOP")
os.system("reboot")
break
elif pulsetime >=4 and pulsetime <=5:
writeq.put("OLEDSTOP")
os.system("shutdown now -h")
break
elif pulsetime >=6 and pulsetime <=7:
writeq.put("OLEDSWITCH")
lineobj.release()
chip.close()
except Exception:
writeq.put("ERROR")
def argonpowerbutton_monitorswitch(writeq):
try:
# Reference https://github.com/brgl/libgpiod/blob/master/bindings/python/examples/gpiomon.py
# Pin Assignments
LINE_SHUTDOWN=4
try:
# Pi5 mapping
chip = gpiod.Chip('4')
except Exception as gpioerr:
# Old mapping
chip = gpiod.Chip('0')
lineobj = chip.get_line(LINE_SHUTDOWN)
lineobj.request(consumer="argon", type=gpiod.LINE_REQ_EV_BOTH_EDGES)
while True:
hasevent = lineobj.event_wait(10)
if hasevent:
pulsetime = 1
eventdata = lineobj.event_read()
if eventdata.type == gpiod.LineEvent.RISING_EDGE:
# Time pulse data
while lineobj.get_value() == 1:
time.sleep(0.01)
pulsetime += 1
if pulsetime >= 10:
writeq.put("OLEDSWITCH")
lineobj.release()
chip.close()
except Exception:
writeq.put("ERROR")

View File

@@ -0,0 +1,66 @@
# For Libreelec/Lakka, note that we need to add system paths
# import sys
# sys.path.append('/storage/.kodi/addons/virtual.rpi-tools/lib')
import RPi.GPIO as GPIO
import os
import time
# This function is the thread that monitors activity in our shutdown pin
# The pulse width is measured, and the corresponding shell command will be issued
def argonpowerbutton_monitor(writeq):
try:
# Pin Assignments
PIN_SHUTDOWN=4
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIN_SHUTDOWN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
while True:
pulsetime = 1
GPIO.wait_for_edge(PIN_SHUTDOWN, GPIO.RISING)
time.sleep(0.01)
while GPIO.input(PIN_SHUTDOWN) == GPIO.HIGH:
time.sleep(0.01)
pulsetime += 1
if pulsetime >=2 and pulsetime <=3:
# Testing
#writeq.put("OLEDSWITCH")
writeq.put("OLEDSTOP")
os.system("reboot")
break
elif pulsetime >=4 and pulsetime <=5:
writeq.put("OLEDSTOP")
os.system("shutdown now -h")
break
elif pulsetime >=6 and pulsetime <=7:
writeq.put("OLEDSWITCH")
except Exception:
writeq.put("ERROR")
GPIO.cleanup()
def argonpowerbutton_monitorswitch(writeq):
try:
# Pin Assignments
PIN_SHUTDOWN=4
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIN_SHUTDOWN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
while True:
pulsetime = 1
GPIO.wait_for_edge(PIN_SHUTDOWN, GPIO.RISING)
time.sleep(0.01)
while GPIO.input(PIN_SHUTDOWN) == GPIO.HIGH:
time.sleep(0.01)
pulsetime += 1
if pulsetime >= 10:
writeq.put("OLEDSWITCH")
except Exception:
writeq.put("ERROR")
GPIO.cleanup()

View File

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

View File

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

642
source/scripts/argonrtc.py Normal file
View File

@@ -0,0 +1,642 @@
#!/usr/bin/python3
import os
import datetime
#########
# Describe Methods
#########
# Helper method to add proper suffix to numbers
def getNumberSuffix(numval):
onesvalue = numval % 10
if onesvalue == 1:
return "st"
elif onesvalue == 2:
return "nd"
elif onesvalue == 3:
return "rd"
return "th"
def describeHourMinute(hour, minute):
if hour < 0:
return ""
outstr = ""
ampmstr = ""
if hour <= 0:
hour = 0
outstr = outstr + "12"
ampmstr = "am"
elif hour <= 12:
outstr = outstr + str(hour)
if hour == 12:
ampmstr = "pm"
else:
ampmstr = "am"
else:
outstr = outstr + str(hour-12)
ampmstr = "pm"
if minute >= 10:
outstr = outstr+":"
elif minute > 0:
outstr = outstr+":0"
else:
if hour == 0:
ampmstr = "mn"
elif hour == 12:
ampmstr = "nn"
return outstr+ampmstr
if minute <= 0:
minute = 0
outstr = outstr+str(minute)
return outstr+ampmstr
# Describe Schedule Parameter Values
def describeSchedule(monthlist, weekdaylist, datelist, hourlist, minutelist):
weekdaynamelist = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
monthnamelist = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
curprefix = ""
hasDate = False
hasMonth = False
foundvalue = False
monthdatestr = ""
for curmonth in monthlist:
for curdate in datelist:
if curdate >= 0:
hasDate = True
if curmonth >= 0:
hasMonth = True
monthdatestr = monthdatestr + "," + monthnamelist[curmonth-1]+" "+str(curdate) + getNumberSuffix(curdate)
else:
monthdatestr = monthdatestr + ","+str(curdate) + getNumberSuffix(curdate)
else:
if curmonth >= 0:
monthdatestr = monthdatestr + "," + monthnamelist[curmonth-1]
if len(monthdatestr) > 0:
foundvalue = True
# Remove Leading Comma
monthdatestr = monthdatestr[1:]
if hasMonth == True:
curprefix = "Annually:"
else:
curprefix = "Monthly:"
monthdatestr = monthdatestr + " of the Month"
monthdatestr = " Every "+monthdatestr
weekdaystr = ""
for curweekday in weekdaylist:
if curweekday >= 0:
hasDate = True
weekdaystr = weekdaystr + "," + weekdaynamelist[curweekday]
if len(weekdaystr) > 0:
foundvalue = True
# Remove Leading Comma
weekdaystr = weekdaystr[1:]
if len(curprefix) == 0:
curprefix = "Weekly:"
weekdaystr = " on " + weekdaystr
else:
weekdaystr = ",on " + weekdaystr
hasHour = False
hasMinute = False
hourminstr = ""
for curhour in hourlist:
for curminute in minutelist:
if curhour >= 0:
hasHour = True
if curminute >= 0:
hasMinute = True
hourminstr = hourminstr + "," + describeHourMinute(curhour, curminute)
elif curminute >= 0:
hasMinute = True
hourminstr = hourminstr + "," + str(curminute) + getNumberSuffix(curminute)
if len(hourminstr) > 0:
foundvalue = True
# Remove Leading Comma
hourminstr = hourminstr[1:]
if hasHour == True:
if hasDate == True:
hourminstr = "at " + hourminstr
else:
hourminstr = "Daily: " + hourminstr
if hasMinute == False:
hourminstr = hourminstr + " every minute"
else:
if hourminstr == "0":
hourminstr = "At the start of every hour"
else:
hourminstr = "Hourly: At " + hourminstr + " minute"
else:
hourminstr = "Every minute"
if len(curprefix) > 0:
hourminstr = ","+hourminstr
return (curprefix + monthdatestr + weekdaystr + hourminstr).strip()
#########
# Alarm
#########
# Alarm to UTC/Local time
def convertAlarmTimezone(weekday, caldate, hour, minute, toutc):
utcdiffsec = getLocaltimeOffset().seconds
if toutc == False:
utcdiffsec = utcdiffsec*(-1)
utcdiffsec = utcdiffsec - (utcdiffsec%60)
utcdiffmin = utcdiffsec % 3600
utcdiffhour = int((utcdiffsec - utcdiffmin)/3600)
utcdiffmin = int(utcdiffmin/60)
addhour = 0
if minute >= 0:
minute = minute - utcdiffmin
if minute < 0:
addhour = -1
minute = minute + 60
elif minute > 59:
addhour = 1
minute = minute - 60
addday = 0
if hour >= 0:
hour = hour - utcdiffhour
tmphour = hour + addhour
if hour < 0:
hour = hour + 24
elif hour > 23:
hour = hour - 24
if tmphour < 0:
addday = -1
elif tmphour > 23:
addday = 1
if addday != 0:
if weekday >= 0:
weekday = weekday + addday
if weekday < 0:
weekday = weekday + 7
elif weekday > 6:
weekday = weekday - 7
if caldate > 0:
# Edge cases might not be handled properly though
curtime = datetime.datetime.now()
maxmonthdate = getLastMonthDate(curtime.year, curtime.month)
caldate = caldate + addday
if caldate == 0:
# move to end of the month
caldate = maxmonthdate
elif caldate > maxmonthdate:
# move to next month
caldate = 1
return [weekday, caldate, hour, minute]
# Get RTC Alarm Setting (Negative values ignored)
def getRTCAlarm(weekday, caldate, hour, minute):
hasError = False
if caldate < 1 and weekday < 0 and hour < 0 and minute < 0:
hasError = True
elif minute > 59:
hasError = True
elif hour > 23:
hasError = True
elif weekday > 6:
hasError = True
elif caldate > 31:
hasError = True
if hasError == True:
return [-1, -1, -1, -1]
# Convert to UTC
return convertAlarmTimezone(weekday, caldate, hour, minute, True)
#########
# Date/Time tools
#########
# Get local time vs UTC
def getLocaltimeOffset():
localdatetime = datetime.datetime.now()
utcdatetime = datetime.datetime.fromtimestamp(localdatetime.timestamp(), datetime.timezone.utc)
# Remove TZ info to allow subtraction
utcdatetime = utcdatetime.replace(tzinfo = None)
return localdatetime - utcdatetime
# Sync Time to RTC Time (for Daemon use)
def updateSystemTime(rtctime):
os.system("date -s '"+rtctime.isoformat()+"' >/dev/null 2>&1")
#########
# Config
#########
# Load config value as array of integers
def getConfigValue(valuestr):
try:
if valuestr == "*":
return [-1]
tmplist = valuestr.split(",")
map_object = map(int, tmplist)
return list(map_object)
except:
return [-1]
# Load config line data as array of Command schedule
def newCommandSchedule(curline):
result = []
linedata = curline.split(" ")
if len(linedata) < 6:
return result
minutelist = getConfigValue(linedata[0])
hourlist = getConfigValue(linedata[1])
datelist = getConfigValue(linedata[2])
#monthlist = getConfigValue(linedata[3])
monthlist = [-1] # Certain edge cases will not be handled properly
weekdaylist = getConfigValue(linedata[4])
cmd = ""
ctr = 5
while ctr < len(linedata):
cmd = cmd + " " + linedata[ctr]
ctr = ctr + 1
cmd = cmd.strip()
for curmin in minutelist:
for curhour in hourlist:
for curdate in datelist:
for curmonth in monthlist:
for curweekday in weekdaylist:
result.append({ "minute": curmin, "hour": curhour, "date": curdate, "month":curmonth, "weekday": curweekday, "cmd":cmd })
return result
# Save updated config file
def saveConfigList(fname, configlist):
f = open(fname, "w")
f.write("#\n")
f.write("# Argon RTC Configuration\n")
f.write("# - Follows cron general format, but with only * and csv support\n")
f.write("# - Each row follows the following format:\n")
f.write("# min hour date month dayOfWeek Command\n")
f.write("# e.g. Shutdown daily at 1am\n")
f.write("# 0 1 * * * off\n")
f.write("# Shutdown daily at 1am and 1pm\n")
f.write("# 0 1,13 * * * off\n")
f.write("# - Commands are currently on or off only\n")
f.write("# - Limititations\n")
f.write("# Requires MINUTE value\n")
f.write("# Month values are ignored (edge cases not supported)\n")
f.write("#\n")
for config in configlist:
f.write(config+"\n")
f.close()
# Remove config line
def removeConfigEntry(fname, entryidx):
configlist = loadConfigList(fname)
if len(configlist) > entryidx:
configlist.pop(entryidx)
saveConfigList(fname, configlist)
# Load config list (removes invalid data)
def loadConfigList(fname):
try:
result = []
with open(fname, "r") as fp:
for curline in fp:
if not curline:
continue
curline = curline.strip().replace('\t', ' ')
# Handle special characters that get encoded
tmpline = "".join([c if 0x20<=ord(c) and ord(c)<=0x7e else "" for c in curline])
if not tmpline:
continue
if tmpline[0] == "#":
continue
checkdata = tmpline.split(" ")
if len(checkdata) > 5:
# Don't include every minute type of schedule
if checkdata[0] != "*":
result.append(tmpline)
return result
except:
return []
# Form Command Schedule list from config list
def formCommandScheduleList(configlist):
try:
result = []
for config in configlist:
result = result + newCommandSchedule(config)
return result
except:
return []
# Describe config list entry
def describeConfigListEntry(configlistitem):
linedata = configlistitem.split(" ")
if len(linedata) < 6:
return ""
minutelist = getConfigValue(linedata[0])
hourlist = getConfigValue(linedata[1])
datelist = getConfigValue(linedata[2])
#monthlist = getConfigValue(linedata[3])
monthlist = [-1] # Certain edge cases will not be handled properly
weekdaylist = getConfigValue(linedata[4])
cmd = ""
ctr = 5
while ctr < len(linedata):
cmd = cmd + " " + linedata[ctr]
ctr = ctr + 1
cmd = cmd.strip().lower()
if cmd == "on":
cmd = "Startup"
else:
cmd = "Shutdown"
return cmd+" | "+describeSchedule(monthlist, weekdaylist, datelist, hourlist, minutelist)
# Describe config list and show indices
def describeConfigList(fname):
# 1 is reserved for New schedule
ctr = 2
configlist = loadConfigList(fname)
for config in configlist:
tmpline = describeConfigListEntry(config)
if len(tmpline) > 0:
print(" "+str(ctr)+". ", tmpline)
ctr = ctr + 1
if ctr == 2:
print(" No Existing Schedules")
# Check Command schedule if it should fire for the give time
def checkDateForCommandSchedule(commandschedule, datetimeobj):
testminute = commandschedule.get("minute", -1)
testhour = commandschedule.get("hour", -1)
testdate = commandschedule.get("date", -1)
testmonth = commandschedule.get("month", -1)
testweekday = commandschedule.get("weekday", -1)
if testminute < 0 or testminute == datetimeobj.minute:
if testhour < 0 or testhour == datetimeobj.hour:
if testdate < 0 or testdate == datetimeobj.day:
if testmonth < 0 or testmonth == datetimeobj.month:
if testweekday < 0:
return True
else:
# python Sunday = 6, RTC Sunday = 0
weekDay = datetimeobj.weekday()
if weekDay == 6:
weekDay = 0
else:
weekDay = weekDay + 1
if testweekday == weekDay:
return True
return False
# Get current command
def getCommandForTime(commandschedulelist, datetimeobj, checkcmd):
ctr = 0
while ctr < len(commandschedulelist):
testcmd = commandschedulelist[ctr].get("cmd", "")
if (testcmd.lower() == checkcmd or len(checkcmd) == 0) and len(testcmd) > 0:
if checkDateForCommandSchedule(commandschedulelist[ctr], datetimeobj) == True:
return testcmd
ctr = ctr + 1
return ""
# Get Last Date of Month
def getLastMonthDate(year, month):
if month < 12:
testtime = datetime.datetime(year, month+1, 1)
else:
testtime = datetime.datetime(year+1, 1, 1)
testtime = testtime - datetime.timedelta(days=1)
return testtime.day
# Increment to the next iteration of command schedule
def incrementCommandScheduleTime(commandschedule, testtime, addmode):
testminute = commandschedule.get("minute", -1)
testhour = commandschedule.get("hour", -1)
testdate = commandschedule.get("date", -1)
testmonth = commandschedule.get("month", -1)
testweekday = commandschedule.get("weekday", -1)
if addmode == "minute":
testfield = commandschedule.get(addmode, -1)
if testfield < 0:
if testtime.minute < 59:
return testtime + datetime.timedelta(minutes=1)
else:
return incrementCommandScheduleTime(commandschedule, testtime.replace(minute=0), "hour")
else:
return incrementCommandScheduleTime(commandschedule, testtime, "hour")
elif addmode == "hour":
testfield = commandschedule.get(addmode, -1)
if testfield < 0:
if testtime.hour < 23:
return testtime + datetime.timedelta(hours=1)
else:
return incrementCommandScheduleTime(commandschedule, testtime.replace(hour=0), "date")
else:
return incrementCommandScheduleTime(commandschedule, testtime, "date")
elif addmode == "date":
testfield = commandschedule.get(addmode, -1)
if testfield < 0:
maxmonthdate = getLastMonthDate(testtime.year, testtime.month)
if testtime.day < maxmonthdate:
return testtime + datetime.timedelta(days=1)
else:
return incrementCommandScheduleTime(commandschedule, testtime.replace(day=1), "month")
else:
return incrementCommandScheduleTime(commandschedule, testtime, "month")
elif addmode == "month":
testfield = commandschedule.get(addmode, -1)
if testfield < 0:
nextmonth = testtime.month
nextyear = testtime.year
while True:
if nextmonth < 12:
nextmonth = nextmonth + 1
else:
nextmonth = 1
nextyear = nextyear + 1
maxmonthdate = getLastMonthDate(nextyear, nextmonth)
if testtime.day <= maxmonthdate:
return testtime.replace(month=nextmonth, year=nextyear)
else:
return incrementCommandScheduleTime(commandschedule, testtime, "year")
else:
# Year
if testtime.month == 2 and testtime.day == 29:
# Leap day handling
nextyear = testtime.year
while True:
nextyear = nextyear + 1
maxmonthdate = getLastMonthDate(nextyear, testtime.month)
if testtime.day <= maxmonthdate:
return testtime.replace(year=nextyear)
else:
return testtime.replace(year=(testtime.year+1))
# Set Next Alarm on RTC
def getNextAlarm(commandschedulelist, prevdatetime):
curtime = datetime.datetime.now()
if prevdatetime > curtime:
return [prevdatetime, -1, -1, -1, -1]
# Divisible by 4 for leap day
checklimityears = 12
foundnextcmd = False
nextcommandschedule = {}
# To be sure it's later than any schedule
nextcommandtime = curtime.replace(year=(curtime.year+checklimityears))
ctr = 0
while ctr < len(commandschedulelist):
testcmd = commandschedulelist[ctr].get("cmd", "").lower()
if testcmd == "on":
invaliddata = False
testminute = commandschedulelist[ctr].get("minute", -1)
testhour = commandschedulelist[ctr].get("hour", -1)
testdate = commandschedulelist[ctr].get("date", -1)
testmonth = commandschedulelist[ctr].get("month", -1)
testweekday = commandschedulelist[ctr].get("weekday", -1)
tmpminute = testminute
tmphour = testhour
tmpdate = testdate
tmpmonth = testmonth
tmpyear = curtime.year
if tmpminute < 0:
tmpminute = curtime.minute
if tmphour < 0:
tmphour = curtime.hour
if tmpdate < 0:
tmpdate = curtime.day
if tmpmonth < 0:
tmpmonth = curtime.month
maxmonthdate = getLastMonthDate(tmpyear, tmpmonth)
if tmpdate > maxmonthdate:
# Invalid month date
if testdate < 0:
tmpdate = maxmonthdate
else:
# Date is fixed
if testminute < 0:
tmpminute = 0
if testhour < 0:
tmphour = 0
if testmonth < 0 and testdate <= 31:
# Look for next valid month
while tmpdate > maxmonthdate:
if tmpmonth < 12:
tmpmonth = tmpmonth + 1
else:
tmpmonth = 1
tmpyear = tmpyear + 1
maxmonthdate = getLastMonthDate(tmpyear, tmpmonth)
elif tmpdate == 29 and tmpmonth == 2:
# Fixed to leap day
while tmpdate > maxmonthdate:
tmpyear = tmpyear + 1
maxmonthdate = getLastMonthDate(tmpyear, tmpmonth)
else:
invaliddata = True
if invaliddata == False:
try:
testtime = datetime.datetime(tmpyear, tmpmonth, tmpdate, tmphour, tmpminute)
except:
# Force time diff
testtime = curtime - datetime.timedelta(hours=1)
tmptimediff = (curtime - testtime).total_seconds()
else:
tmptimediff = 0
if testweekday >= 0:
# Day of Week check
# python Sunday = 6, RTC Sunday = 0
weekDay = testtime.weekday()
if weekDay == 6:
weekDay = 0
else:
weekDay = weekDay + 1
if weekDay != testweekday or tmptimediff > 0:
# Resulting 0-ed time will be <= the testtime
if testminute < 0:
testtime = testtime.replace(minute=0)
if testhour < 0:
testtime = testtime.replace(hour=0)
dayoffset = testweekday-weekDay
if dayoffset < 0:
dayoffset = dayoffset + 7
elif dayoffset == 0:
dayoffset = 7
testtime = testtime + datetime.timedelta(days=dayoffset)
# Just look for the next valid weekday; Can be optimized
while checkDateForCommandSchedule(commandschedulelist[ctr], testtime) == False and (testtime.year - curtime.year) < checklimityears:
testtime = testtime + datetime.timedelta(days=7)
if (testtime.year - curtime.year) >= checklimityears:
# Too many iterations, abort/ignore
tmptimediff = 0
else:
tmptimediff = (curtime - testtime).total_seconds()
if tmptimediff > 0:
# Find next iteration that's greater than the current time (Day of Week check already handled)
while tmptimediff >= 0:
testtime = incrementCommandScheduleTime(commandschedulelist[ctr], testtime, "minute")
tmptimediff = (curtime - testtime).total_seconds()
if nextcommandtime > testtime and tmptimediff < 0:
nextcommandschedule = commandschedulelist[ctr]
nextcommandtime = testtime
foundnextcmd = True
ctr = ctr + 1
if foundnextcmd == True:
# Schedule Alarm
# Assume no date,weekday involved just shift the hour and minute accordingly
paramminute = nextcommandschedule.get("minute", -1)
paramhour = nextcommandschedule.get("hour", -1)
if nextcommandschedule.get("weekday", -1) >=0 or nextcommandschedule.get("date", -1) > 0:
# Set alarm based on hour/minute of next occurrence to factor in timezone changes if any
paramminute = nextcommandtime.minute
paramhour = nextcommandtime.hour
weekday, caldate, hour, minute = getRTCAlarm(nextcommandschedule.get("weekday", -1), nextcommandschedule.get("date", -1), paramhour, paramminute)
return [nextcommandtime, weekday, caldate, hour, minute]
# This will ensure that this will be replaced next iteration
return [curtime, -1, -1, -1, -1]

View File

@@ -0,0 +1,172 @@
#!/usr/bin/python3
import sys
import os
sys.path.append("/etc/argon/")
from argonsysinfo import *
from argonregister import *
from argononed import *
def getFahrenheit(celsiustemp):
try:
return (32+9*(celsiustemp)/5)
except:
return 0
temperature="C"
tmpconfig=load_unitconfig(UNIT_CONFIGFILE)
if "temperature" in tmpconfig:
temperature = tmpconfig["temperature"]
baseleftoffset = ""
stdleftoffset = " "
#if len(sys.argv) > 2:
# baseleftoffset = stdleftoffset
baseleftoffset = stdleftoffset
argctr = 1
while argctr < len(sys.argv):
cmd = sys.argv[argctr].lower()
argctr = argctr + 1
if baseleftoffset != "":
print(cmd.upper(),"INFORMATION:")
if cmd == "cpu usage":
# CPU Usage
curlist = argonsysinfo_listcpuusage()
while len(curlist) > 0:
curline = ""
tmpitem = curlist.pop(0)
curline = tmpitem["title"]+": "+str(tmpitem["value"])+"%"
print(baseleftoffset+curline)
elif cmd == "storage":
# Storage Info
curlist = []
try:
tmpobj = argonsysinfo_listhddusage()
for curdev in tmpobj:
curlist.append({"title": curdev, "value": argonsysinfo_kbstr(tmpobj[curdev]['total']), "usage": int(100*tmpobj[curdev]['used']/tmpobj[curdev]['total']) })
#curlist = argonsysinfo_liststoragetotal()
except Exception:
curlist = []
while len(curlist) > 0:
tmpitem = curlist.pop(0)
# Right column first, safer to overwrite white space
print(baseleftoffset+tmpitem["title"], str(tmpitem["usage"])+"%","used of", tmpitem["value"])
elif cmd == "raid":
# Raid Info
curlist = []
try:
tmpobj = argonsysinfo_listraid()
curlist = tmpobj['raidlist']
except Exception:
curlist = []
if len(curlist) > 0:
tmpitem = curlist.pop(0)
print(baseleftoffset+tmpitem["title"], tmpitem["value"], argonsysinfo_kbstr(tmpitem["info"]["size"]))
if len(tmpitem['info']['state']) > 0:
print(baseleftoffset+stdleftoffset,tmpitem['info']['state'])
if len(tmpitem['info']['rebuildstat']) > 0:
print(baseleftoffset+stdleftoffset,"Rebuild:" + tmpitem['info']['rebuildstat'])
print(baseleftoffset+stdleftoffset,"Active:"+str(int(tmpitem["info"]["active"]))+"/"+str(int(tmpitem["info"]["devices"])))
print(baseleftoffset+stdleftoffset,"Working:"+str(int(tmpitem["info"]["working"]))+"/"+str(int(tmpitem["info"]["devices"])))
print(baseleftoffset+stdleftoffset,"Failed:"+str(int(tmpitem["info"]["failed"]))+"/"+str(int(tmpitem["info"]["devices"])))
else:
print(baseleftoffset+stdleftoffset,"N/A")
elif cmd == "ram":
# RAM
try:
tmpraminfo = argonsysinfo_getram()
print(baseleftoffset+tmpraminfo[0],"of", tmpraminfo[1])
except Exception:
pass
elif cmd == "temperature":
# Temp
try:
hddtempctr = 0
maxcval = 0
mincval = 200
alltempobj = {"cpu": argonsysinfo_getcputemp()}
# Get min/max of hdd temp
hddtempobj = argonsysinfo_gethddtemp()
for curdev in hddtempobj:
alltempobj[curdev] = hddtempobj[curdev]
if hddtempobj[curdev] < mincval:
mincval = hddtempobj[curdev]
if hddtempobj[curdev] > maxcval:
maxcval = hddtempobj[curdev]
hddtempctr = hddtempctr + 1
if hddtempctr > 0:
alltempobj["hdd min"]=mincval
alltempobj["hdd max"]=maxcval
for curdev in alltempobj:
if temperature == "C":
# Celsius
tmpstr = str(alltempobj[curdev])
if len(tmpstr) > 4:
tmpstr = tmpstr[0:4]
else:
# Fahrenheit
tmpstr = str(getFahrenheit(alltempobj[curdev]))
if len(tmpstr) > 5:
tmpstr = tmpstr[0:5]
print(baseleftoffset+curdev.upper()+": "+ tmpstr+ chr(176) +temperature)
except Exception:
pass
elif cmd == "ip":
# IP Address
try:
print(baseleftoffset+argonsysinfo_getip())
except Exception:
pass
elif cmd == "fan speed":
# Fan Speed
try:
newspeed = argonregister_getfanspeed(argonregister_initializebusobj())
if newspeed <= 0:
fanconfig = load_fancpuconfig()
fanhddconfig = load_fanhddconfig()
# Speed based on CPU Temp
val = argonsysinfo_getcputemp()
newspeed = get_fanspeed(val, fanconfig)
val = argonsysinfo_getmaxhddtemp()
tmpspeed = get_fanspeed(val, fanhddconfig)
if tmpspeed > newspeed:
newspeed = tmpspeed
print(baseleftoffset+"Fan Speed",str(newspeed))
except Exception:
pass
elif cmd == "fan configuration":
fanconfig = load_fancpuconfig()
fanhddconfig = load_fanhddconfig()
if len(fanhddconfig) > 0:
print(baseleftoffset+"Fan Temp-Speed cut-offs")
for curconfig in fanconfig:
print(baseleftoffset+stdleftoffset,curconfig)
if len(fanhddconfig) > 0:
print(baseleftoffset+"HDD Temp-Speed cut-offs")
for curconfig in fanhddconfig:
print(baseleftoffset+stdleftoffset,curconfig)

View File

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

View File

@@ -0,0 +1,42 @@
#!/bin/bash
NTPSERVER="time.google.com"
TMPCONFIG=/dev/shm/tmpconfig.conf
# timesyncd
CONFIG=/etc/systemd/timesyncd.conf
if [ -f "$CONFIG" ]
then
cat "$CONFIG" | grep -v -e 'NTP=' > "$TMPCONFIG"
echo "NTP=$NTPSERVER" >> "$TMPCONFIG"
sudo chown root:root "$TMPCONFIG"
sudo chmod 644 "$TMPCONFIG"
sudo mv "$TMPCONFIG" "$CONFIG"
# /usr/sbin/ntpd
sudo service systemd-timesyncd restart > /dev/null 2>&1
fi
for CURSERVICECONFIG in ntp chrony
do
CONFIG=/etc/${CURSERVICECONFIG}.conf
if [ -f "$CONFIG" ]
then
cat "$CONFIG" | grep -v -e 'pool ' > "$TMPCONFIG"
#echo "server $NTPSERVER" >> "$TMPCONFIG"
echo "pool time1.google.com iburst" >> "$TMPCONFIG"
echo "pool time2.google.com iburst" >> "$TMPCONFIG"
echo "pool time3.google.com iburst" >> "$TMPCONFIG"
echo "pool time4.google.com iburst" >> "$TMPCONFIG"
sudo chown root:root "$TMPCONFIG"
sudo chmod 644 "$TMPCONFIG"
sudo mv "$TMPCONFIG" "$CONFIG"
sudo service ${CURSERVICECONFIG} restart > /dev/null 2>&1
fi
done