An 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 | 81% |
1.6 | messages_1.6.md | 0% |
Add Dependency
def deps do
[
{:ocpp_model, "~> 0.3.0"}
]
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
+-------------------+ +------------+ +------------------------+
| Charger Behaviour | | OCPP Model | | ChargeSystem Behaviour |
+-------------------+ +------------+ +------------------------+
| | | |
| +--------------------------------+ +--------------------------------+ |
| | | |
V V V V
+---------------+ +----------------+ Internet +----------------+ +--------------------+
| MyTestCharger | <-> | json/websocket | <- Lora -> | websocket/json | <-> | MyTestChargeSystem |
+---------------+ +----------------+ IoT +----------------+ +--------------------+
An example Charger
defmodule Implementations.MyTestBasicCharger 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], state) do
case B.BasicCharger.handle(__MODULE__, action, payload, state) do
{{:ok, response_payload}, new_state} -> {[3, id, response_payload], new_state}
{{:error, error, desc}, new_state} -> {[4, id, Atom.to_string(error), desc, {}], new_state}
end
end
def handle({[3, id, payload], _state}),
do: IO.puts("Received answer for id #{id}: #{inspect(payload)}")
def handle({[4, id, err, desc, det], _state}),
do: IO.puts("Received error for id #{id}: #{err}, #{desc}, #{det}")
@impl B.BasicCharger
def reset(req, state) do
if ET.validate?(:reset, req.type) do
case req.type do
"Immediate" -> {{:ok, %M.ResetResponse{status: "Accepted"}}, state}
"OnIdle" -> {{:ok, %M.ResetResponse{status: "Scheduled"}}, state}
end
else
{{:error, "Unknown Reset Type #{req.type}"}, state}
end
end
@impl B.BasicCharger
def unlock_connector(_req, state),
do:
{{:ok,
%M.UnlockConnectorResponse{
status: "Unlocked",
statusInfo: %DT.StatusInfo{reasonCode: "cable unlocked"}
}}, state}
end
An Example ChargeSystem
defmodule Implementations.MyTestBasicChargeSystem 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], state) do
case B.BasicChargeSystem.handle(__MODULE__, action, payload, state) do
{{:ok, response_payload}, new_state} -> {[3, id, response_payload], new_state}
{{:error, error, desc}, state} -> {[4, id, Atom.to_string(error), desc, {}], state}
end
end
def handle({[3, id, payload], _state}),
do: IO.puts("Received answer for id #{id}: #{inspect(payload)}")
def handle({[4, id, err, desc, det], _state}),
do: IO.puts("Received error for id #{id}: #{err}, #{desc}, #{det}")
@impl B.BasicChargeSystem
def authorize(_req, state) do
{{:ok, %M.AuthorizeResponse{idTokenInfo: %DT.IdTokenInfo{status: "Accepted"}}}, state}
end
@impl B.BasicChargeSystem
def boot_notification(req, state) do
if ET.validate?(:bootReason, req.reason) do
{{:ok,
%M.BootNotificationResponse{
currentTime: current_time(),
interval: 900,
status: %DT.StatusInfo{reasonCode: ""}
}}, state}
else
{{:error, :boot_notification, "#{req.reason} is not a valid BootReason"}, state}
end
end
@impl B.BasicChargeSystem
def heartbeat(_req, state) do
{{:ok, %M.HeartbeatResponse{currentTime: current_time()}}, state}
end
@impl B.BasicChargeSystem
def status_notification(_req, state) do
{{:ok, %M.StatusNotificationResponse{}}, state}
end
@impl B.BasicChargeSystem
def transaction_event(_req, state) do
{{:ok, %M.TransactionEventResponse{}}, state}
end
def current_time, do: DateTime.now!("Etc/UTC") |> DateTime.to_iso8601()
end