From 135b091567a866257c7a49e0c9b96e40adb2aae1 Mon Sep 17 00:00:00 2001 From: eka Date: Sun, 27 Jul 2025 22:42:47 +0500 Subject: [PATCH] Initial commit --- .gitignore | 1 + README.md | 9 ++ atxctl/README.md | 3 + atxctl/VERSION | 1 + atxctl/atx-reset | 9 ++ atxctl/atx-status | 9 ++ atxctl/atx-swich-power | 9 ++ atxctl/atxctl | 9 ++ atxctl/main.py | 110 +++++++++++++++++++ atxctl/requirements.txt | 2 + ups/README.md | 0 ups/VERSION | 1 + ups/main.py | 198 ++++++++++++++++++++++++++++++++++ ups/requirements.txt | 3 + ups/systemd/ups-check.service | 9 ++ ups/systemd/ups-check.timer | 11 ++ ups/systemd/ups-init.service | 15 +++ ups/ups-init | 9 ++ ups/ups-status | 9 ++ ups/upsctl | 9 ++ 20 files changed, 426 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 atxctl/README.md create mode 100644 atxctl/VERSION create mode 100755 atxctl/atx-reset create mode 100755 atxctl/atx-status create mode 100755 atxctl/atx-swich-power create mode 100755 atxctl/atxctl create mode 100644 atxctl/main.py create mode 100644 atxctl/requirements.txt create mode 100644 ups/README.md create mode 100644 ups/VERSION create mode 100644 ups/main.py create mode 100644 ups/requirements.txt create mode 100644 ups/systemd/ups-check.service create mode 100644 ups/systemd/ups-check.timer create mode 100644 ups/systemd/ups-init.service create mode 100755 ups/ups-init create mode 100755 ups/ups-status create mode 100755 ups/upsctl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..626724e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*/venv diff --git a/README.md b/README.md new file mode 100644 index 0000000..ab6c36e --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# ARM + +## Подготовка VENV + + python -m venv venv + source venv/bin/activate + + # Установка зависимостей + pip install --only-binary spidev -r requirements.txt diff --git a/atxctl/README.md b/atxctl/README.md new file mode 100644 index 0000000..eb4044c --- /dev/null +++ b/atxctl/README.md @@ -0,0 +1,3 @@ +# ATXCTL + +Утилита для управления питанием материнской платы diff --git a/atxctl/VERSION b/atxctl/VERSION new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/atxctl/VERSION @@ -0,0 +1 @@ +1.0.0 diff --git a/atxctl/atx-reset b/atxctl/atx-reset new file mode 100755 index 0000000..6bd345e --- /dev/null +++ b/atxctl/atx-reset @@ -0,0 +1,9 @@ +#!/bin/bash + +# Путь к виртуальному окружению +VENV_PATH="./venv" +# Путь к основному скрипту +SCRIPT_PATH="./main.py" + +# Запуск скрипта с использованием Python из venv +"$VENV_PATH/bin/python" "$SCRIPT_PATH" --reset diff --git a/atxctl/atx-status b/atxctl/atx-status new file mode 100755 index 0000000..2cde240 --- /dev/null +++ b/atxctl/atx-status @@ -0,0 +1,9 @@ +#!/bin/bash + +# Путь к виртуальному окружению +VENV_PATH="./venv" +# Путь к основному скрипту +SCRIPT_PATH="./main.py" + +# Запуск скрипта с использованием Python из venv +"$VENV_PATH/bin/python" "$SCRIPT_PATH" --status diff --git a/atxctl/atx-swich-power b/atxctl/atx-swich-power new file mode 100755 index 0000000..6e06073 --- /dev/null +++ b/atxctl/atx-swich-power @@ -0,0 +1,9 @@ +#!/bin/bash + +# Путь к виртуальному окружению +VENV_PATH="./venv" +# Путь к основному скрипту +SCRIPT_PATH="./main.py" + +# Запуск скрипта с использованием Python из venv +"$VENV_PATH/bin/python" "$SCRIPT_PATH" --switch-power diff --git a/atxctl/atxctl b/atxctl/atxctl new file mode 100755 index 0000000..1827a48 --- /dev/null +++ b/atxctl/atxctl @@ -0,0 +1,9 @@ +#!/bin/bash + +# Путь к виртуальному окружению +VENV_PATH="./venv" +# Путь к основному скрипту +SCRIPT_PATH="./main.py" + +# Запуск скрипта с использованием Python из venv +"$VENV_PATH/bin/python3" "$SCRIPT_PATH" "$@" diff --git a/atxctl/main.py b/atxctl/main.py new file mode 100644 index 0000000..7919f72 --- /dev/null +++ b/atxctl/main.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 + +from pyiArduinoI2Cexpander import * +import os +import argparse +import time + +PROG_NAME = "ATX Control" +PROG_VERSION = 'v' + open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "VERSION")).readline() +PROG_DESCRIPTION = "" +PROG_EPILOG = "" + +EXP_ADDR = 0x08 +POWER_PIN = 0 +RESET_PIN = 1 +STATUS_PIN = 2 + +PRESS_TIME_SEC = 1 +LONG_PRESS_TIME_SEC = 5 + +def exp_init(exp_addr, power_pin, reset_pin, status_pin): + exp = pyiArduinoI2Cexpander(exp_addr) + + try: + exp.pinMode(power_pin, OUTPUT, DIGITAL) + exp.digitalWrite(power_pin, LOW) + + exp.pinMode(reset_pin, OUTPUT, DIGITAL) + exp.digitalWrite(reset_pin, LOW) + + exp.pinMode(status_pin, INPUT, DIGITAL) + exp.pinPull(status_pin, PULL_DOWN) + except Exception as E: + print(f"Error initializing expander hat: {E}") + exit(1) + + return exp + + +def get_status(exp, status_pin): + print("Getting status...") + try: + status_raw = exp.digitalRead(status_pin) + except Exception as E: + print(f"Error reading status: {E}") + exit(1) + + return status_raw + + +def press_power(exp, power_pin): + print("Switching power...") + try: + exp.digitalWrite(power_pin, HIGH) + time.sleep(PRESS_TIME_SEC) + exp.digitalWrite(power_pin, LOW) + except Exception as E: + print(f"Error pressing power: {E}") + exit(1) + + +def press_reset(exp, reset_pin): + print("Resetting...") + try: + exp.digitalWrite(reset_pin, HIGH) + time.sleep(PRESS_TIME_SEC) + exp.digitalWrite(reset_pin, LOW) + except Exception as E: + print(f"Error pressing reset: {E}") + exit(1) + + +def power_off(exp, power_pin): + print("Powering off...") + try: + exp.digitalWrite(power_pin, HIGH) + time.sleep(LONG_PRESS_TIME_SEC) + exp.digitalWrite(power_pin, LOW) + except Exception as E: + print(f"Error powering off: {E}") + exit(1) + + +def main(): + parser = argparse.ArgumentParser(description="ATX Power Control") + parser.add_argument('-s', '--status', action='store_true', help='Get status of the host') + parser.add_argument('-p', '--press-power', action='store_true', help='Press power button') + parser.add_argument('-r', '--press-reset', action='store_true', help='Press reset button') + parser.add_argument('--power-off', action='store_true', help='Force power off (long press power button)') + args = parser.parse_args() + + exp = exp_init(EXP_ADDR, POWER_PIN, RESET_PIN, STATUS_PIN) + + if args.status: + if get_status(exp, STATUS_PIN): + print("Host is powered on") + else: + print("Host is powered off") + elif args.press_power: + press_power(exp, POWER_PIN) + elif args.press_reset: + press_reset(exp, RESET_PIN) + elif args.power_off: + power_off(exp, POWER_PIN) + else: + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/atxctl/requirements.txt b/atxctl/requirements.txt new file mode 100644 index 0000000..cd20632 --- /dev/null +++ b/atxctl/requirements.txt @@ -0,0 +1,2 @@ +# https://wiki.iarduino.ru/page/trema-expander-hat/ +pyiArduinoI2Cexpander==1.0.4 diff --git a/ups/README.md b/ups/README.md new file mode 100644 index 0000000..e69de29 diff --git a/ups/VERSION b/ups/VERSION new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/ups/VERSION @@ -0,0 +1 @@ +1.0.0 diff --git a/ups/main.py b/ups/main.py new file mode 100644 index 0000000..c15e0fa --- /dev/null +++ b/ups/main.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 + +# '''Enable Auto-Shutdown Protection Function ''' +import os +import time +import smbus2 +import logging +import argparse +from ina219 import INA219,DeviceRangeError + + +PROG_NAME = "UPS Control" +PROG_VERSION = 'v' + open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "VERSION")).readline() +PROG_DESCRIPTION = "" +PROG_EPILOG = "" + +# Define I2C bus +DEVICE_BUS = 1 +# Define device i2c slave address. +DEVICE_ADDR = 0x17 +# Set the threshold of UPS automatic power-off to prevent damage caused by battery over-discharge, unit: mV. +PROTECT_VOLT = 3500 + +logger = logging.getLogger("upsctl") +logging.basicConfig(level=logging.INFO) + + +def check_ups_status(): + # Instance INA219 and getting information from it. + ina_supply = INA219(0.00725, busnum=DEVICE_BUS, address=0x40) + ina_supply.configure() + supply_voltage = ina_supply.voltage() + supply_current = ina_supply.current() + supply_power = ina_supply.power() + + logger.debug(" - Current information of the detected Raspberry Pi") + logger.debug("Raspberry Pi Supply Voltage: %.3f V" % supply_voltage) + logger.debug("Raspberry Pi Current Current Consumption: %.3f mA" % supply_current) + logger.debug("Raspberry Pi Current Power Consumption: %.3f mW" % supply_power) + + # Batteries information + ina_batt = INA219(0.005, busnum=DEVICE_BUS, address=0x45) + ina_batt.configure() + batt_voltage = ina_batt.voltage() + batt_current = ina_batt.current() + batt_power = ina_batt.power() + + logger.debug(" - Batteries information") + logger.debug("Voltage of Batteries: %.3f V" % batt_voltage) + try: + if batt_current > 0: + logger.debug("Battery Current (Charging) Rate: %.3f mA"% batt_current) + logger.debug("Current Battery Power Supplement: %.3f mW"% batt_power) + else: + logger.debug("Battery Current (discharge) Rate: %.3f mA"% batt_current) + logger.debug("Current Battery Power Consumption: %.3f mW"% batt_power) + except DeviceRangeError: + logger.error('Battery power is too high.') + + # Raspberry Pi Communicates with MCU via i2c protocol. + bus = smbus2.SMBus(DEVICE_BUS) + + aReceiveBuf = [] + aReceiveBuf.append(0x00) + + # Read register and add the data to the list: aReceiveBuf + for i in range(1, 255): + aReceiveBuf.append(bus.read_byte_data(DEVICE_ADDR, i)) + + # UID0 = "%08X" % (aReceiveBuf[243] << 24 | aReceiveBuf[242] << 16 | aReceiveBuf[241] << 8 | aReceiveBuf[240]) + # UID1 = "%08X" % (aReceiveBuf[247] << 24 | aReceiveBuf[246] << 16 | aReceiveBuf[245] << 8 | aReceiveBuf[244]) + # UID2 = "%08X" % (aReceiveBuf[251] << 24 | aReceiveBuf[250] << 16 | aReceiveBuf[249] << 8 | aReceiveBuf[248]) + # logger.info('UID:' + UID0 + '/' + UID1 + '/' + UID2) + + logger.debug(" - Current state") + if (aReceiveBuf[8] << 8 | aReceiveBuf[7]) > 4000: + logger.debug('Currently charging via Type C Port') + elif (aReceiveBuf[10] << 8 | aReceiveBuf[9]) > 4000: + logger.debug('Currently charging via Micro USB Port') + else: + logger.warning('Currently not charging.') + # Consider shutting down to save data or send notifications + if (str(batt_voltage)) == ("0.0"): + logger.error("Bad battery voltage value") + if (str(batt_voltage)) != ("0.0"): + if ((batt_voltage * 1000) < (PROTECT_VOLT + 200)): + logger.warning('The battery is going to dead! Ready to shut down!') + # It will cut off power when initialized shutdown sequence. + bus.write_byte_data(DEVICE_ADDR, 24, 240) + os.system("sync && halt") + while True: + time.sleep(10) + + +def init_ups(): + logger.info("Setting initial parameters for the UPS") + # Raspberry Pi Communicates with MCU via i2c protocol. + bus = smbus2.SMBus(DEVICE_BUS) + + # Enable Back-to-AC fucntion. + # Enable: write 1 to register 0x19 == 25 + # Disable: write 0 to register 0x19 == 25 + bus.write_byte_data(DEVICE_ADDR, 25, 1) + logger.info("Back-to-AC function enabled") + + # Reset Protect voltage + bus.write_byte_data(DEVICE_ADDR, 17, PROTECT_VOLT & 0xFF) + bus.write_byte_data(DEVICE_ADDR, 18, (PROTECT_VOLT >> 8)& 0xFF) + logger.info(f"Successfully set the protection voltage to: {PROTECT_VOLT} mV") + + +def print_status(): + # Instance INA219 and getting information from it. + ina_supply = INA219(0.00725, busnum=DEVICE_BUS, address=0x40) + ina_supply.configure() + supply_voltage = ina_supply.voltage() + supply_current = ina_supply.current() + supply_power = ina_supply.power() + print("-" * 60) + print("----- Current information of the detected Raspberry Pi -----") + print("-" * 60) + print("Raspberry Pi Supply Voltage: %.3f V" % supply_voltage) + print("Raspberry Pi Current Current Consumption: %.3f mA" % supply_current) + print("Raspberry Pi Current Power Consumption: %.3f mW" % supply_power) + print("-" * 60) + + # Batteries information + ina_batt = INA219(0.005, busnum=DEVICE_BUS, address=0x45) + ina_batt.configure() + batt_voltage = ina_batt.voltage() + batt_current = ina_batt.current() + batt_power = ina_batt.power() + print("------------------ Batteries information ------------------") + print("-" * 60) + print("Voltage of Batteries: %.3f V" % batt_voltage) + try: + if batt_current > 0: + print("Battery Current (Charging) Rate: %.3f mA" % batt_current) + print("Current Battery Power Supplement: %.3f mW" % batt_power) + else: + print("Battery Current (discharge) Rate: %.3f mA" % batt_current) + print("Current Battery Power Consumption: %.3f mW" % batt_power) + print("-" * 60) + except DeviceRangeError: + print("-" * 60) + print('Battery power is too high.') + + # Raspberry Pi Communicates with MCU via i2c protocol. + bus = smbus2.SMBus(DEVICE_BUS) + + aReceiveBuf = [] + aReceiveBuf.append(0x00) + + # Read register and add the data to the list: aReceiveBuf + for i in range(1, 255): + aReceiveBuf.append(bus.read_byte_data(DEVICE_ADDR, i)) + + # UID0 = "%08X" % (aReceiveBuf[243] << 24 | aReceiveBuf[242] << 16 | aReceiveBuf[241] << 8 | aReceiveBuf[240]) + # UID1 = "%08X" % (aReceiveBuf[247] << 24 | aReceiveBuf[246] << 16 | aReceiveBuf[245] << 8 | aReceiveBuf[244]) + # UID2 = "%08X" % (aReceiveBuf[251] << 24 | aReceiveBuf[250] << 16 | aReceiveBuf[249] << 8 | aReceiveBuf[248]) + # print('UID:' + UID0 + '/' + UID1 + '/' + UID2) + + if (aReceiveBuf[8] << 8 | aReceiveBuf[7]) > 4000: + print('-' * 60) + print('Currently charging via Type C Port.') + elif (aReceiveBuf[10] << 8 | aReceiveBuf[9]) > 4000: + print('-' * 60) + print('Currently charging via Micro USB Port.') + else: + print('-' * 60) + print('Currently not charging.') + if (str(batt_voltage)) == ("0.0"): + print("Bad battery voltage value") + if (str(batt_voltage)) != ("0.0"): + if ((batt_voltage * 1000) < (PROTECT_VOLT + 200)): + print('-' * 60) + print('The battery is going to dead!') + + +def main(): + arg_parse = argparse.ArgumentParser(description='UPS Control Script') + arg_parse.add_argument('-s', '--status', action='store_true', help='Print the status of the UPS') + arg_parse.add_argument('-i', '--init', action='store_true', help='Set initial parameters for the UPS') + arg_parse.add_argument('-c', '--check', action='store_true', help='Check the status of the UPS') + args = arg_parse.parse_args() + + if args.check: + check_ups_status() + elif args.init: + init_ups() + elif args.status: + print_status() + else: + arg_parse.print_help() + + +if __name__ == "__main__": + main() diff --git a/ups/requirements.txt b/ups/requirements.txt new file mode 100644 index 0000000..90cf53b --- /dev/null +++ b/ups/requirements.txt @@ -0,0 +1,3 @@ +smbus2==0.5.0 +pi-ina219==1.4.1 + diff --git a/ups/systemd/ups-check.service b/ups/systemd/ups-check.service new file mode 100644 index 0000000..7726317 --- /dev/null +++ b/ups/systemd/ups-check.service @@ -0,0 +1,9 @@ +[Unit] +Description=Check UPS status +After=init-ups.service + +[Service] +Type=oneshot +ExecStart=/opt/arm/ups/venv/bin/python3 /opt/arm/ups/main.py --check +User=root +Group=root diff --git a/ups/systemd/ups-check.timer b/ups/systemd/ups-check.timer new file mode 100644 index 0000000..e0bb396 --- /dev/null +++ b/ups/systemd/ups-check.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Timer for periodic UPS status check +After=check-ups.service + +[Timer] +OnCalendar=*-*-* *:*:00,30 +Persistent=false +AccuracySec=1s + +[Install] +WantedBy=timers.target diff --git a/ups/systemd/ups-init.service b/ups/systemd/ups-init.service new file mode 100644 index 0000000..cd81ea5 --- /dev/null +++ b/ups/systemd/ups-init.service @@ -0,0 +1,15 @@ +[Unit] +Description=Initialize UPS settings on boot +After=multi-user.target + +[Service] +Type=oneshot +ExecStart=/opt/arm/ups/venv/bin/python3 /opt/arm/ups/main.py --init +User=root +Group=root +RemainAfterExit=yes +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/ups/ups-init b/ups/ups-init new file mode 100755 index 0000000..dfe05b3 --- /dev/null +++ b/ups/ups-init @@ -0,0 +1,9 @@ +#!/bin/bash + +# Путь к виртуальному окружению +VENV_PATH="./venv" +# Путь к основному скрипту +SCRIPT_PATH="./main.py" + +# Запуск скрипта с использованием Python из venv +"$VENV_PATH/bin/python3" "$SCRIPT_PATH" --init diff --git a/ups/ups-status b/ups/ups-status new file mode 100755 index 0000000..bcaf157 --- /dev/null +++ b/ups/ups-status @@ -0,0 +1,9 @@ +#!/bin/bash + +# Путь к виртуальному окружению +VENV_PATH="./venv" +# Путь к основному скрипту +SCRIPT_PATH="./main.py" + +# Запуск скрипта с использованием Python из venv +"$VENV_PATH/bin/python3" "$SCRIPT_PATH" --status diff --git a/ups/upsctl b/ups/upsctl new file mode 100755 index 0000000..1827a48 --- /dev/null +++ b/ups/upsctl @@ -0,0 +1,9 @@ +#!/bin/bash + +# Путь к виртуальному окружению +VENV_PATH="./venv" +# Путь к основному скрипту +SCRIPT_PATH="./main.py" + +# Запуск скрипта с использованием Python из venv +"$VENV_PATH/bin/python3" "$SCRIPT_PATH" "$@"