diff --git a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
index 3ab35fe84bbf..763010ae82a3 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
@@ -278,6 +278,16 @@
services.prometheus.sachet.
+
+
+ EVCC is an EV charge
+ controller with PV integration. It supports a multitude of
+ chargers, meters, vehicle APIs and more and ties that together
+ with a well-tested backend and a lightweight web frontend.
+ Available as
+ services.evcc.
+
+
infnoise,
diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md
index 81aef68a716f..b8aa0cc09fa9 100644
--- a/nixos/doc/manual/release-notes/rl-2211.section.md
+++ b/nixos/doc/manual/release-notes/rl-2211.section.md
@@ -98,6 +98,8 @@ In addition to numerous new and upgraded packages, this release has the followin
- [Sachet](https://github.com/messagebird/sachet/), an SMS alerting tool for the Prometheus Alertmanager. Available as [services.prometheus.sachet](#opt-services.prometheus.sachet.enable).
+- [EVCC](https://evcc.io) is an EV charge controller with PV integration. It supports a multitude of chargers, meters, vehicle APIs and more and ties that together with a well-tested backend and a lightweight web frontend. Available as [services.evcc](#opt-services.evcc.enable).
+
- [infnoise](https://github.com/leetronics/infnoise), a hardware True Random Number Generator dongle.
Available as [services.infnoise](options.html#opt-services.infnoise.enable).
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 2a23a32eaba6..bc5f6f1d76cd 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -491,6 +491,7 @@
./services/hardware/vdr.nix
./services/home-automation/home-assistant.nix
./services/home-automation/zigbee2mqtt.nix
+ ./services/home-automation/evcc.nix
./services/logging/SystemdJournal2Gelf.nix
./services/logging/awstats.nix
./services/logging/filebeat.nix
diff --git a/nixos/modules/services/home-automation/evcc.nix b/nixos/modules/services/home-automation/evcc.nix
new file mode 100644
index 000000000000..c12ba9d0c1e2
--- /dev/null
+++ b/nixos/modules/services/home-automation/evcc.nix
@@ -0,0 +1,92 @@
+{ lib
+, pkgs
+, config
+, ...
+}:
+
+with lib;
+
+let
+ cfg = config.services.evcc;
+
+ format = pkgs.formats.yaml {};
+ configFile = format.generate "evcc.yml" cfg.settings;
+
+ package = pkgs.evcc;
+in
+
+{
+ meta.maintainers = with lib.maintainers; [ hexa ];
+
+ options.services.evcc = with types; {
+ enable = mkEnableOption (lib.mdDoc "EVCC, the extensible EV Charge Controller with PV integration");
+
+ extraArgs = mkOption {
+ type = listOf str;
+ default = [];
+ description = lib.mdDoc ''
+ Extra arguments to pass to the evcc executable.
+ '';
+ };
+
+ settings = mkOption {
+ type = format.type;
+ description = lib.mdDoc ''
+ evcc configuration as a Nix attribute set.
+
+ Check for possible options in the sample [evcc.dist.yaml](https://github.com/andig/evcc/blob/${package.version}/evcc.dist.yaml].
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.evcc = {
+ after = [
+ "network-online.target"
+ "mosquitto.target"
+ ];
+ wantedBy = [
+ "multi-user.target"
+ ];
+
+ serviceConfig = {
+ ExecStart = "${package}/bin/evcc --config ${configFile} ${escapeShellArgs cfg.extraArgs}";
+ CapabilityBoundingSet = [ "" ];
+ DeviceAllow = [
+ "char-ttyUSB"
+ ];
+ DevicePolicy = "closed";
+ DynamicUser = true;
+ LockPersonality = true;
+ MemoryDenyWriteExecute = true;
+ RestrictAddressFamilies = [
+ "AF_INET"
+ "AF_INET6"
+ "AF_UNIX"
+ ];
+ RestrictNamespaces = true;
+ RestrictRealtime = true;
+ PrivateTmp = true;
+ PrivateUsers = true;
+ ProcSubset = "pid";
+ ProtectClock = true;
+ ProtectControlGroups= true;
+ ProtectHome = true;
+ ProtectHostname = true;
+ ProtectKernelLogs = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+ ProtectProc = "invisible";
+ SystemCallArchitectures = "native";
+ SystemCallFilter = [
+ "@system-service"
+ "~@privileged"
+ ];
+ UMask = "0077";
+ User = "evcc";
+ };
+ };
+ };
+
+ meta.buildDocsInSandbox = false;
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index d3dc8b9ca08a..7b1006ba6015 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -198,6 +198,7 @@ in {
etebase-server = handleTest ./etebase-server.nix {};
etesync-dav = handleTest ./etesync-dav.nix {};
extra-python-packages = handleTest ./extra-python-packages.nix {};
+ evcc = handleTest ./evcc.nix {};
fancontrol = handleTest ./fancontrol.nix {};
fcitx = handleTest ./fcitx {};
fenics = handleTest ./fenics.nix {};
diff --git a/nixos/tests/evcc.nix b/nixos/tests/evcc.nix
new file mode 100644
index 000000000000..0fc261142f78
--- /dev/null
+++ b/nixos/tests/evcc.nix
@@ -0,0 +1,96 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} :
+
+{
+ name = "evcc";
+ meta.maintainers = with lib.maintainers; [ hexa ];
+
+ nodes = {
+ machine = { config, ... }: {
+ services.evcc = {
+ enable = true;
+ settings = {
+ network = {
+ schema = "http";
+ host = "localhost";
+ port = 7070;
+ };
+
+ log = "info";
+
+ site = {
+ title = "NixOS Test";
+ meters = {
+ grid = "grid";
+ pv = "pv";
+ };
+ };
+
+ meters = [ {
+ type = "custom";
+ name = "grid";
+ power = {
+ source = "script";
+ cmd = "/bin/sh -c 'echo -4500'";
+ };
+ } {
+ type = "custom";
+ name = "pv";
+ power = {
+ source = "script";
+ cmd = "/bin/sh -c 'echo 7500'";
+ };
+ } ];
+
+ chargers = [ {
+ name = "dummy-charger";
+ type = "custom";
+ status = {
+ source = "script";
+ cmd = "/bin/sh -c 'echo charger status F'";
+ };
+ enabled = {
+ source = "script";
+ cmd = "/bin/sh -c 'echo charger enabled state false'";
+ };
+ enable = {
+ source = "script";
+ cmd = "/bin/sh -c 'echo set charger enabled state true'";
+ };
+ maxcurrent = {
+ source = "script";
+ cmd = "/bin/sh -c 'echo set charger max current 7200'";
+ };
+ } ];
+
+ loadpoints = [ {
+ title = "Dummy";
+ charger = "dummy-charger";
+ } ];
+ };
+ };
+ };
+ };
+
+ testScript = ''
+ start_all()
+
+ machine.wait_for_unit("evcc.service")
+ machine.wait_for_open_port(7070)
+
+ with subtest("Check package version propagates into frontend"):
+ machine.fail(
+ "curl --fail http://localhost:7070 | grep '0.0.1-alpha'"
+ )
+ machine.succeed(
+ "curl --fail http://localhost:7070 | grep '${pkgs.evcc.version}'"
+ )
+
+ with subtest("Check journal for errors"):
+ _, output = machine.execute("journalctl -o cat -u evcc.service")
+ assert "ERROR" not in output
+
+ with subtest("Check systemd hardening"):
+ _, output = machine.execute("systemd-analyze security evcc.service | grep -v '✓'")
+ machine.log(output)
+ '';
+})
diff --git a/pkgs/servers/home-automation/evcc/default.nix b/pkgs/servers/home-automation/evcc/default.nix
new file mode 100644
index 000000000000..a242f9d1cc3a
--- /dev/null
+++ b/pkgs/servers/home-automation/evcc/default.nix
@@ -0,0 +1,96 @@
+{ lib
+, buildGoModule
+, fetchFromGitHub
+, fetchNpmDeps
+, cacert
+, go
+, git
+, enumer
+, mockgen
+, nodejs
+, npmHooks
+, nix-update-script
+, nixosTests
+, stdenv
+}:
+
+buildGoModule rec {
+ pname = "evcc";
+ version = "0.107.1";
+
+ src = fetchFromGitHub {
+ owner = "evcc-io";
+ repo = pname;
+ rev = version;
+ hash = "sha256-Yu7ebZ6WkLpdvmg7H9A1Sveyu9SRuQ+78gFrCZrYhCU=";
+ };
+
+ vendorHash = "sha256-10W1BNHcdP77m7lJ/mc+jQeUigoUid3K0wI4bUm5y+s=";
+
+ npmDeps = fetchNpmDeps {
+ inherit src;
+ hash = "sha256-+l5LuxJAjrTvOL5XEQ4OIktdupSpn6IqrNX5x4MRmNw=";
+ };
+
+ nativeBuildInputs = [
+ nodejs
+ npmHooks.npmConfigHook
+ ];
+
+ overrideModAttrs = _: {
+ nativeBuildInputs = [
+ enumer
+ go
+ git
+ cacert
+ mockgen
+ ];
+
+ preBuild = ''
+ make assets
+ '';
+ };
+
+ tags = [
+ "release"
+ ];
+
+ ldflags = [
+ "-X github.com/evcc-io/evcc/server.Version=${version}"
+ "-X github.com/evcc-io/evcc/server.Commit=${src.rev}"
+ "-s"
+ "-w"
+ ];
+
+ npmInstallFlags = [
+ "--legacy-peer-deps"
+ ];
+
+ preBuild = ''
+ make ui
+ '';
+
+ doCheck = !stdenv.isDarwin; # tries to bind to local network, doesn't work in darwin sandbox
+
+ preCheck = ''
+ # requires network access
+ rm meter/template_test.go
+ '';
+
+ passthru = {
+ tests = {
+ inherit (nixosTests) evcc;
+ };
+ updateScript = nix-update-script {
+ attrPath = pname;
+ };
+ };
+
+ meta = with lib; {
+ description = "EV Charge Controller";
+ homepage = "https://evcc.io";
+ changelog = "https://github.com/andig/evcc/releases/tag/${version}";
+ license = licenses.mit;
+ maintainers = with maintainers; [ hexa ];
+ };
+}
diff --git a/pkgs/tools/misc/enumer/default.nix b/pkgs/tools/misc/enumer/default.nix
new file mode 100644
index 000000000000..c7e7818d09ef
--- /dev/null
+++ b/pkgs/tools/misc/enumer/default.nix
@@ -0,0 +1,26 @@
+{ lib
+, stdenv
+, fetchFromGitHub
+, buildGoModule
+}:
+
+buildGoModule rec {
+ pname = "enumer";
+ version = "1.5.7";
+
+ src = fetchFromGitHub {
+ owner = "dmarkham";
+ repo = "enumer";
+ rev = "refs/tags/v${version}";
+ hash = "sha256-2fVWrrWOiCtg7I3Lul2PgQ2u/qDEDioPSB61Tp0rfEo=";
+ };
+
+ vendorSha256 = "sha256-BmFv0ytRnjaB7z7Gb+38Fw2ObagnaFMnMhlejhaGxsk=";
+
+ meta = with lib; {
+ description = "Go tool to auto generate methods for enums";
+ homepage = "https://github.com/dmarkham/enumer";
+ license = licenses.bsd2;
+ maintainers = with maintainers; [ hexa ];
+ };
+}
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index bb7b2bfd548d..8a4a31df2778 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -480,6 +480,8 @@ with pkgs;
efficient-compression-tool = callPackage ../tools/compression/efficient-compression-tool { };
+ enumer = callPackage ../tools/misc/enumer { };
+
evans = callPackage ../development/tools/evans { };
expressvpn = callPackage ../applications/networking/expressvpn { };
@@ -6452,6 +6454,8 @@ with pkgs;
ettercap = callPackage ../applications/networking/sniffers/ettercap { };
+ evcc = callPackage ../servers/home-automation/evcc { };
+
eventstat = callPackage ../os-specific/linux/eventstat { };
evillimiter = python3Packages.callPackage ../tools/networking/evillimiter { };