46045-syslab/battery_local_control.py

111 lines
4.3 KiB
Python

from util import pos, clamp, soc_scaler, cast_to_cm
import parameters
from time import time, sleep
import zmq
import logging
import syslab
logging.basicConfig(level=logging.INFO)
# Controller Parameters
Kp = 0.4 # P factor for our controller.
Ki = 0.8 # I factor for our controller.
### Variables
# Target that we are trying to reach at the grid connection.
pcc_target = 0.0
# Controller variables
x_battery = 1 # Default splitting factor
# Info on battery state
battery_setpoint = 0.0 # Default setpoint
battery_soc = 0.5 # Default SOC (= state of charge)
### Communication
# Make a context that we use to set up sockets
context = zmq.Context()
# Set up a socket we can use to publish our soc on
soc_out_socket = context.socket(zmq.PUB)
soc_out_socket.bind(f"tcp://*:{parameters.BATTERY_SOC_PORT}")
# Set up a socket to subscribe to our splitting factor
splitting_in_socket = context.socket(zmq.SUB)
splitting_in_socket.connect(f"tcp://{parameters.SUPERVISOR_IP}:{parameters.SUPERVISOR_PORT}")
# Ensure we only see message on the battery's splitting factor
splitting_in_socket.subscribe(parameters.TOPIC_BATTERY_SPLITTING)
### Unit connections
# TODO step 1.2: Set up connection to control the battery and reconstruct the pcc (remember that vswitchboard is still not working)
gaia = syslab.WindTurbine("vgaia1")
dumpload = syslab.Dumpload("vload1")
mobload1 = syslab.Dumpload("vmobload1")
pv319 = syslab.Photovoltaics("vpv319")
batt = syslab.Battery('vbatt1')
### Import your controller class
# TODO step 1.2: Import the controller class from "simlab_controller_d5_batt.py" or copy/paste it here and pick reasonable controller parameters
# Note: The controller is identical to Day 5 with the exception of incorporating the splitting factor
from simlab_controller_d5_batt import PIDController
pid = PIDController(Kp=Kp, Ki=Ki, Kd=0.0,
u_min=parameters.MIN_BATTERY_P,
u_max=parameters.MAX_BATTERY_P,
Iterm=0.0)
# Put everything in a "try" block so clean-up is easy
try:
while True:
try:
# Try to connect to the supervisor.
# If we have a connection to the supervisor, get our requested splitting factor.
# If none have come in, continue with previous splitting factor.
# We put this in a while-loop to ensure we empty the queue each time,
# so we always have the latest value.
while True:
# Receive the latest splitting factor
incoming_str = splitting_in_socket.recv_string(flags=zmq.NOBLOCK)
# The incoming string will look like "batt_split;0.781",
# so we split it up and take the last bit forward.
x_battery = float(incoming_str.split(" ")[-1])
logging.info(f"New splitting factor: {x_battery}")
except zmq.Again as e:
# No (more) new messages, move along
pass
# Poll the grid connection to get the current grid exchange.
pcc_p = cast_to_cm(-(
gaia.getActivePower().value
+ batt.getActivePower().value
+ pv319.getACActivePower().value
- dumpload.getActivePower().value
- mobload1.getActivePower().value
))
# Check our own state of charge
battery_soc = batt.getSOC() # TODO step 1.2: Read the true battery SOC
# Calculate new requests using PID controller
battery_setpoint = pid.update(pcc_p.value, x_batt=x_battery)
# Ensure we don't exceed our bounds for the battery
battery_setpoint = clamp(parameters.MIN_BATTERY_P, battery_setpoint, parameters.MAX_BATTERY_P)
# Send the new setpoint to the battery
# TODO step 1.2: Send the new setpoint to the battery
batt.setActivePower(battery_setpoint)
logging.info(f"Sent setpoint: {battery_setpoint}")
# Publish our current state of charge for the supervisory controller to see
soc_out_socket.send_string(f"{parameters.TOPIC_BATTERY_SOC} {battery_soc:.06f}")
# Loop once more in a second
sleep(1)
finally:
# Clean up by closing our sockets.
# TODO step 1.2: Set the setpoint of the battery to zero after use
batt.setActivePower(0.)
splitting_in_socket.close()
soc_out_socket.close()