Well friends, perhaps I’m just dumb, but it took me a good long time to get the UPS to work for me. I’ll give you the short version and the long version
Acknowledgement: A big thanks to @shawnj007 for helping me get an understanding of what is going on. He went out into the cold cold night to pull his rPi out of his car setup to get his code back out.
Equipment: I’m using a RaspberryPi 4 and a Sixfab UPS v2. I’m not using solar at this time, but will begin testing that soon.
Short Version:
Create a systemd service to run a python script that controls the Sixfab API on startup, and that script must reset to factory settings and set the RTC settings. Within that same script, you can then create the scheduled events or power outage event from the API. Make sure no other scripts access the SixFab API at the same time.
Long Version:
I am setting up an off-grid, solar-powered sensor array and I need my RaspberryPi to log data from all of the sensors and send me reports via email automatically. I will not have physical access to the computer. This is where the Sixfab UPS comes into play - it will accept solar power to charge the computer and keep the power regulated.
Problems I ran into, at least in my experience:
- API must be reset to default factory settings after restarting from a power outage event or it will glitch
- API cannot accept two different python scripts talking to it at the same time or it will glitch and you will have to remove the battery after shutting down to clear the UPS’ memory
- Since I need reports using data from the API, I had to combine my API power setting script, my computer startup email script and my sensor datalogging script into a single script
First, we need to make a systemd service:
In my case I named the service “custompi.service” and it will run the python file “startup_mailer.py”
Notice the ExecStartPre= line. This service will only run if an internet connection is established.
Make sure after you save the service file after creating/editing that you give the proper chmod permissions to the service and python script.
Also make sure you do a daemon-reload, then enable the service then start the service in terminal.
Now to the fun part, the python script:
Read the comments after the #s to understand each section of code
This script:
- Sends an email report to me when the computer starts up and at noon every day
- Creates a power outage event on the UPS if the battery level is <= 30 and is not charging
- Logs battery level, input_power level, UPS working mode, soil sensor humidity, soil sensor temperature
#Import the SixFab Power Api
from power_api import SixfabPower, Definition, Event
#Import BeautifulSoup to read Sensor Data from an ESP8266 microcontroller (has nothing to do with SixFab)
from bs4 import BeautifulSoup
import requests
#Import datetime and time for sleep and timestamps and RTC functions
import datetime
import time
#Import libraries for sending an email (has nothing to do with SixFab)
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
#Key to Making Script Work: Reset the UPS
#Do Not Run SixFabPower API in any other script at the same time, especially if you have loops like I do
api = SixfabPower()
api.restore_factory_defaults()
time.sleep(2)
#Set Power Outage Event Parameters to 3 minutes of wake time, 8 hours of sleep time
api.set_power_outage_params(480,3)
time.sleep(2)
#Set RTC every time you reset SixFab
epoch = time.time() # to get timestamp in seconds in GMT0
localtime = time.asctime( time.localtime(time.time()) )
print("Local current time :", localtime)
print(time.timezone)
print("Epoch Time: " + str(int(epoch)))
print("Epoch Local: " + str(int(epoch) - time.timezone))
print("Result: " + str(api.set_rtc_time(int(epoch) - time.timezone)))
#Find values for Startup Email
battery = api.get_battery_level()
mode = api.get_working_mode()
input_power = api.get_input_power()
battstr = str(battery)
modestr = str(mode)
inputstr = str(input_power)
time_s = str(datetime.datetime.now())
time_d = time_s[0:10]
time_t = time_s[11:19]
#Check Sensors
try:
requests.get('http://192.168.0.14/humid', timeout=10)
s1status = "Online"
except Exception:
s1status = "Offline"
print("Battery Level: "+str(battery)+"\n\nSensor1 Status: "+s1status)
#Prepare Dashboard (This is to allow time for an automatic PDF Dashboard update that is not described in this script)
time.sleep(90)
#Send Startup Email
email_user = "xxxx@gmail.com"
email_password = "xxxxxxxx"
email_send = "xxxx@gmail.com"
subject = "Central computer on"
msg = MIMEMultipart()
msg["From"] = "Farm rPi"
msg["To"] = email_send
msg["Subject"] = subject
body = "Battery Level: "+str(battery)+"\n\nSensor1 Status: "+s1status
msg.attach(MIMEText(body,"plain"))
file = '/home/pi/Desktop/Dashboard.pdf'
attachment2 = MIMEBase('application', 'octet-stream')
attachment2.set_payload(open(file,'rb').read())
encoders.encode_base64(attachment2)
attachment2.add_header('Content-Disposition', 'attachment; filename="Dashboard.pdf"')
msg.attach(attachment2)
file = '/home/pi/Code/DataLog.txt'
attachment = MIMEBase('application', 'octet-stream')
attachment.set_payload(open(file,'rb').read())
encoders.encode_base64(attachment)
attachment.add_header('Content-Disposition', 'attachment; filename="DataLog.txt"')
msg.attach(attachment)
#Anything that involves an internet connection should have try/except error handling. Internet connections can break!
try:
text = msg.as_string()
server = smtplib.SMTP("smtp.gmail.com",587)
server.starttls()
server.login(email_user,email_password)
server.sendmail(email_user,email_send,text)
server.quit()
print("Email Sent")
except Exception:
print("Email Not Sent")
#Write Timestamp to Agriculture Sensor DataLog
if battery >= 1:
print("Will Write Startup Timestamp")
csvline = ("\nSTARTUP;"+time_d+";"+time_t+";"+";;"+battstr+";"+modestr+";"+inputstr)
print(csvline)
f = open ("/home/pi/Code/DataLog.txt","a")
f.write (csvline)
f.close()
else:
print("Will Not Write Timestamp")
#Begin main loop to write sensor data every 60 seconds
i = 1
while i == 1:
print("Start Loop")
#Query API for current levels
battery = api.get_battery_level()
mode = api.get_working_mode()
input_power = api.get_input_power()
battstr = str(battery)
modestr = str(mode)
inputstr = str(input_power)
print("Battery Level: " +battstr)
print("Working Mode: " +modestr)
print("Input Power: " +inputstr)
#Create power off event to power off the device in 15 seconds if battery level is 30 or less and the battery is not charging
if battery <= 30 and mode == 3:
api.set_power_outage_event_status(1)
print("Beginning Shutdown Event in 2 Minutes if Not Powered")
#Write a timestamp to the Agriculture Sensor Datalog
csvline = ("\nSHUTDOWN;"+time_d+";"+time_t+";"+";;"+battstr+";"+modestr+";"+inputstr)
print(csvline)
f = open ("/home/pi/Code/DataLog.txt","a")
f.write (csvline)
f.close()
time.sleep(30)
#Exit loop so you don't accidentally write to a file during a shutdown
exit()
else:
print("No Shutdown Event")
#If power level is acceptable, proceed to with 60 second loop to write to Agriculture Sensor DataLog
#Try/Except is due to requests needing an internet connection, which is not always available
try:
#Try to get soil sensor readings from ESP8266
pageh = requests.get('http://192.168.0.14/humid', timeout=10)
souph = BeautifulSoup(pageh.text,'html.parser')
paget = requests.get('http://192.168.0.14/temp', timeout=10)
soupt = BeautifulSoup(paget.text,'html.parser')
h = str(souph)
t = str(soupt)
time_s = str(datetime.datetime.now())
time_d = time_s[0:10]
time_t = time_s[11:19]
#Format then write soil sensor readings to agriculture sensor datalog
csvline = ("\nS1;"+time_d+";"+time_t+";"+h+";"+t+";"+str(battery)+";"+str(mode)+";"+str(input_power))
print(csvline)
f = open ("/home/pi/Code/DataLog.txt","a")
f.write (csvline)
f.close()
except Exception:
#If unable to connect to the sensor, write blanks/nulls in sensor readings to log
print("Unable to Connect to Sensor")
csvline = ("\nS1;"+time_d+";"+time_t+";;;"+str(battery)+";"+str(mode)+";"+str(input_power))
print(csvline)
f = open ("/home/pi/Code/DataLog.txt","a")
f.write (csvline)
f.close()
#Noon Email Report will only send within 1 minute of noon everyday
time_s = str(datetime.datetime.now())
time_hr = int(time_s[11:13])
time_min = int(time_s[14:16])
time_sec = int(time_s[17:19])
time_1 = datetime.timedelta(hours= time_hr , minutes= time_min, seconds= time_sec)
time_2 = datetime.timedelta(hours= 12, minutes=00, seconds=00)
time_delta_string = (time_2 - time_1)
email_timer = time_delta_string.seconds
print("Email Seconds Timer: "+str(email_timer))
if (email_timer <= 85):
#Send Email
email_user = "xxxx@gmail.com"
email_password = "xxxxxxxx"
email_send = "xxxx@gmail.com"
subject = "Noon Report"
msg = MIMEMultipart()
msg["From"] = "Farm rPi"
msg["To"] = email_send
msg["Subject"] = subject
body = "Battery Level: "+str(battery)+"\n\nSensor1 Status: "+s1status
msg.attach(MIMEText(body,"plain"))
file = '/home/pi/Desktop/Dashboard.pdf'
attachment2 = MIMEBase('application', 'octet-stream')
attachment2.set_payload(open(file,'rb').read())
encoders.encode_base64(attachment2)
attachment2.add_header('Content-Disposition', 'attachment; filename="Dashboard.pdf"')
msg.attach(attachment2)
file = '/home/pi/Code/DataLog.txt'
attachment = MIMEBase('application', 'octet-stream')
attachment.set_payload(open(file,'rb').read())
encoders.encode_base64(attachment)
attachment.add_header('Content-Disposition', 'attachment; filename="DataLog.txt"')
msg.attach(attachment)
#Always use Try/Except with anything demanding an internet connection
try:
text = msg.as_string()
server = smtplib.SMTP("smtp.gmail.com",587)
server.starttls()
server.login(email_user,email_password)
server.sendmail(email_user,email_send,text)
server.quit()
print("Noon Email Sent")
except Exception:
print("Noon Email Not Sent")
else:
print("Noon Email Not Sent")
#Loop should only run every 60 seconds
time.sleep(60)
print("--")
print("--")
Hope this helps. I will put out an update that creates its own battery% level from voltage readings in the future rather than using battery level from the API that I think is less accurate since it is based on amps.