// Contains the definitions for Microgrid gRPC API.
//
// Copyright:
// Copyright 2022 Frequenz Energy-as-a-Service GmbH
//
// License:
// MIT

syntax = "proto3";

package frequenz.api.microgrid;

import "frequenz/api/microgrid/battery.proto";
import "frequenz/api/microgrid/ev_charger.proto";
import "frequenz/api/microgrid/grid.proto";
import "frequenz/api/microgrid/inverter.proto";
import "frequenz/api/microgrid/meter.proto";
import "frequenz/api/microgrid/sensor.proto";

import "frequenz/api/common/components.proto";
import "frequenz/api/common/metrics.proto";

import "google/api/annotations.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/wrappers.proto";

service Microgrid {
  /// Returns the microgrid metadata
  /// The metadata consists of information that describes the overall
  /// microgrid, as opposed to its components,
  /// e.g., the microgrid ID, location.
  rpc GetMicrogridMetadata(google.protobuf.Empty) returns (MicrogridMetadata) {
    option (google.api.http) = {
      get : "/v1/metadata"
    };
  }

  // List components in the local microgrid, optionally filtered by a given list
  // of component IDs and component categories.
  //
  // If provided, the filters for component IDs and categories have an `AND`
  // relationship with one another, meaning that they are applied serially,
  // but the elements within a single filter list have an `OR` relationship with
  // each other.
  // E.g., if `ids` = [1, 2, 3], and `categories` = [
  //  `ComponentCategory::COMPONENT_CATEGORY_INVERTER`,
  //  `ComponentCategory::COMPONENT_CATEGORY_BATTERY`],
  // then the results will consist of elements that
  // have the IDs 1, OR 2, OR 3,
  // AND
  // are of the categories `ComponentCategory::COMPONENT_CATEGORY_INVERTER` OR
  // `ComponentCategory::COMPONENT_CATEGORY_BATTERY`.
  //
  // If a filter list is empty, then that filter is not applied.
  rpc ListComponents(ComponentFilter) returns (ComponentList) {
    option (google.api.http) = {
      get : "/v1/components"
    };
  }

  // Returns a list of the connections between components as `(start, end)`
  // pairs of connection IDs, where the direction of individual connections
  // is always away from the grid endpoint, i.e. aligned with the direction
  // of positive current according to the passive sign convention:
  // https://en.wikipedia.org/wiki/Passive_sign_convention
  //
  // The request may be filtered by `start`/`end` component(s) of individual
  // connections.  If provided, the `start` and `end` filters have an `AND`
  // relationship between each other, meaning that they are applied serially,
  // but an `OR` relationship with other elements in the same list.
  // For example, if `start` = `[1, 2, 3]`, and `end` = `[4, 5, 6]`, then the
  // result should have all the connections where
  // * each `start` component ID is either `1`, `2`, OR `3`,
  //  AND
  // * each `end` component ID is either `4`, `5`, OR `6`.
  rpc ListConnections(ConnectionFilter) returns (ConnectionList) {
    option (google.api.http) = {
      get : "/v1/connections"
    };
  }

  // Returns a stream containing data from a component with a given ID.
  rpc StreamComponentData(ComponentIdParam) returns (stream ComponentData) {
    option (google.api.http) = {
      get : "/v1/components/{id}/data"
    };
  }

  // Returns whether the component with given ID can stream data.
  rpc CanStreamData(ComponentIdParam) returns (google.protobuf.BoolValue) {
    option (google.api.http) = {
      get : "/v1/components/{id}/canStream"
    };
  }

  // Adds exclusion bounds for a given metric of a given component, and returns
  // the UTC timestamp until which the given exclusion bounds will stay in
  // effect.
  //
  // Exclusion bounds refer to the range of values that are disallowed for the
  // metric. If these bounds for a metric are [`lower`, `upper`], then this
  // metric's `value` needs to comply with the constraints
  // `value <= lower` OR `upper <= value`.
  //
  // Exclusion bounds are a useful tool for enhancing the performance of a
  // system. They can be used to restrict the acceptance of commands that fall
  // below a certain threshold, which can help ensure the smooth functioning of
  // the system.
  // E.g., exclusion bounds can be set to limit the minimum charging power to a
  // sufficiently high level, preventing a peak-shaver client from sending
  // charge powers that are too low when a DC heater client is executing a
  // charge pulse. This can significantly improve the overall performance of the
  // DC heating mechanism.
  //
  //
  // If multiple exclusion bounds have been provided bor a metric, then the
  // aggregated lower and upper exclusion bounds are calculated as follows:
  // lower: the minimum of all lower exclusion bounds
  // upper: the maximum of all upper exclusion bounds
  //
  // It is important to note that these bounds work together with
  // `system_inclusion_bounds`.
  //
  // E.g., for the system to accept a charge command,
  // clients need to request power values within the bounds
  // `[system_inclusion_bounds.lower, system_exclusion_bounds.lower]`.
  // This means that clients can only request charge commands with values that
  // are within the `system_inclusion_bounds`, but not within
  // `system_exclusion_bounds`.
  // Similarly, for the system to accept a discharge command,
  // clients need to request power values within the bounds
  // `[system_exclusion_bounds.upper, system_inclusion_bounds.upper]`.
  //
  // The following diagram illustrates the relationship between the bounds.
  // ```
  //   inclusion.lower                              inclusion.upper
  // <-------|============|------------------|============|--------->
  //                exclusion.lower    exclusion.upper
  // ```
  // ---- values here are disallowed and wil be rejected
  // ==== vales here are allowed and will be accepted
  rpc AddExclusionBounds(SetBoundsParam) returns (google.protobuf.Timestamp);

  // Adds inclusion bounds for a given metric of a given component, and returns
  // the UTC timestamp until which the given inclusion bounds will stay in
  // effect.
  //
  // Inclusion bounds refer to the range of values that are allowed for the
  // metric. If these bounds for a metric are [`lower`, `upper`], then this
  // metric's `value` needs to comply with the constraint
  // `lower <= value <= upper`.
  //
  // If multiple inclusion bounds have been provided bor a metric, then the
  // aggregated lower and upper inclusion bounds are calculated as follows:
  // lower: the maximum of all lower inclusion bounds
  // upper: the minimum of all upper inclusion bounds
  //
  // It is important to note that these bounds work together with
  // `system_exclusion_bounds`.
  //
  // E.g., for the system to accept a charge command,
  // clients need to request power values within the bounds
  // `[system_inclusion_bounds.lower, system_exclusion_bounds.lower]`.
  // This means that clients can only request charge commands with values that
  // are within the `system_inclusion_bounds`, but not within
  // `system_exclusion_bounds`.
  // Similarly, for the system to accept a discharge command,
  // clients need to request power values within the bounds
  // `[system_exclusion_bounds.upper, system_inclusion_bounds.upper]`.
  //
  // The following diagram illustrates the relationship between the bounds.
  // ```
  //   inclusion.lower                              inclusion.upper
  // <-------|============|------------------|============|--------->
  //                exclusion.lower    exclusion.upper
  // ```
  // ---- values here are disallowed and wil be rejected
  // ==== vales here are allowed and will be accepted
  rpc AddInclusionBounds(SetBoundsParam) returns (google.protobuf.Timestamp);

  // Sets the active power output of a component with a given ID, provided the
  // component supports it.
  //
  // Note that the target component may have a resolution of more than 1 W.
  // E.g., an inverter may have a resolution of 88 W.
  // In such cases, the magnitude of power will be floored to the nearest
  // multiple of the resolution.
  //
  // Performs the following sequence actions for the following component
  // categories:
  //
  // * Inverter: Sends the discharge command to the inverter, making it deliver
  //  AC power.
  rpc SetPowerActive(SetPowerActiveParam) returns (google.protobuf.Empty) {
    option (google.api.http) = {
      get : "/v1/components/{component_id}/setPowerActive/{power}"
    };
  }

  // Sets the reactive power output of a component with a given ID, provided the
  // component supports it.
  //
  // Note that the target component may have a resolution of more than 1 VAr.
  // E.g., an inverter may have a resolution of 88 VAr.
  // In such cases, the magnitude of power will be floored to the nearest
  // multiple of the resolution.
  rpc SetPowerReactive(SetPowerReactiveParam) returns (google.protobuf.Empty) {
    option (google.api.http) = {
      get : "/v1/components/{component_id}/setPowerReactive/{power}"
    };
  }

  // Starts the component, and brings it into a state where it is immediately
  // operational.
  //
  // Performs the following sequence of actions for the following component
  // categories:
  //
  // * Inverter:
  //  * closes DC relays, if the feature is available
  //  * closes AC relays
  //  * sets power output to 0
  //
  // * Battery: Checks if DC relays are open, then
  //  * closes DC relays
  //
  // If any of the above mentioned actions for a given component has already
  // been performed, then this method call effectively skips that action.
  //
  // If a feature required to perform an action is missing, then that action is
  // skipped.
  rpc Start(ComponentIdParam) returns (google.protobuf.Empty) {
    option (google.api.http) = {
      get : "/v1/components/{id}/start"
    };
  }

  // Sets the given component into a hot-standby state, from which it can return
  // into an operational state within at most 5 seconds.
  //
  // Performs the following sequence actions for the following component
  // categories:
  //
  // * Inverter: Checks if AC and DC relays are closed, then
  //  * sets power to 0
  //
  // If any of the checks mentioned above fails, then the method call returns an
  // error.
  //
  // If any of the above mentioned actions for a given component has already
  // been performed, then this method call effectively skips that action.
  rpc HotStandby(ComponentIdParam) returns (google.protobuf.Empty) {
    option (google.api.http) = {
      get : "/v1/components/{id}/hotStandby"
    };
  }

  // Sets the given component into a cold-standby state, from which it can
  // return into an operational state within at most 2 minutes.
  //
  // Performs the following sequence actions for the following component
  // categories:
  //
  // * Inverter: Checks if AC and DC relays are closed, then
  //  * sets power to 0, and
  //  * opens AC relays
  //
  // If any of the checks mentioned above fails, then the method call returns an
  // error.
  //
  // If any of the above mentioned actions for a given component has already
  // been performed, then this method call efffectively skips that action.
  rpc ColdStandby(ComponentIdParam) returns (google.protobuf.Empty) {
    option (google.api.http) = {
      get : "/v1/components/{id}/coldStandby"
    };
  }

  // Stops the component completely, potentially disengaging its power
  // electronics controller(s).
  //
  // Performs the following sequence actions for the following component
  // categories:
  //
  // * Inverter:
  //  * Brings component to the cold-standby state
  //  * opens DC relays
  //
  // * Battery: Checks if the power output is 0, then
  //  * opens DC relays
  //
  // If any of the checks mentioned above fails, then the method call returns an
  // error.
  //
  // If any of the above mentioned actions for a given component has already
  // been performed, then this method call effectively skips that action.
  //
  // If a feature required to perform an action is missing, then that action is
  // skipped.
  rpc Stop(ComponentIdParam) returns (google.protobuf.Empty) {
    option (google.api.http) = {
      get : "/v1/components/{id}/stop"
    };
  }

  // Acknowledges any recoverable error reported by the component, and brings it
  // back to the stopped or cold-standby state.
  rpc ErrorAck(ComponentIdParam) returns (google.protobuf.Empty) {
    option (google.api.http) = {
      get : "/v1/components/{id}/errorAck"
    };
  }
}

// A pair of geographical co-ordinates, representing the location of a place.
message Location {
  // The latitude of the place.
  float latitude = 1;

  // The longitude of the place.
  float longitude = 2;
}

// Metadata that describes a microgrid.
message MicrogridMetadata {
  // The microgrid ID.
  // This is a natural number that uniquely identifies a given microgrid.
  uint64 microgrid_id = 1;

  // The location of the microgrid, in geographical co-ordinates.
  Location location = 2;
}

// Parameters for filtering the components.
message ComponentFilter {
  // Return components that have the specified IDs only.
  repeated uint64 ids = 1;

  // Return components that have the specified categories only.
  repeated frequenz.api.common.components.ComponentCategory categories = 2;
}

// Encapsulation of a component ID, intended to be used as a parameter for rpc
// methods.
message ComponentIdParam {
  uint64 id = 1;
}

// Parameters for filtering the component connections
message ConnectionFilter {
  // Only return connections that start from the specified component ID(s):
  // if empty, connections with any `start` will be returned
  repeated uint64 starts = 1;

  // Only return connections that end at the specified component ID(s):
  // if empty, connections with any `end` will be returned
  repeated uint64 ends = 2;
}

// Parameters for setting the charge/discharge power of an appropriate
// component.
message PowerLevelParam {
  // The ID of the component to set the output power of.
  uint64 component_id = 1;

  // The output power level, in watts. This is always a +ve integer. The sign
  // of the power level is controlled by the implementations of the `Charge`
  // and `Discharge` RPC methods.
  uint64 power_w = 2;
}

// Parameters for setting the active power of an appropriate component using the
// `SetPowerActive` RPC.
message SetPowerActiveParam {
  // The ID of the component to set the output active power of.
  uint64 component_id = 1;

  // The output active power level, in watts.
  // -ve values are for discharging, and +ve values are for charging.
  float power = 2;
}

// Parameters for setting the reactive power of an appropriate component using
// the `SetPowerReactive` RPC.
message SetPowerReactiveParam {
  // The ID of the component to set the output reactive power of.
  uint64 component_id = 1;

  // The output reactive power level, in VAr.
  // -ve values are for inductive (lagging) power , and +ve values are for
  //  capacitive (leading) power.
  float power = 2;
}

// Parameters for setting bounds of a given metric of a given component.
message SetBoundsParam {
  // An enumerated list of metrics whose bounds can be set.
  enum TargetMetric {
    TARGET_METRIC_UNSPECIFIED = 0;
    TARGET_METRIC_POWER_ACTIVE = 1;
    TARGET_METRIC_CURRENT = 2;
    TARGET_METRIC_CURRENT_PHASE_1 = 3;
    TARGET_METRIC_CURRENT_PHASE_2 = 4;
    TARGET_METRIC_CURRENT_PHASE_3 = 5;
    TARGET_METRIC_POWER_REACTIVE = 6;
  }

  // The ID of the target component.
  uint64 component_id = 1;

  // The target metric whose bounds have to be set.
  TargetMetric target_metric = 2;

  // The bounds for the target metric.
  frequenz.api.common.metrics.Bounds bounds = 3;
}

// A generic message for components. It is used to represent any category of
// component, with its static parameters.
message Component {
  // A unique identifier for the component.
  uint64 id = 1;

  // An optional name for the component.
  string name = 2;

  // The category of the component.
  frequenz.api.common.components.ComponentCategory category = 3;

  // The component manufacturer.
  string manufacturer = 4;

  // The model name of the component.
  string model_name = 5;

  // The metadata specific to the component category.
  oneof metadata {
    grid.Metadata grid = 11;
    battery.Metadata battery = 12;
    inverter.Metadata inverter = 13;
    meter.Metadata meter = 14;
    ev_charger.Metadata ev_charger = 15;
    sensor.Metadata sensor = 16;
  }
}

// A message containing a list of components, used as a return typ in certain
// RPC methods.
message ComponentList {
  repeated Component components = 1;
}

// A generic container for data that can originate from any component type.
message ComponentData {
  // The timestamp of when the data was measured.
  google.protobuf.Timestamp ts = 1;

  // The component ID.
  uint64 id = 2;

  // The data object.
  oneof data {
    meter.Meter meter = 3;
    inverter.Inverter inverter = 4;
    battery.Battery battery = 5;
    ev_charger.EvCharger ev_charger = 6;
    sensor.Sensor sensor = 7;
  }
}

// Describes a single connection between components of the microgrid,
// with direction away from the grid endpoint, meaning it is aligned
// with positive current according to the passive sign convention:
// https://en.wikipedia.org/wiki/Passive_sign_convention
message Connection {
  // `id` of the component the connection starts from
  uint64 start = 1;

  // `id` of the component the connection points to
  uint64 end = 2;
}

// List of connections between components
message ConnectionList {
  repeated Connection connections = 1;
};
