Skip to content

Instantly share code, notes, and snippets.

@mmoollllee
Last active June 19, 2024 19:11
Show Gist options
  • Save mmoollllee/bf56637080911c89e0a03af72ab6f5c6 to your computer and use it in GitHub Desktop.
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)
# 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