Last active
June 19, 2024 19:11
-
-
Save mmoollllee/bf56637080911c89e0a03af72ab6f5c6 to your computer and use it in GitHub Desktop.
Some python functions to control the [LiFePO4wered/Pi+](https://github.com/xorbit/LiFePO4wered-Pi)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# LiFePO4wered access Python module | |
# Copyright (c) 2017 Patrick Van Oosterwijck | |
from ctypes import cdll | |
import humanized_opening_hours as hoh | |
from datetime import datetime, timedelta | |
import time, multiprocessing | |
import os.path | |
from typing import Union | |
# Variable definitions | |
I2C_REG_VER = 0 | |
I2C_ADDRESS = 1 | |
LED_STATE = 2 | |
TOUCH_STATE = 3 | |
TOUCH_CAP_CYCLES = 4 | |
TOUCH_THRESHOLD = 5 | |
TOUCH_HYSTERESIS = 6 | |
DCO_RSEL = 7 | |
DCO_DCOMOD = 8 | |
VIN = 9 | |
VBAT = 10 | |
VOUT = 11 | |
IOUT = 12 | |
VBAT_MIN = 13 | |
VBAT_SHDN = 14 | |
VBAT_BOOT = 15 | |
VOUT_MAX = 16 | |
VIN_THRESHOLD = 17 | |
IOUT_SHDN_THRESHOLD = 18 | |
VOFFSET_ADC = 19 | |
VBAT_OFFSET = 20 | |
VOUT_OFFSET = 21 | |
VIN_OFFSET = 22 | |
IOUT_OFFSET = 23 | |
AUTO_BOOT = 24 | |
WAKE_TIME = 25 | |
SHDN_DELAY = 26 | |
AUTO_SHDN_TIME = 27 | |
PI_BOOT_TO = 28 | |
PI_SHDN_TO = 29 | |
RTC_TIME = 30 | |
RTC_WAKE_TIME = 31 | |
WATCHDOG_CFG = 32 | |
WATCHDOG_GRACE = 33 | |
WATCHDOG_TIMER = 34 | |
PI_RUNNING = 35 | |
CFG_WRITE = 36 | |
# Touch states and masks | |
TOUCH_INACTIVE = 0x00 | |
TOUCH_START = 0x03 | |
TOUCH_STOP = 0x0C | |
TOUCH_HELD = 0x0F | |
TOUCH_ACTIVE_MASK = 0x03 | |
TOUCH_MASK = 0x0F | |
# LED states when Pi on | |
LED_STATE_OFF = 0x00 | |
LED_STATE_ON = 0x01 | |
LED_STATE_PULSING = 0x02 | |
LED_STATE_FLASHING = 0x03 | |
# Auto boot settings | |
AUTO_BOOT_OFF = 0x00 | |
AUTO_BOOT_VBAT = 0x01 | |
AUTO_BOOT_VBAT_SMART = 0x02 | |
AUTO_BOOT_VIN = 0x03 | |
AUTO_BOOT_VIN_SMART = 0x04 | |
AUTO_BOOT_NO_VIN = 0x05 | |
AUTO_BOOT_NO_VIN_SMART = 0x06 | |
AUTO_BOOT_VIN_SMART2 = 0x07 | |
# Watchdog settings | |
WATCHDOG_OFF = 0x00 | |
WATCHDOG_ALERT = 0x01 | |
WATCHDOG_SHDN = 0x02 | |
# Register access masks | |
ACCESS_READ = 0x01 | |
ACCESS_WRITE = 0x02 | |
# Load shared object and check if it's installed | |
lib = False | |
import os.path | |
if os.path.isfile('/usr/local/lib/liblifepo4wered.so'): | |
lib = cdll.LoadLibrary('/usr/local/lib/liblifepo4wered.so') | |
# Determine if the specified variable can be accessed in the specified | |
# manner (read, write or both) | |
def access_lifepo4wered(var, access_mask): | |
if lib: | |
return lib.access_lifepo4wered(var, access_mask) | |
else: | |
return False | |
# Read data from LiFePO4wered device | |
def read_lifepo4wered(var): | |
if lib: | |
return lib.read_lifepo4wered(var) | |
else: | |
return False | |
# Write data to LiFePO4wered device | |
def write_lifepo4wered(var, value): | |
if lib: | |
return lib.write_lifepo4wered(var, value) | |
else: | |
return False | |
def is_on(schedule: str) -> bool: | |
""" Parse the schedule string (e.g. 9:00-18:00) and check if we're currently within the 'openinghours'. | |
Returns True if so, False if not. | |
""" | |
oh = hoh.OHParser(schedule) | |
return oh.is_open() | |
def lifepo4wered_setup(): | |
""" Make sure to set inital lifepo4wered settings for usage with a 25Ah 3,2V LiFePO4 battery in a solar powered system meant to be shut down from during the night to save power. | |
Returns True on success, False on failure | |
""" | |
if lib: | |
try: | |
write_lifepo4wered(AUTO_BOOT, AUTO_BOOT_VBAT) | |
write_lifepo4wered(VBAT_MIN, 2850) | |
write_lifepo4wered(VBAT_SHDN, 2900) | |
write_lifepo4wered(VBAT_BOOT, 3150) | |
write_lifepo4wered(PI_SHDN_TO, 90) # Cut Power after n sec | |
write_lifepo4wered(WATCHDOG_GRACE, 600) # Initial Watchdog Timer on boot | |
write_lifepo4wered(WATCHDOG_CFG, 2) # Make Watchdog shutdown the system | |
return True | |
except: | |
pass | |
return False | |
def lifepo4wered_save_reboot(): | |
""" Shutdown the system and cut RPi's power and make sure its definitely going to restart as long as battery power is not tooo low. | |
Returns True if reboot is happening, False if not | |
""" | |
if lib: | |
try: | |
write_lifepo4wered(AUTO_BOOT, AUTO_BOOT_VBAT) | |
write_lifepo4wered(VBAT_BOOT, 3050) | |
os.system("sudo shutdown now") | |
return True | |
except: | |
pass | |
return False | |
def lifepo4wered_save_shutdown(schedule: Union[str, int, bool] = 5): | |
""" Shutdown the System to stay off! But make sure theres a schedule set before. | |
Returns True if Shutdown is save and is going to happen, else False | |
Arguments: | |
schedule: Pass a string of the schedule (e.g. "8:00-19:00") or a integer as minutes from now. Boolean is allowed but does nothing. | |
""" | |
if lib: | |
try: | |
if not schedule: | |
# Don't do anything | |
return False | |
if isinstance(schedule, int) and schedule < 3: | |
# Make sure minutes from now is far enough in the future for the system to shutdown completly if its a integer | |
schedule = 3 | |
write_lifepo4wered(AUTO_BOOT, AUTO_BOOT_VIN_SMART2) | |
write_lifepo4wered(VBAT_BOOT, 3050) | |
schedule_next_start(schedule) | |
if next_start_scheduled(): | |
# Recheck if WAKE_TIME or RTC_WAKE_TIME is set to something in the future. | |
time.sleep(1) | |
os.system("sudo shutdown now") | |
return True | |
except: | |
pass | |
return False | |
def schedule_next_start(schedule: Union[str, int, bool] = 5): | |
""" Schedule or clear LiFePO4wered's next startup. Writes the time to startup from now in minutes to the hardware. | |
Returns True on success, False on failure | |
Arguments: | |
schedule: Pass a string of the schedule (e.g. "8:00-19:00") or a integer as minutes from now. Boolean is allowed but does nothing. | |
""" | |
if lib: | |
try: | |
if isinstance(schedule, int): | |
# if integer is provided, startup in x minutes | |
diff = schedule | |
write_lifepo4wered(WAKE_TIME, diff) | |
write_lifepo4wered(RTC_WAKE_TIME, 0) | |
elif isinstance(schedule, str): | |
# if string is provided parse it and check for next start time | |
oh = hoh.OHParser(schedule) | |
next = oh.next_change().replace(tzinfo=None) + timedelta(seconds=10) | |
if not oh.is_open(next): | |
next = oh.next_change(next).replace(tzinfo=None) | |
diff = round(next.timestamp()) | |
write_lifepo4wered(RTC_WAKE_TIME, diff) | |
write_lifepo4wered(WAKE_TIME, 0) | |
elif schedule == False: | |
write_lifepo4wered(RTC_WAKE_TIME, 0) | |
write_lifepo4wered(WAKE_TIME, 0) | |
return True | |
except: | |
pass | |
return False | |
def next_start_scheduled(): | |
""" Check if next Wake Time is set and is >3min in the future. | |
Returns True if so, False if not | |
""" | |
if lib: | |
wake_time = read_lifepo4wered(WAKE_TIME) | |
if wake_time > 3: | |
return True | |
rtc_wake_time = read_lifepo4wered(RTC_WAKE_TIME) | |
rtc_wake_time = (rtc_wake_time - datetime.now().timestamp())/60 | |
return rtc_wake_time > 3 | |
return False | |
def LED_off(): | |
""" Turn off LiFePO4wered LED. """ | |
if lib: | |
LED_stop() | |
write_lifepo4wered(LED_STATE, LED_STATE_OFF) | |
def LED_stop(): | |
""" Stop running LED_control process' """ | |
if lib: | |
for p in multiprocessing.active_children(): | |
if p.name == "LED": | |
p.terminate() | |
def LED_control(duration: float = 1, state = LED_STATE_ON, count: int = 1): | |
""" Change state of LED | |
Arguments: | |
duration: How long to set the 'state' in every cycle 'count' | |
state: The state you want, standard is LED_STATE_ON (1) | |
count: how often to repeat | |
""" | |
# TODO: Save current LED State, which should be read_lifepo4wered(LED_STATE) but might inference if LED_control is triggered while another is running. | |
LED_cur = LED_STATE_OFF | |
if LED_cur == state: | |
# If LED State is On or Off, toggle | |
if state == LED_STATE_ON: | |
state = LED_STATE_OFF | |
else: | |
state = LED_STATE_ON | |
if duration: | |
for x in range(count): | |
write_lifepo4wered(LED_STATE, state) | |
time.sleep(duration) | |
write_lifepo4wered(LED_STATE, LED_cur) | |
time.sleep(duration) | |
else: | |
write_lifepo4wered(LED_STATE, state) | |
def LED_on(duration: float = 3): | |
""" Turn on LiFePO4wered LED. | |
Arguments: | |
duration: For how long (in seconds) | |
""" | |
if lib: | |
LED_stop() | |
multiprocessing.Process(target=LED_control, name="LED", args=(duration, LED_STATE_PULSING)).start() | |
def LED_pulse(duration: float = 3): | |
""" Pulse LiFePO4wered LED for the given duration. | |
Arguments: | |
duration: For how long (in seconds) | |
""" | |
if lib: | |
LED_stop() | |
multiprocessing.Process(target=LED_control, name="LED", args=(duration, LED_STATE_PULSING)).start() | |
def LED_flash(duration: float = 3): | |
""" Flash LiFePO4wered LED for the given duration. | |
Arguments: | |
duration: For how long (in seconds) | |
""" | |
if lib: | |
LED_stop() | |
multiprocessing.Process(target=LED_control, name="LED", args=(duration, LED_STATE_FLASHING)).start() | |
def LED_blink(duration: float = 1, count: int = 3): | |
""" Pulse LiFePO4wered LED for the given duration. | |
Arguments: | |
duration: For how long to pulse the LED (in seconds) | |
""" | |
if lib: | |
LED_stop() | |
multiprocessing.Process(target=LED_control, name="LED", args=(duration/2, LED_STATE_ON, count)).start() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment