Пример #1
0
class Publisher:
    def __init__(self, config_settings):

        # Obtains config file settings and starts Subscribers
        self.settings = config_settings

        # Initial Parameters
        self.bat_num = 0
        self.grid_num = 0
        self.bat_power = 0
        self.grid = 0
        self.sol_grid = 0
        self.initial_time = 0
        self.day_count = 0
        self.savings = 0
        self.sol_savings = 0
        self.house_import = 0

        # Non-Optimiser Control Settings
        self.grid_ref = self.settings.simulation["grid_ref"]
        self.grid_control_dir = self.settings.simulation["control_dir"]

        # Sets up Kalman Filters
        self.bat_cov = self.settings.control["bat_cov"]
        self.battery_filter = KalmanFilter(1, 0, 1, 0, 1, self.bat_cov, 1)

        # Creates Internal Data Store
        self.data_store = dict()
        self.data_store["bat_power"] = list()
        self.data_store["bat_plot"] = list()
        self.data_store["bat_time"] = list()
        self.data_store["grid_power"] = list()
        self.data_store["grid_plot"] = list()
        self.data_store["grid_time"] = list()

        # ZeroMQ Publishing
        pub_context = zmq.Context()
        self.pub_socket = pub_context.socket(zmq.PUB)
        self.pub_socket.bind("tcp://*:%s" % str(self.settings.ZeroMQ["battery_power_port"]))

    def set_power(self, bat_power):
        self.bat_power = bat_power

    def set_grid(self, solar, house):
        self.grid = self.bat_power + solar + house
        self.sol_grid = solar + house

    def update_data_store(self, device, power, house_time):

        # Power Value
        self.data_store[device + "_power"].append(power / 1000)

        # Time Value
        if self.settings.simulation["use_real_time"]:
            curr_time = (round(time.time() - self.initial_time, 2) / 3600) - (24 * self.day_count)
        else:
            curr_time = house_time
        self.data_store[device + "_time"].append(curr_time)

        # Plot Value
        if 24 - self.data_store[device + "_time"][-1] < 0.1:
            self.data_store[device + "_plot"].append(np.nan)
        else:
            self.data_store[device + "_plot"].append(power / 1000)

        # Increase total counters
        if device == "bat":
            self.bat_num += 1
        else:
            self.grid_num += 1

    def non_optimiser_control(self, soc):

        # Above, Below and Both Control
        above_control = self.grid_control_dir == "Above" and self.grid > self.grid_ref
        below_control = self.grid_control_dir == "Below" and self.grid < self.grid_ref

        # Sets new Battery Power
        if above_control or below_control or self.grid_control_dir == "Both":
            self.set_power(-self.grid + self.bat_power + self.grid_ref)

        # Accounting for SOC
        if (soc == 0 and self.bat_power < 0) or (soc == 100 and self.bat_power > 0):
            self.set_power(0)

        # Battery Power Filtering
        if self.settings.control["battery_filtering"]:
            self.battery_filter.step(0, self.bat_power)
            self.set_power(self.battery_filter.current_state())

    def publish_power(self):
        self.pub_socket.send_string("%d %d" % (self.settings.ZeroMQ["battery_power_topic"], self.bat_power))
Пример #2
0
class ControlSystem:
    def __init__(self, config_settings):

        # Initialises Classes
        self.settings = config_settings
        self.sub = Subscriber(config_settings)
        self.pub = Publisher(config_settings)
        self.optimiser = Optimiser(config_settings)
        if self.settings.simulation["use_visualisation"]:
            self.plot = DataVisualisation(config_settings)

        # Sets Boolean Parameters
        self.control_mod = False
        self.opt_mod = False
        self.data_mod = False
        self.initial_connect = False
        self.connected = False

        # Total Energy
        self.solar_energy = 0
        self.house_energy = 0

        # Sets internal parameters
        self.mod_thresh = 0.002
        self.optimiser_index = 0
        self.data_skip = 0
        self.covariance = 0.3
        self.power_cov = 0.5
        self.power_scale = self.settings.control["power_covariance"]
        self.prev_house_data = None
        self.prev_house_control = None
        self.prev_house_opt = None
        self.prev_house_day = None
        self.time_step = self.settings.control["data_time_step"]
        self.control_step = self.settings.control["control_time_step"]
        self.opt_step = self.settings.control["optimiser_time_step"]

        # Creates 24 hour data stores and filters
        self.power = None
        self.load = list(self.optimiser.load)
        self.pv = list(self.optimiser.pv)
        self.import_tariff = list(self.optimiser.import_tariff.values())
        self.export_tariff = list(self.optimiser.export_tariff.values())

        self.load_filter = None
        self.pv_filter = None
        self.power_filter = KalmanFilter(1, 0, 1, 0, 1, self.power_cov, 1)

    def current_time(self):

        # Obtains the current time
        if self.settings.simulation["use_real_time"]:
            curr_time = (round(time.time() - self.sub.initial_time, 2) /
                         3600) - (24 * self.sub.day_count)
        elif bool(self.sub.data_store["house_time"]) is False:
            curr_time = 0
        else:
            curr_time = self.sub.data_store["house_time"][-1]
        return curr_time

    def update_day_counter(self):

        # Obtains the current time
        curr_time = self.current_time()

        # Obtains current house time
        if bool(self.sub.data_store["house_time"]) is False:
            curr_house_time = 0
        else:
            curr_house_time = self.sub.data_store["house_time"][-1]

        if 1 < curr_time < 23:
            self.prev_house_day = None

        # Checks if it has been 24 hours
        if abs(curr_time -
               24) < 0.1 and curr_house_time != self.prev_house_day:
            self.sub.day_count += 1
            self.pub.day_count += 1
            if self.settings.simulation["use_visualisation"]:
                self.plot.day_count += 1
            self.prev_house_day = curr_house_time

    def calculate_savings(self):

        # Energy Totals
        self.solar_energy += (self.sub.solar_power / 1000) * (self.time_step /
                                                              60)
        self.house_energy += (self.sub.house_power / 1000) * (self.time_step /
                                                              60)

        # Total Savings
        if self.pub.grid < 0:
            self.pub.savings += (self.pub.grid / 1000) * (
                self.time_step / 60) * self.export_tariff[0]
        else:
            self.pub.savings += (self.pub.grid / 1000) * (
                self.time_step / 60) * self.import_tariff[0]

        # Battery Exclusive Savings
        if self.pub.sol_grid < 0:
            self.pub.sol_savings += (self.pub.sol_grid / 1000) * (
                self.time_step / 60) * self.export_tariff[0]
        else:
            self.pub.sol_savings += (self.pub.sol_grid / 1000) * (
                self.time_step / 60) * self.import_tariff[0]

        # No PV System Cost
        self.pub.house_import += (self.sub.house_power / 1000) * (
            self.time_step / 60) * self.import_tariff[0]

    def update_24_data(self, data_skip):

        # Applies filters and removes last entries (solar and load)
        if len(self.load) == (60 / self.time_step) * 24:

            for i in reversed(range(0, data_skip + 1)):
                # Create Filters
                self.load_filter = KalmanFilter(1, 0, 1, self.load[0], 1,
                                                self.covariance, 1)
                self.pv_filter = KalmanFilter(1, 0, 1, self.pv[0], 1,
                                              self.covariance, 1)

                # Apply Filters
                self.load_filter.step(
                    0, self.sub.data_store["house_value"][-(i + 1)] /
                    (60 / self.time_step))
                self.pv_filter.step(
                    0, self.sub.data_store["solar_value"][-(i + 1)] /
                    (60 / self.time_step))

                # Remove First Value
                self.load.pop(0)
                self.pv.pop(0)

                # Append new values
                self.load.append(self.load_filter.current_state())
                self.pv.append(self.pv_filter.current_state())
        else:
            # Append new values
            self.load.append(self.sub.house_power / (1000 *
                                                     (60 / self.time_step)))
            self.pv.append(self.sub.solar_power / (1000 *
                                                   (60 / self.time_step)))

        # Update Tariffs
        for i in range(0, data_skip + 1):
            self.import_tariff.append(self.import_tariff.pop(0))
            self.export_tariff.append(self.export_tariff.pop(0))

        # Update profile classes and energy system
        self.optimiser.update_profiles(np.array(self.load), np.array(self.pv),
                                       np.array(self.import_tariff),
                                       np.array(self.export_tariff),
                                       self.sub.bat_SOC)
        self.optimiser.update_energy_system()

    def connection_loop(self):
        while self.connected is False:

            # Checks for Initial Connection Values
            bat_connect = self.sub.bat_SOC == b'bat_connect'
            solar_connect = self.sub.solar_power == b'solar_connect'
            house_connect = self.sub.house_power == b'house_connect'

            connecting = bat_connect and solar_connect and house_connect
            residual_connect = bat_connect or solar_connect or house_connect

            # Runs if Initial Connection is Established
            if connecting:
                self.pub.pub_socket.send_string(
                    "%d %s" %
                    (self.settings.ZeroMQ["battery_power_topic"], 'connected'))
                self.initial_connect = True

            # Runs if all Subscribers start returning intended values
            if self.initial_connect and residual_connect is False:
                self.connected = True
                self.sub.initial_time = round(time.time(), 2)
                self.pub.initial_time = self.sub.initial_time
                if self.settings.simulation["use_visualisation"]:
                    self.plot.initial_time = self.sub.initial_time

    def main_loop(self):

        # Updates day counter
        self.update_day_counter()

        # Checks if all subscribers have new values
        all_read = self.sub.battery_read == 1 and self.sub.solar_read == 1 and self.sub.house_read == 1

        # Obtains the current time
        curr_time = self.current_time()

        # True every time step
        curr_data_mod = curr_time % (self.time_step / 60)
        self.data_mod = curr_data_mod < self.mod_thresh or (
            self.time_step / 60) - curr_data_mod < self.mod_thresh

        # True every control time step
        curr_control_mod = curr_time % (self.control_step / 60)
        self.control_mod = curr_control_mod < self.mod_thresh or (
            self.control_step / 60) - curr_control_mod < self.mod_thresh

        # True on every optimiser time step
        curr_opt_mod = curr_time % (self.opt_step / 60)
        self.opt_mod = curr_opt_mod < self.mod_thresh or (
            self.opt_step / 60) - curr_opt_mod < self.mod_thresh

        if all_read:

            # Applies Control if necessary
            self.apply_control()

            # Resets Subscriber Read Values
            self.sub.battery_read = 0
            self.sub.solar_read = 0
            self.sub.house_read = 0

    def apply_control(self):

        # Obtains current house time
        if bool(self.sub.data_store["house_time"]) is False:
            curr_time = 0
        else:
            curr_time = self.sub.data_store["house_time"][-1]

        # Calculates new grid value
        self.pub.set_grid(self.sub.solar_power, self.sub.house_power)

        # PV Self Consumption Control
        if self.settings.control["pv_self_cons"]:
            if self.control_mod and curr_time != self.prev_house_control:

                # Applies Control
                self.pub.non_optimiser_control(self.sub.bat_SOC)
                self.prev_house_control = curr_time

        # Optimiser Control
        if self.settings.control["optimiser"] and len(
                self.load) == (60 / self.time_step) * 24:
            if self.opt_mod and curr_time != self.prev_house_opt:
                # Applies Control
                self.data_skip = self.sub.house_num
                self.optimiser.optimise()
                self.power = list(self.optimiser.return_battery_power())
                self.data_skip = self.sub.house_num - self.data_skip
                self.optimiser_index = 0 + self.data_skip
                self.prev_house_opt = curr_time

        # Data Time Step
        if self.data_mod and curr_time != self.prev_house_data:

            # Print Outputs
            print('Optimiser skipped ' + str(self.data_skip) + ' time steps')
            print('Total PV system money saved = $' +
                  str(round(self.pub.house_import - self.pub.savings, 2)))
            print('Additional money saved by battery = $' +
                  str(round(self.pub.sol_savings - self.pub.savings, 2)))
            print('Total cost of load import = $' +
                  str(round(self.pub.house_import, 2)))
            print('Total load energy = ' + str(round(self.house_energy, 2)) +
                  ' kWh')
            print('Total solar energy = ' + str(round(self.solar_energy, 2)) +
                  ' kWh')
            print('Day counter = ' + str(self.sub.day_count))

            # Sets new power value
            if self.settings.control["pv_self_cons"] and self.settings.control[
                    "optimiser"]:
                if bool(self.power):
                    opt_power = self.power[self.optimiser_index] * 1000 * (
                        60 / self.time_step)
                    opt_power = self.power_scale * opt_power + (
                        1 - self.power_scale) * self.pub.bat_power
                    self.power_filter.step(0, opt_power)
                    self.pub.set_power(self.power_filter.current_state())
                else:
                    self.pub.set_power(0)
            if self.settings.control["optimiser"] and self.settings.control[
                    "pv_self_cons"] is False:
                if bool(self.power):
                    opt_power = self.power[self.optimiser_index] * 1000 * (
                        60 / self.time_step)
                    self.pub.set_power(opt_power)
                else:
                    self.pub.set_power(0)

            # Updates optimiser index and 24 hour data
            if self.settings.control["optimiser"]:
                self.optimiser_index += 1
            self.update_24_data(self.data_skip)
            self.prev_house_data = curr_time

            # Updates data stores and optimiser skips
            self.pub.update_data_store("grid", self.pub.grid,
                                       self.current_time())
            self.pub.update_data_store("bat", self.pub.bat_power,
                                       self.current_time())
            self.data_skip = 0

            # Calculates Savings
            self.calculate_savings()

            # Publishes power
            self.pub.publish_power()
Пример #3
0
class Subscriber:
    def __init__(self, config_settings):

        # Obtains settings from config file
        self.settings = config_settings

        # Parameters and Initial Values
        self.battery_read = 0
        self.solar_read = 0
        self.house_read = 0

        self.bat_SOC = self.settings.battery["initial_SOC"]
        self.solar_power = 0
        self.house_power = 0

        self.day_count = 0
        self.soc_num = 0
        self.solar_num = 0
        self.house_num = 0
        self.initial_time = 0

        # Creates Thread Lock
        self.lock = threading.Lock()

        # Erases Previous Text File Contents
        open("control_power_values.txt", "w+").close()

        # Creates Internal Data Store
        self.data_store = dict()
        self.data_store["soc_time"] = list()
        self.data_store["soc_value"] = list()
        self.data_store["soc_plot"] = list()
        self.data_store["solar_time"] = list()
        self.data_store["solar_value"] = list()
        self.data_store["solar_plot"] = list()
        self.data_store["house_time"] = list()
        self.data_store["house_value"] = list()
        self.data_store["house_plot"] = list()

        # Sets up Kalman Filters
        self.solar_cov = self.settings.control["solar_cov"]
        self.solar_filter = KalmanFilter(1, 0, 1, 0, 1, self.solar_cov, 1)
        self.house_cov = self.settings.control["house_cov"]
        self.house_filter = KalmanFilter(1, 0, 1, 700, 1, self.house_cov, 1)

        # Defines Sockets and Threads
        self.bat_socket = None
        self.solar_socket = None
        self.house_socket = None

        self.bat_thread = None
        self.solar_thread = None
        self.house_thread = None

        # Starts Subscribers
        self.start_subscribers()

    def write_to_text(self, device, curr_time, value):

        self.lock.acquire()

        file = open("control_power_values.txt", "a+")

        file.write("\n"+device+" "+str(curr_time)+" "+str(value))
        file.close()

        self.lock.release()

    def update_data_store(self, device, value):

        # New Value
        if device == "soc":
            self.data_store[device + "_value"].append(value)
        else:
            self.data_store[device + "_value"].append(value / 1000)

        # Time Value
        if self.settings.simulation["use_real_time"]:
            curr_time = (round(time.time() - self.initial_time, 2) / 3600) - (24 * self.day_count)
        elif bool(self.data_store[device + "_time"]) is False:
            curr_time = 0
        else:
            curr_time = self.data_store[device + "_time"][-1] + self.settings.simulation["time_step"] / 60
            if abs(curr_time - 24) < 0.02:
                curr_time = 0
        self.data_store[device + "_time"].append(curr_time)

        # Plot Value
        if 24 - self.data_store[device + "_time"][-1] <= 0.1:
            self.data_store[device + "_plot"].append(np.nan)
        else:
            if device == "soc":
                self.data_store[device + "_plot"].append(value / (100 / 6))
            else:
                self.data_store[device + "_plot"].append(value / 1000)

    def start_subscribers(self):

        # Connects Sockets
        sub_context = zmq.Context()
        self.bat_socket = sub_context.socket(zmq.SUB)
        self.bat_socket.connect("tcp://localhost:%s" % self.settings.ZeroMQ["battery_SOC_port"])
        self.bat_socket.setsockopt_string(zmq.SUBSCRIBE, str(self.settings.ZeroMQ["battery_SOC_topic"]))
        self.solar_socket = sub_context.socket(zmq.SUB)
        self.solar_socket.connect("tcp://localhost:%s" % self.settings.ZeroMQ["solar_port"])
        self.solar_socket.setsockopt_string(zmq.SUBSCRIBE, str(self.settings.ZeroMQ["solar_topic"]))
        self.house_socket = sub_context.socket(zmq.SUB)
        self.house_socket.connect("tcp://localhost:%s" % self.settings.ZeroMQ["house_port"])
        self.house_socket.setsockopt_string(zmq.SUBSCRIBE, str(self.settings.ZeroMQ["house_topic"]))

        # Starts Battery Sub Thread
        print('starting battery SOC subscriber')
        self.bat_thread = threading.Thread(target=self.battery_subscriber)
        self.bat_thread.start()

        # Starts Solar Sub Thread
        print('starting solar subscriber')
        self.solar_thread = threading.Thread(target=self.solar_subscriber)
        self.solar_thread.start()

        # Starts House Sub Thread
        print('starting house subscriber')
        self.house_thread = threading.Thread(target=self.house_subscriber)
        self.house_thread.start()

    def battery_subscriber(self):
        while True:
            # Obtains Value from Topic
            bat_string = self.bat_socket.recv()
            b_topic, self.bat_SOC = bat_string.split()

            # Runs if not connecting
            if self.bat_SOC != b'bat_connect':

                # Converts to int
                self.bat_SOC = int(self.bat_SOC)

                # Updates Text File and Data Store
                curr_time = (round(time.time() - self.initial_time, 2) / 3600) - (24 * self.day_count)
                self.write_to_text("SOC", curr_time, self.bat_SOC)
                self.update_data_store("soc", self.bat_SOC)

                # Sets Read and Increases Counter
                self.soc_num += 1
                self.battery_read = 1

    def solar_subscriber(self):
        while True:
            # Obtains Value from Topic
            solar_string = self.solar_socket.recv()
            s_topic, self.solar_power = solar_string.split()

            # Runs if not connecting
            if self.solar_power != b'solar_connect':

                # Converts to int
                self.solar_power = int(self.solar_power)

                # Applies Filtering
                if self.settings.control["solar_filtering"]:
                    self.solar_filter.step(0, self.solar_power)
                    self.solar_power = self.solar_filter.current_state()

                # Updates Text File and Data Store
                curr_time = (round(time.time() - self.initial_time, 2) / 3600) - (24 * self.day_count)
                self.write_to_text("solar", curr_time, self.solar_power)
                self.update_data_store("solar", self.solar_power)

                # Sets Read and Increases Counter
                self.solar_num += 1
                self.solar_read = 1

    def house_subscriber(self):
        while True:
            # Obtains Value from Topic
            house_string = self.house_socket.recv()
            h_topic, self.house_power = house_string.split()

            # Runs if not connecting
            if self.house_power != b'house_connect':

                # Converts to int
                self.house_power = int(self.house_power)

                # Applies Filtering
                if self.settings.control["house_filtering"]:
                    self.house_filter.step(0, self.house_power)
                    self.house_power = self.house_filter.current_state()

                # Updates Text File and Data Store
                curr_time = (round(time.time() - self.initial_time, 2) / 3600) - (24 * self.day_count)
                self.write_to_text("house", curr_time, self.house_power)
                self.update_data_store("house", self.house_power)

                # Sets Read and Increases Counter
                self.house_num += 1
                self.house_read = 1