srcfl / srcful-gateway

Srcful energy gateway to connect your solar inverter and mine src-token
https://srcful.io
MIT License
4 stars 1 forks source link

Add support for multi DER #209

Open h0bb3 opened 1 month ago

h0bb3 commented 1 month ago

related to #177

the fw would need to support multiple DERs. Eg. PV and Battery. This should be flexible enough so that any DER can be added. At the highest level there are Production DERs (PV), Consumption DERs (heater) and combination PruductonAndConsumption (battery).

Maybe we can find some common interface(s) and work with that. DERs should support harvesting data and controll (depending on DER type). The underlying mechanism to do this is currently via an inverter -> modbus tcp. So this seems like another layer of abstraction on top of things?

This would mean the harvester works with a DER and not an inverter. The DER then implements the actual harvesting.

An open issue is if a user has both Solar and Battery, will it be able to participate in controll contracts of both kinds or is the control at the level of the "facility"?

h0bb3 commented 1 month ago

One interesting aspect is that as the tasks are now threads of their own the Harvest can actually be simplified (as the non blocking task requirement is no more). This would allow for some other design alternatives. I.e we could push some parts of the retry functionality to the inverter (or some new middle man class i.e. InverterHarvest) That is something not also a Task (as we do not need to return but can simply sleep).

In fact the harvest can be one perpetual task with e.g. a while true loop in it. Though then we do loose the ability to control, i.e kill, the thread if we do not specifically create something new here in this extreme case.

But the Harvest task could essentially be

try:
  backoff_time, data = self.der.harvest()
  self.time += backoff_time
  # harvest transport stuff

  return self
except:
 # ... possibly no need to do anything here
h0bb3 commented 1 month ago

This is basically the current design

classDiagram
    Inverter <|-- ModbusTCP
    Inverter <|-- ModbusRTU
    Inverter <|-- SolarmanTCP

    TaskScheduler --> "*" ITask

    ITask <|.. Task

    Task <|-- Harvest
    Task --> BlackBoard

    BlackBoard--> "*" Inverter

    Harvest --> Inverter
    Harvest ..> HarvestTransport
    Task <|-- HarvestTransport

    class Task {
        event_time:int
    }

    class ITask {
      <<interface>>
    }
h0bb3 commented 1 month ago

latest iteration

classDiagram

    namespace der_hardware_protocol {
        class ICom {
            <<interface>>
            +connect()*
            +read_harvest_data()*
            +get_harvest_data_type()*
            +get_hw_id()*
        }

        class HarvestDataType {
            <<enumeration>>
            MODBUS_REGISTERS
            JSON_OBJECT
        }

        class ModbusTCP {
            +connect()
        }

        class ModbusRTU {
            +connect()
        }
        class MagickAPI
        class Solarman {
        }

        class Sunspec

        class hwc_factory {
            create_from_string(...) ICom
            create_from_json(...)
        }
    }

    hwc_factory ..> ModbusTCP
    hwc_factory ..> ModbusRTU
    hwc_factory ..> Solarman
    hwc_factory ..> MagickAPI
    hwc_factory ..> Sunspec
    ICom ..> HarvestDataType
    ModbusTCP ..|> ICom
    ModbusRTU ..|> ICom
    Solarman ..|> ICom
    MagickAPI ..|> ICom
    Sunspec ..|> ICom

    class DER {
        +read_harvest_data()*
        +get_hw_id()*
    }

    class IConsumer {
        <<interface>>
        +consume(percent)*
    }

    class IProducer {
        <<interface>>
        +produce(percent)*
    }

    class IStorage {
        <<interface>>
        +stateOfCharge()*
        +getConfig()*
    }

        class Solar {
        +produce()
    }

    namespace tasks {
        class ITask {
           <<interface>>
        }

        class Task {

        }

        class ControlProduction {

        }

        class Harvest
        class OpenDERTask
        class OpenDERPerpetualTask

    }

    namespace der {
        class DER
        class IProducer
        class IConsumer
        class IStorage
        class Solar
        class PoolHeater
        class Battery
    }

    BlackBoard --> "*"IConsumer
    BlackBoard --> "*"IProducer
    BlackBoard --> "*"IStorage

    Task ..|> ITask
    Task --> BlackBoard

    Harvest --|> Task
    OpenDERPerpetualTask --|> Task
    OpenDERTask --|> Task
    ControlProduction --|> Task
    ControlProduction --> IProducer
    Harvest --> DER
    OpenDERPerpetualTask --> DER
    OpenDERTask --> DER
    TaskQueue --> "*" ITask

    DER <|-- PoolHeater
    DER <|-- Battery

    IStorage <|.. Battery
    IConsumer <|.. PoolHeater
    IProducer <|.. Solar
    DER <|-- Solar

    DER --> "icomm" ICom : 
h0bb3 commented 1 week ago

I will suppose this issue should be divided into several smaller ones to not create a mega merge and break everything.

davmoz commented 5 days ago

The hw_factory side is partially implemented now. Consider making ModbusRTU/TCP/Solarman use SunSpecModbusClientDevice for "normal" modbus operations, e.g. read/write registers.

Updated version:


classDiagram

    namespace der_hardware_protocol {
        class ICom {
            <<interface>>
            +connect()*
            +read_harvest_data()*
            +get_harvest_data_type()*
            +get_hw_id()*
        }

        class HarvestDataType {
            <<enumeration>>
            MODBUS_REGISTERS
            JSON_OBJECT
        }

        class ModbusTCP 
        class ModbusRTU 
        class MagicAPI
        class Solarman 
        class SunSpec

        class hwc_factory {
            create_from_string(...) ICom
            create_from_json(...)
        }

        class ModbusDevice
        class SunSpecModbusClientDevice
    }

    hwc_factory ..> ModbusTCP
    hwc_factory ..> ModbusRTU
    hwc_factory ..> Solarman
    hwc_factory ..> MagicAPI
    hwc_factory ..> SunSpec
    ICom ..> HarvestDataType
    ModbusTCP --|> ModbusDevice
    ModbusRTU --|> ModbusDevice
    Solarman --|> ModbusDevice

    SunSpec --|> SunSpecModbusClientDevice
    SunSpecModbusClientDevice ..|> ICom

    ModbusDevice ..|> ICom

    MagicAPI ..|> ICom

    class DER {
        +read_harvest_data()*
        +get_hw_id()*
    }

    class IConsumer {
        <<interface>>
        +consume(percent)*
    }

    class IProducer {
        <<interface>>
        +produce(percent)*
    }

    class IStorage {
        <<interface>>
        +stateOfCharge()*
        +getConfig()*
    }

        class Solar {
        +produce()
    }

    namespace tasks {
        class ITask {
           <<interface>>
        }

        class Task {

        }

        class ControlProduction {

        }

        class Harvest
        class OpenDERTask
        class OpenDERPerpetualTask

    }

    namespace der {
        class DER
        class IProducer
        class IConsumer
        class IStorage
        class Solar
        class PoolHeater
        class Battery
    }

    BlackBoard --> "*"IConsumer
    BlackBoard --> "*"IProducer
    BlackBoard --> "*"IStorage

    Task ..|> ITask
    Task --> BlackBoard

    Harvest --|> Task
    OpenDERPerpetualTask --|> Task
    OpenDERTask --|> Task
    ControlProduction --|> Task
    ControlProduction --> IProducer
    Harvest --> DER
    OpenDERPerpetualTask --> DER
    OpenDERTask --> DER
    TaskQueue --> "*" ITask

    DER <|-- PoolHeater
    DER <|-- Battery

    IStorage <|.. Battery
    IConsumer <|.. PoolHeater
    IProducer <|.. Solar
    DER <|-- Solar

    DER --> "icomm" ICom :