README.md

# An Ocpp Model
[![codecov](https://codecov.io/gh/gertjana/ocpp_model/branch/main/graph/badge.svg?token=nrXnKllzIA)](https://codecov.io/gh/gertjana/ocpp_model)
[![gitlab](https://gitlab.com/gertjana/ocpp_model/badges/develop/pipeline.svg)](https://gitlab.com/gertjana/ocpp_model)
[![Hex pm](http://img.shields.io/hexpm/v/ocpp_model.svg?style=flat)](https://hex.pm/packages/ocpp_model)

Currently I'm doing 2 experiments
 - An Ocpp Backend in Elixir
 - A Nerves based Charger on a Rasberry Pi Zero

This library contains the OCPP 2.0.1 Model / Protocol that is needed for both those projects

It will be populated on a 'need to have' basis starting with basic charger functionality

## Implemented Messages

| OCPP Version | State                                  | Done-ness |
| ------------ | -------------------------------------- | --------- |
| 2.0.1        | [messages_2.0.1.md](messages_2.0.1.md) | 81%       |
| 1.6          | [messages_1.6.md](messages_1.6.md)     | 0%        |

## Add Dependency

```elixir
def deps do
  [
    {:ocpp_model, "~> 0.2.5"}
  ]
end
```

## Usage

Using the library is by having your modules assume either the of the following behaviours:

| Behaviour                                    | Summary                                                         |
| -------------------------------------------- | --------------------------------------------------------------- |
| `OcppModel.V20.Behaviours.BasicCharger`      | Supports callbacke for only the basic messages                  |
| `OcppModel.V20.Behaviours.Charger`           | Supports callbacks for all messages implemented in this library |
| `OcppModel.V20.Behaviours.BasicChargeSystem` | Supports callbacks for only the basic messages                  |
| `OcppModel.V20.Behaviours.ChargeSystem`      | Supports callbacks for all messages implemented in this library |


This library does not make any decisions on transport, you can do the json over websockets thing, or protobuf over http long-polling
or an IoT solution as long as it supports bi-directional communication

```bash
+-------------------+                    +------------+                    +------------------------+
| Charger Behaviour |                    | OCPP Model |                    | ChargeSystem Behaviour |
+-------------------+                    +------------+                    +------------------------+
    |                                       |      |                                          |
    |      +--------------------------------+      +--------------------------------+         |
    |      |                                                                        |         |
    V      V                                                                        V         V
+---------------+     +----------------+    Internet    +----------------+     +--------------------+
| MyTestCharger | <-> | json/websocket | <- Lora     -> | websocket/json | <-> | MyTestChargeSystem | 
+---------------+     +----------------+    IoT         +----------------+     +--------------------+ 
```

## An example Charger

```elixir
defmodule Implementations.MyBasicCharger do
  @moduledoc """
    Basic charger implementation, only supports the minimum required to do a chargesession.
  """

  alias OcppModel.V20.Behaviours, as: B
  alias OcppModel.V20.DataTypes,  as: DT
  alias OcppModel.V20.EnumTypes,  as: ET
  alias OcppModel.V20.Messages,   as: M

  @behaviour B.BasicCharger

  def handle([2, id, action, payload]) do
    case B.BasicCharger.handle(__MODULE__, action, payload) do
      {:ok, response_payload} -> [3, id, response_payload]
      {:error, error, desc} -> [4, id, Atom.to_string(error), desc, {}]
    end
  end

  def handle([3, id, payload]), do: IO.puts "Received answer for id #{id}: #{inspect(payload)}"
  def handle([4, id, err, desc, det]), do: IO.puts "Received error for id #{id}: #{err}, #{desc}, #{det}"

  @impl B.BasicCharger
  def reset(req) do
    if ET.validate?(:reset, req.type) do
      case req.type do
        "Immediate" -> {:ok, %M.ResetResponse{status: "Accepted"}}
        "OnIdle"    -> {:ok, %M.ResetResponse{status: "Scheduled"}}
      end
    else
      {:error, "Unknown Reset Type #{req.type}"}
    end
  end

  @impl B.BasicCharger
  def unlock_connector(_req), do:
    {:ok, %M.UnlockConnectorResponse{status: "Unlocked",
                                     statusInfo: %DT.StatusInfo{reasonCode: "cable unlocked"}}}

end
```

## An Example ChargeSystem

```elixir
defmodule Implementations.MyBasicChargeSystem do
  @moduledoc """
    Basic chargesystem implementation, only supports the minimum required to do a chargesession.
  """

  alias OcppModel.V20.Behaviours, as: B
  alias OcppModel.V20.DataTypes,  as: DT
  alias OcppModel.V20.EnumTypes,  as: ET
  alias OcppModel.V20.Messages,   as: M

  @behaviour B.BasicChargeSystem

  def handle([2, id, action, payload]) do
    case B.BasicChargeSystem.handle(__MODULE__, action, payload) do
      {:ok, response_payload} -> [3, id, response_payload]
      {:error, error, desc} -> [4, id, Atom.to_string(error), desc, {}]
    end
  end
  def handle([3, id, payload]), do: IO.puts "Received answer for id #{id}: #{inspect(payload)}"
  def handle([4, id, err, desc, det]), do: IO.puts "Received error for id #{id}: #{err}, #{desc}, #{det}"

  @impl B.BasicChargeSystem
  def authorize(_req) do
    {:ok, %M.AuthorizeResponse{idTokenInfo: %DT.IdTokenInfo{status: "Accepted"}}}
  end

  @impl B.BasicChargeSystem
  def boot_notification(req) do
    if ET.validate?(:bootReason, req.reason) do
      {:ok, %M.BootNotificationResponse{currentTime: current_time(), interval: 900,
              status: %DT.StatusInfo{reasonCode: ""}}}
    else
      {:error, :invalid_bootreason, "#{req.reason} is not a valid BootReason"}
    end
  end

  @impl B.BasicChargeSystem
  def heartbeat(_req) do
    {:ok, %M.HeartbeatResponse{currentTime: current_time()}}
  end

  @impl B.BasicChargeSystem
  def status_notification(_req) do
    {:ok, %M.StatusNotificationResponse{}}
  end

  @impl B.BasicChargeSystem
  def transaction_event(_req) do
    {:ok, %M.TransactionEventResponse{}}
  end

  def current_time, do: DateTime.now!("Etc/UTC") |> DateTime.to_iso8601()
end
```