From 11a34e510a43ba93d5a953022548486f22aca14d Mon Sep 17 00:00:00 2001 From: root Date: Sun, 16 Nov 2025 22:32:03 +0100 Subject: [PATCH] First implementation of OCPI 2.3 --- ocpi.bookinglocation.v23.ts | 141 ++++++++++ ocpi.cdrs.v23.ts | 105 ++++++++ ocpi.chargingprofiles.v23.ts | 64 +++++ ocpi.commands.v23.ts | 86 ++++++ ocpi.common.v23.ts | 27 ++ ocpi.credentials.v23.ts | 26 ++ ocpi.financialadviceconfirmation.v23.ts | 19 ++ ocpi.generate.ts | 156 ++++++++++- ocpi.hubclientinfo.v23.ts | 19 ++ ocpi.location.v23.ts | 344 ++++++++++++++++++++++++ ocpi.sessions.v23.ts | 59 ++++ ocpi.tariff.v23.ts | 83 ++++++ ocpi.terminal.v23.ts | 30 +++ ocpi.tokens.v23.ts | 34 +++ ocpi.versions.v23.ts | 40 +++ 15 files changed, 1232 insertions(+), 1 deletion(-) create mode 100644 ocpi.bookinglocation.v23.ts create mode 100644 ocpi.cdrs.v23.ts create mode 100644 ocpi.chargingprofiles.v23.ts create mode 100644 ocpi.commands.v23.ts create mode 100644 ocpi.common.v23.ts create mode 100644 ocpi.credentials.v23.ts create mode 100644 ocpi.financialadviceconfirmation.v23.ts create mode 100644 ocpi.hubclientinfo.v23.ts create mode 100644 ocpi.location.v23.ts create mode 100644 ocpi.sessions.v23.ts create mode 100644 ocpi.tariff.v23.ts create mode 100644 ocpi.terminal.v23.ts create mode 100644 ocpi.tokens.v23.ts create mode 100644 ocpi.versions.v23.ts diff --git a/ocpi.bookinglocation.v23.ts b/ocpi.bookinglocation.v23.ts new file mode 100644 index 0000000..5f6124d --- /dev/null +++ b/ocpi.bookinglocation.v23.ts @@ -0,0 +1,141 @@ +import { z } from "zod"; +import { ConnectorFormat, EvsePosition, VehicleType } from "./ocpi.location.v23"; +import { OcpiInterfaceRole } from "./ocpi.versions.v23"; +import { TokenType } from "./ocpi.common.v23"; + +export const BookableParkingOptions = z.object({ + evse_position: EvsePosition.nullish(), + vehicle_types: z.array(VehicleType).nonempty(), + format: ConnectorFormat, + max_vehicle_weight: z.number().nonnegative().nullish(), + max_vehicle_height: z.number().nonnegative().nullish(), + max_vehicle_length: z.number().nonnegative().nullish(), + max_vehicle_width: z.number().nonnegative().nullish(), + parking_space_length: z.number().nonnegative().nullish(), + parking_space_width: z.number().nonnegative().nullish(), + dangerous_goods_allowed: z.boolean().nullish(), + drive_through: z.boolean().nullish(), + restricted_to_type: z.boolean().nullish(), + refrigeration_outlet: z.boolean().nullish(), +}); + +export const CanceledReason = z.enum([ + "POWER_OUTAGE", + "BROKEN_CHARGER", + "FULL", + "BLOCKED", + "TRAFFIC", + "BROKEN_VEHICLE", + "NO_CANCELED", + "UNKNOWN", +]); + +export const Cancellation = z.object({ + cancellation_reason: CanceledReason, + who_canceled: OcpiInterfaceRole, +}); + +export const LocationAccess = z.enum([ + "OPEN", + "TOKEN", + "LICENSE_PLATE", + "ACCESS_CODE", + "INTERCOM", + "PARKING_TICKET", +]); + +export const AccessMethod = z.object({ + location_access: LocationAccess, + value: z.string().nullish(), +}); + +export const ReservationRequestStatus = z.enum([ + "PENDING", + "ACCEPTED", + "DECLINED", + "FAILED", +]); + +export const BookingToken = z.object({ + country_code: z.string().max(2), + party_id: z.string().max(3), + uid: z.string().max(36).nullish(), + type: TokenType, + contract_id: z.string().max(36).nullish(), +}); + +export const Timeslot = z.object({ + start_from: z.date(), + end_from: z.date(), + min_power: z.number().nonnegative().nullish(), + max_power: z.number().nonnegative().nullish(), + green_energy_support: z.boolean().nullish(), +}); + +export const BookingRequest = z.object({ + country_code: z.string().max(2), + party_id: z.string().max(3), + request_id: z.string().max(36), + bookable_parking_option: BookableParkingOptions.nullish(), + location_id: z.string().max(36), + evse_uid: z.string().max(36).nullish(), + connector_id: z.string().max(36).nullish(), + tokens: z.array(BookingToken).nullish(), + period: Timeslot, + authorization_reference: z.string().max(36), + power_required: z.number().int().nullish(), + cancelled: Cancellation.nullish(), +}); + +export const BookingRequestStatus = z.object({ + request_status: ReservationRequestStatus, + booking_request: BookingRequest, + request_received: z.date(), +}); + +export const Bookable = z.object({ + reservation_required: z.boolean(), + ad_hoc: z.number().nonnegative().nullish(), +}); + +export const BookingTerms = z.object({ + RFID_auth_required: z.boolean().nullish(), + token_groups_supported: z.boolean().nullish(), + remote_auth_supported: z.array(LocationAccess).nonempty(), + supported_access_methods: z.number().nonnegative().nullish(), + change_until_minutes: z.number().nonnegative(), + cancel_until_minutes: z.number().nonnegative(), + change_not_allowed: z.boolean().nullish(), + early_start_allowed: z.boolean().nullish(), + early_start_time: z.number().nonnegative().nullish(), + noshow_timeout: z.number().nonnegative().nullish(), + noshow_fee: z.boolean().nullish(), + late_stop_allowed: z.boolean().nullish(), + late_stop_time: z.number().nonnegative().nullish(), + overlapping_bookings_allowed: z.boolean().nullish(), + booking_terms: z.string().url().nullish(), +}); + +export const Calendar = z.object({ + id: z.string().max(36), + begin_from: z.date(), + end_before: z.date(), + step_size: z.number().int().nullish(), + available_timeslots: z.array(Timeslot).nonempty(), + last_updated: z.date(), +}); + +export const BookingLocation = z.object({ + country_code: z.string().max(2), + party_id: z.string().max(3), + id: z.string().max(36), + location_id: z.string().max(36), + evse_uid: z.string().max(36).nullish(), + connector_id: z.string().max(36).nullish(), + bookable_parking_options: z.array(BookableParkingOptions).nullish(), + bookable: Bookable.nullish(), + tariff_id: z.string().max(36).nullish(), + booking_terms: z.array(BookingTerms).nullish(), + calendars: z.array(Calendar).nullish(), + last_updated: z.date(), +}); \ No newline at end of file diff --git a/ocpi.cdrs.v23.ts b/ocpi.cdrs.v23.ts new file mode 100644 index 0000000..b4df584 --- /dev/null +++ b/ocpi.cdrs.v23.ts @@ -0,0 +1,105 @@ +import { z } from "zod"; + +import { Price } from "./ocpi.common.v23"; +import { TokenType } from "./ocpi.common.v23"; +import { ConnectorFormat, ConnectorType, GeoLocation, PowerType } from "./ocpi.location.v23"; +import { Tariff } from "./ocpi.tariff.v221"; + +export const AuthMethod = z.enum(["AUTH_REQUEST", "COMMAND", "WHITELIST"]); + +const CdrDimensionType = z.enum([ + "ENERGY", + "MAX_CURRENT", + "MIN_CURRENT", + "MAX_POWER", + "MIN_POWER", + "PARKING_TIME", + "RESERVATION_TIME", + "TIME", +]); + +export const CdrDimension = z.object({ + type: CdrDimensionType, + volume: z.number(), +}); + +export const CdrToken = z.object({ + uid: z.string().max(36), + type: TokenType.nullish(), + contract_id: z.string().max(36), + country_code: z.string().max(2), + party_id: z.string().max(3), +}) + +export const CdrLocation = z.object({ + id: z.string().max(36), + name: z.string().max(255).nullish(), + address: z.string().max(45), + city: z.string().max(45), + postal_code: z.string().max(10).nullish(), + state: z.string().max(20).nullish(), + country: z.string().length(3), + coordinates: GeoLocation, + evse_uid: z.string().max(36), + evse_id: z.string().max(48).regex(/^(([A-Z]{2}\*?[A-Z0-9]{3}\*?E[A-Z0-9\*]{1,30})|(\+?[0-9]{1,3}\*[0-9]{3}\*[0-9\*]{1,32}))$/), + connector_id: z.string().max(36), + connector_standard: ConnectorType, + connector_format: ConnectorFormat, + connector_power_type: PowerType, +}) + +export const ChargingPeriod = z.object({ + start_date_time: z.date(), + dimensions: z.array(CdrDimension).nonempty(), + tariff_id: z.string().max(36).nullish(), +}) + +export const SignedValue = z.object({ + nature: z.string().max(32), + plain_data: z.string().max(512), + signed_data: z.string().max(5000) +}); + +export const SignedData = z.object({ + encoding_method: z.string().max(36), + encoding_method_version: z.number().int().nullish(), + public_key: z.string().max(512).nullish(), + signed_values: z.array(SignedValue).nonempty(), + url: z.string().max(512).nullish() +}); + +export const Cdr = z.object({ + country_code: z.string().length(2), + party_id: z.string().max(3), + id: z.string().max(39), + start_date_time: z.date(), + end_date_time: z.date(), + booking_id: z.string().max(36).nullish(), + session_id: z.string().max(36).nullish(), + cdr_token: CdrToken, + auth_method: AuthMethod, + authorization_reference: z.string().max(36).nullish(), + cdr_location: CdrLocation, + meter_id: z.string().max(255).nullish(), + currency: z.string().length(3), + tariffs: z.array(Tariff).nullish(), + charging_periods: z.array(ChargingPeriod).nonempty(), + signed_data: SignedData.nullish(), + total_cost: Price, + total_fixed_cost: Price.nullish(), + total_energy: z.number().nonnegative(), + total_energy_cost: Price.nullish(), + total_time: z.number().nonnegative(), + total_time_cost: Price.nullish(), + total_parking_time: z.number().nonnegative().nullish(), + total_parking_cost: Price.nullish(), + total_reservation_cost: Price.nullish(), + remark: z.string().max(255).nullish(), + invoice_reference_id: z.string().max(39).nullish(), + credit: z.boolean().nullish(), + credit_reference_id: z.string().max(39).nullish(), + home_charging_compensation: z.boolean().nullish(), + last_updated: z.date(), +}); + +export const Cdrs = z.array(Cdr); diff --git a/ocpi.chargingprofiles.v23.ts b/ocpi.chargingprofiles.v23.ts new file mode 100644 index 0000000..aec3000 --- /dev/null +++ b/ocpi.chargingprofiles.v23.ts @@ -0,0 +1,64 @@ +import { z } from "zod"; + +const ChargingProfileResponseType = z.enum([ + "ACCEPTED", + "NOT_SUPPORTED", + "REJECTED", + "TOO_OFTEN", + "UNKNOWN_SESSION" +]); + +const ChargingRateUnit = z.enum([ + "W", + "A" +]); + +const ChargingProfilePeriod = z.object({ + start_period: z.number().int().nonnegative(), + limit: z.number().multipleOf(0.1).nonnegative(), +}); + +export const ChargingProfile = z.object({ + start_date_time: z.date().nullish(), + duration: z.number().int().nonnegative().nullish(), + charging_rate_unit: ChargingRateUnit, + min_charging_rate: z.number().multipleOf(0.1).nonnegative().nullish(), + charging_profile_period: z.array(ChargingProfilePeriod).nullish() +}); + +const ChargingProfileResultType = z.enum([ + "ACCEPTED", + "REJECTED", + "UNKNOWN" +]); + +export const ActiveChargingProfile = z.object({ + start_date_time: z.date(), + charging_profile: ChargingProfile +}); + +export const SetChargingProfile = z.object({ + charging_profile: ChargingProfile, + response_url: z.string().url(), +}); + +export const ChargingProfileResponse = z.object({ + result: ChargingProfileResponseType, + timeout: z.number().int().nonnegative(), +}); + +export const ActiveChargingProfileResult = z.object({ + result: ChargingProfileResultType, + profile: ActiveChargingProfile.nullish(), +}); + +export const ChargingProfileResult = z.object({ + result: ChargingProfileResultType, +}); + +export const ClearProfileResult = z.object({ + result: ChargingProfileResultType, +}); + + + diff --git a/ocpi.commands.v23.ts b/ocpi.commands.v23.ts new file mode 100644 index 0000000..b795cf9 --- /dev/null +++ b/ocpi.commands.v23.ts @@ -0,0 +1,86 @@ +import { z } from "zod"; +import { + DisplayText, +} from "./ocpi.common.v221"; +import { + Token, +} from "./ocpi.tokens.v221"; +import { createOpenEnum } from "./ocpi.common.v23"; + + +export const ReserveNowCommand = z.object({ + response_url: z.string().url(), + token: Token, + expiry_date: z.date(), + reservation_id: z.string().max(36), + location_id: z.string().max(36), + evse_uid: z.string().max(36).nullish(), + authorization_reference: z.string().max(36).nullish(), +}); + +export const CancelReservationCommand = z.object({ + response_url: z.string().url(), + reservation_id: z.string().max(36), +}); + +export const StartSessionCommand = z.object({ + response_url: z.string().url(), + token: Token, + location_id: z.string().max(36), + evse_uid: z.string().max(36).nullish(), + connector_id: z.string().max(36).nullish(), + authorization_reference: z.string().max(36).nullish(), +}); + +export const StopSessionCommand = z.object({ + response_url: z.string().url(), + session_id: z.string().max(36), +}); + +export const UnlockConnectorCommand = z.object({ + response_url: z.string().url(), + location_id: z.string().max(36), + evse_uid: z.string().max(36), + connector_id: z.string().max(36), +}); + +const CommandResponseType = z.enum([ + "NOT_SUPPORTED", + "REJECTED", + "ACCEPTED", + "UNKNOWN_SESSION", +]); + +export const CommandResponse = z.object({ + result: CommandResponseType, + timeout: z.number().int().nonnegative(), + message: z.array(DisplayText).nullish(), +}); + +const CommandResultType = z.enum([ + "ACCEPTED", + "CANCELED_RESERVATION", + "EVSE_OCCUPIED", + "EVSE_INOPERATIVE", + "FAILED", + "NOT_SUPPORTED", + "REJECTED", + "TIMEOUT", + "UNKNOWN_RESERVATION", +]); + +export const CommandResult = z.object({ + result: CommandResultType, + message: z.array(DisplayText).nullish(), +}); + +const CommandType = createOpenEnum([ + "CANCEL_RESERVATION", + "RESERVE_NOW", + "START_SESSION", + "STOP_SESSION", + "UNLOCK_CONNECTOR" +]); + + + diff --git a/ocpi.common.v23.ts b/ocpi.common.v23.ts new file mode 100644 index 0000000..3b6c883 --- /dev/null +++ b/ocpi.common.v23.ts @@ -0,0 +1,27 @@ +import { z } from "zod"; + +export const createOpenEnum = (knownValues: readonly T[]) => { + return z.union([ + z.enum(knownValues as [T, ...T[]]), + z.string().trim().min(1), + ]); +}; + +export const TaxAmount = z.object({ + name: z.string(), + account_number: z.string().nullish(), + percentage: z.number().nonnegative().nullish(), + amount: z.number(), +}); + +export const Price = z.object({ + before_taxes: z.number().nonnegative(), + taxes: z.array(TaxAmount).nullish(), +}); + +export const TokenType = createOpenEnum(["AD_HOC_USER", "APP_USER", "EMAID", "LICENSE_PLATE", "OTHER", "RFID"]); + +export const DisplayText = z.object({ + language: z.string().length(2), + text: z.string().max(512), +}); \ No newline at end of file diff --git a/ocpi.credentials.v23.ts b/ocpi.credentials.v23.ts new file mode 100644 index 0000000..3be0e22 --- /dev/null +++ b/ocpi.credentials.v23.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import { BusinessDetails } from "./ocpi.common"; + +export const Role = z.enum([ + "CPO", + "EMSP", + "HUB", + "NAP", + "NSP", + "OTHER", + "SCSP" +]) + +export const CredentialsRole = z.object({ + role: Role, + business_details: BusinessDetails, + party_id: z.string().length(3), + country_code: z.string().length(2), +}) + +export const OcpiCredentials = z.object({ + token: z.string().max(64), + url: z.string().url(), + hub_party_id: z.string().max(5).nullish(), + roles: z.array(CredentialsRole).nonempty(), +}); diff --git a/ocpi.financialadviceconfirmation.v23.ts b/ocpi.financialadviceconfirmation.v23.ts new file mode 100644 index 0000000..c0223d9 --- /dev/null +++ b/ocpi.financialadviceconfirmation.v23.ts @@ -0,0 +1,19 @@ +import { z } from "zod"; +import { Price } from "./ocpi.common.v23"; + +export const InvoiceCreator = z.enum(["CPO", "PTP"]); + +export const CaptureStatusCode = z.enum(["SUCCESS", "PARTIAL_SUCCESS", "FAILED"]); + +export const FinancialAdviceConfirmation = z.object({ + id: z.string().max(36), + authorization_reference: z.string().max(36), + total_costs: Price, + currency: z.string().max(3), + eft_data: z.array(z.string().min(1).max(255)).nonempty(), + capture_status_code: CaptureStatusCode, + capture_status_message: z.string().min(1).max(255).nullish(), + last_updated: z.date(), +}); + +export const FinancialAdviceConfirmations = z.array(FinancialAdviceConfirmation); \ No newline at end of file diff --git a/ocpi.generate.ts b/ocpi.generate.ts index 1c13c31..84a7f81 100644 --- a/ocpi.generate.ts +++ b/ocpi.generate.ts @@ -9,6 +9,10 @@ import { Token as TokenV221, Tokens as TokensV221 } from "./ocpi.tokens.v221"; +import { + Token as TokenV23, + Tokens as TokensV23 +} from "./ocpi.tokens.v23"; import { Connector as ConnectorV22, Evse as EvseV22, @@ -21,6 +25,12 @@ import { Location as LocationV221, Locations as LocationsV221 } from "./ocpi.location.v221"; +import { + Connector as ConnectorV23, + Evse as EvseV23, + Location as LocationV23, + Locations as LocationsV23 +} from "./ocpi.location.v23"; import { Session as SessionV22, Sessions as SessionsV22, @@ -29,6 +39,10 @@ import { Session as SessionV221, Sessions as SessionsV221, } from "./ocpi.sessions.v221" +import { + Session as SessionV23, + Sessions as SessionsV23, +} from "./ocpi.sessions.v23" import { Cdr as CdrV22, Cdrs as CdrsV22, @@ -37,6 +51,10 @@ import { Cdr as CdrV221, Cdrs as CdrsV221, } from "./ocpi.cdrs.v221"; +import { + Cdr as CdrV23, + Cdrs as CdrsV23, +} from "./ocpi.cdrs.v23"; import { Tariff as TariffV22, Tariffs as TariffsV22, @@ -45,6 +63,10 @@ import { Tariff as TariffV221, Tariffs as TariffsV221, } from "./ocpi.tariff.v221"; +import { + Tariff as TariffV23, + Tariffs as TariffsV23, +} from "./ocpi.tariff.v23"; import { OcpiEmpty, OcpiErrorResponse, @@ -55,9 +77,11 @@ import { ZodTypeAny } from "zod"; import { OcpiVersionDetails, OcpiVersions } from "./ocpi.version"; import { OcpiVersionDetails as OcpiVersionDetailsV22, OcpiVersions as OcpiVersionsV22 } from "./ocpi.versions.v22"; import { OcpiVersionDetails as OcpiVersionDetailsV221, OcpiVersions as OcpiVersionsV221 } from "./ocpi.versions.v221"; +import { OcpiVersionDetails as OcpiVersionDetailsV23, OcpiVersions as OcpiVersionsV23 } from "./ocpi.versions.v23"; import { OcpiCredentials } from "./ocpi.credentials"; import { OcpiCredentials as OcpiCredentialsV22 } from "./ocpi.credentials.v22"; import { OcpiCredentials as OcpiCredentialsV221 } from "./ocpi.credentials.v221"; +import { OcpiCredentials as OcpiCredentialsV23 } from "./ocpi.credentials.v23"; import { Cdr, Cdrs } from "./ocpi.cdr"; import { Tariff, Tariffs } from "./ocpi.tariff"; import { Token, Tokens } from "./ocpi.tokens"; @@ -86,6 +110,15 @@ import { StopSessionCommand as StopSessionCommandV221, UnlockConnectorCommand as UnlockConnectorCommandV221 } from "./ocpi.commands.v221"; +import { + CommandResponse as CommandResponseV23, + CommandResult as CommandResultV23, + ReserveNowCommand as ReserveNowCommandV23, + CancelReservationCommand as CancelReservationCommandV23, + StartSessionCommand as StartSessionCommandV23, + StopSessionCommand as StopSessionCommandV23, + UnlockConnectorCommand as UnlockConnectorCommandV23 +} from "./ocpi.commands.v23"; import { ocpiDeepPartial } from "./ocpi.utils"; @@ -104,12 +137,35 @@ import { ClearProfileResult as ClearProfileResultV221, SetChargingProfile as SetChargingProfileV221 } from "./ocpi.chargingprofiles.v221"; +import { + ActiveChargingProfile as ActiveChargingProfileV23, + ActiveChargingProfileResult as ActiveChargingProfileResultV23, + ChargingProfileResponse as ChargingProfileResponseV23, + ChargingProfileResult as ChargingProfileResultV23, + ClearProfileResult as ClearProfileResultV23, + SetChargingProfile as SetChargingProfileV23 +} from "./ocpi.chargingprofiles.v23"; import {ClientInfo, ClientInfos} from "./ocpi.hubclientinfo.v22"; import { ClientInfo as ClientInfoV221, ClientInfos as ClientInfosV221 } from "./ocpi.hubclientinfo.v221"; +import { + ClientInfo as ClientInfoV23, + ClientInfos as ClientInfosV23 +} from "./ocpi.hubclientinfo.v23"; + +import { + Terminal as TerminalV23, + Terminals as TerminalsV23, + TerminalWithOptionalId as TerminalWithOptionalIdV23, +} from "./ocpi.terminal.v23"; +import { + FinancialAdviceConfirmation as FinancialAdviceConfirmationV23 , + FinancialAdviceConfirmations as FinancialAdviceConfirmationsV23 +} from "./ocpi.financialadviceconfirmation.v23"; + const OCPI_SCHEMA_DIR = ".ocpi-schema"; @@ -158,6 +214,17 @@ saveSchema( ocpiSuccessResponse(OcpiVersionDetailsV221), ); +/* Versions 2.3 */ +saveSchema("ocpi.2_3.versions", OcpiVersionsV23); +saveSchema("ocpi.2_3.versions.response", ocpiSuccessResponse(OcpiVersionsV23)); + +/* Version details 2.3 */ +saveSchema("ocpi.2_3.version_details", OcpiVersionDetailsV23); +saveSchema( + "ocpi.2_3.version_details.response", + ocpiSuccessResponse(OcpiVersionDetailsV23), +); + /* Credentials */ saveSchema("ocpi.2_1_1.credentials", OcpiCredentials); saveSchema( @@ -179,6 +246,13 @@ saveSchema( ocpiSuccessResponse(OcpiCredentialsV221), ); +/* Credentials 2.3 */ +saveSchema("ocpi.2_3.credentials", OcpiCredentialsV23); +saveSchema( + "ocpi.2_3.credentials.response", + ocpiSuccessResponse(OcpiCredentialsV23), +); + /* Locations */ saveSchema("ocpi.2_1_1.locations", Locations); saveSchema("ocpi.2_1_1.locations.response", ocpiSuccessResponse(Locations)); @@ -218,6 +292,19 @@ saveSchema("ocpi.2_2_1.connector", ConnectorV221); saveSchema("ocpi.2_2_1.connector.partial", ocpiDeepPartial(ConnectorV221)); saveSchema("ocpi.2_2_1.connector.response", ocpiSuccessResponse(ConnectorV221)); +/* Locations 2.3 */ +saveSchema("ocpi.2_3.locations", LocationsV23); +saveSchema("ocpi.2_3.locations.response", ocpiSuccessResponse(LocationsV23)); +saveSchema("ocpi.2_3.location", LocationV23); +saveSchema("ocpi.2_3.location.partial", ocpiDeepPartial(LocationV23)); +saveSchema("ocpi.2_3.location.response", ocpiSuccessResponse(LocationV23)); +saveSchema("ocpi.2_3.evse", EvseV23); +saveSchema("ocpi.2_3.evse.partial", ocpiDeepPartial(EvseV23)); +saveSchema("ocpi.2_3.evse.response", ocpiSuccessResponse(EvseV23)); +saveSchema("ocpi.2_3.connector", ConnectorV23); +saveSchema("ocpi.2_3.connector.partial", ocpiDeepPartial(ConnectorV23)); +saveSchema("ocpi.2_3.connector.response", ocpiSuccessResponse(ConnectorV23)); + /* Sessions */ saveSchema("ocpi.2_1_1.sessions", Sessions); saveSchema("ocpi.2_1_1.sessions.response", ocpiSuccessResponse(Sessions)); @@ -239,6 +326,13 @@ saveSchema("ocpi.2_2_1.session", SessionV221); saveSchema("ocpi.2_2_1.session.partial", ocpiDeepPartial(SessionV221)); saveSchema("ocpi.2_2_1.session.response", ocpiSuccessResponse(SessionV221)); +/* Sessions 2.3 */ +saveSchema("ocpi.2_3.sessions", SessionsV23); +saveSchema("ocpi.2_3.sessions.response", ocpiSuccessResponse(SessionsV23)); +saveSchema("ocpi.2_3.session", SessionV23); +saveSchema("ocpi.2_3.session.partial", ocpiDeepPartial(SessionV23)); +saveSchema("ocpi.2_3.session.response", ocpiSuccessResponse(SessionV23)); + /* CDRs */ saveSchema("ocpi.2_1_1.cdrs", Cdrs); saveSchema("ocpi.2_1_1.cdrs.response", ocpiSuccessResponse(Cdrs)); @@ -260,6 +354,13 @@ saveSchema("ocpi.2_2_1.cdr", CdrV221); saveSchema("ocpi.2_2_1.cdr.partial", ocpiDeepPartial(CdrV221)); saveSchema("ocpi.2_2_1.cdr.response", ocpiSuccessResponse(CdrV221)); +/* CDRs 2.3 */ +saveSchema("ocpi.2_3.cdrs", CdrsV23); +saveSchema("ocpi.2_3.cdrs.response", ocpiSuccessResponse(CdrsV23)); +saveSchema("ocpi.2_3.cdr", CdrV23); +saveSchema("ocpi.2_3.cdr.partial", ocpiDeepPartial(CdrV23)); +saveSchema("ocpi.2_3.cdr.response", ocpiSuccessResponse(CdrV23)); + /* Charging Profiles 2.2 */ saveSchema("ocpi.2_2.chargingprofiles", SetChargingProfile); saveSchema("ocpi.2_2.chargingprofiles.active", ActiveChargingProfile); @@ -276,6 +377,14 @@ saveSchema("ocpi.2_2_1.chargingprofiles.active_charging_profile.result", ActiveC saveSchema("ocpi.2_2_1.chargingprofiles.charging_profile.result", ChargingProfileResultV221); saveSchema("ocpi.2_2_1.chargingprofiles.clear_profile.result", ClearProfileResultV221); +/* Charging Profiles 2.3 */ +saveSchema("ocpi.2_3.chargingprofiles", SetChargingProfileV23); +saveSchema("ocpi.2_3.chargingprofiles.active", ActiveChargingProfileV23); +saveSchema("ocpi.2_3.chargingprofiles.response", ocpiSuccessResponse(ChargingProfileResponseV23)); +saveSchema("ocpi.2_3.chargingprofiles.active_charging_profile.result", ActiveChargingProfileResultV23); +saveSchema("ocpi.2_3.chargingprofiles.charging_profile.result", ChargingProfileResultV23); +saveSchema("ocpi.2_3.chargingprofiles.clear_profile.result", ClearProfileResultV23); + /* Hub Client Info 2.2 */ saveSchema("ocpi.2_2.hubclientinfo", ClientInfo); saveSchema("ocpi.2_2.hubclientinfo.response", ocpiSuccessResponse(ClientInfo)); @@ -286,6 +395,11 @@ saveSchema("ocpi.2_2_1.hubclientinfo", ClientInfoV221); saveSchema("ocpi.2_2_1.hubclientinfo.response", ocpiSuccessResponse(ClientInfoV221)); saveSchema("ocpi.2_2_1.hubclientinfos", ocpiSuccessResponse(ClientInfosV221)); +/* Hub Client Info 2.3 */ +saveSchema("ocpi.2_3.hubclientinfo", ClientInfoV23); +saveSchema("ocpi.2_3.hubclientinfo.response", ocpiSuccessResponse(ClientInfoV23)); +saveSchema("ocpi.2_3.hubclientinfos", ocpiSuccessResponse(ClientInfosV23)); + /* Tariffs */ saveSchema("ocpi.2_1_1.tariffs", Tariffs); saveSchema("ocpi.2_1_1.tariffs.response", ocpiSuccessResponse(Tariffs)); @@ -307,6 +421,13 @@ saveSchema("ocpi.2_2_1.tariff", TariffV221); saveSchema("ocpi.2_2_1.tariff.partial", ocpiDeepPartial(TariffV221)); saveSchema("ocpi.2_2_1.tariff.response", ocpiSuccessResponse(TariffV221)); +/* Tariffs 2.3 */ +saveSchema("ocpi.2_3.tariffs", TariffsV23); +saveSchema("ocpi.2_3.tariffs.response", ocpiSuccessResponse(TariffsV23)); +saveSchema("ocpi.2_3.tariff", TariffV23); +saveSchema("ocpi.2_3.tariff.partial", ocpiDeepPartial(TariffV23)); +saveSchema("ocpi.2_3.tariff.response", ocpiSuccessResponse(TariffV23)); + /* Tokens */ saveSchema("ocpi.2_1_1.tokens", Tokens); saveSchema("ocpi.2_1_1.tokens.response", ocpiSuccessResponse(Tokens)); @@ -321,13 +442,21 @@ saveSchema("ocpi.2_2.token", TokenV22); saveSchema("ocpi.2_2.token.partial", ocpiDeepPartial(TokenV22)); saveSchema("ocpi.2_2.token.response", ocpiSuccessResponse(TokenV22)); -/* Tokens 2.2 */ +/* Tokens 2.2.1 */ saveSchema("ocpi.2_2_1.tokens", TokensV221); saveSchema("ocpi.2_2_1.tokens.response", ocpiSuccessResponse(TokensV221)); saveSchema("ocpi.2_2_1.token", TokenV221); saveSchema("ocpi.2_2_1.token.partial", ocpiDeepPartial(TokenV221)); saveSchema("ocpi.2_2_1.token.response", ocpiSuccessResponse(TokenV221)); + +/* Tokens 2.3 */ +saveSchema("ocpi.2_3.tokens", TokensV23); +saveSchema("ocpi.2_3.tokens.response", ocpiSuccessResponse(TokensV23)); +saveSchema("ocpi.2_3.token", TokenV23); +saveSchema("ocpi.2_3.token.partial", ocpiDeepPartial(TokenV23)); +saveSchema("ocpi.2_3.token.response", ocpiSuccessResponse(TokenV23)); + /* Commands */ saveSchema("ocpi.2_1_1.commands.reserve_now.request", ReserveNowCommand); saveSchema("ocpi.2_1_1.commands.start_session.request", StartSessionCommand); @@ -359,5 +488,30 @@ saveSchema("ocpi.2_2_1.commands.unlock_connector.request", UnlockConnectorComman saveSchema("ocpi.2_2_1.commands.response", ocpiSuccessResponse(CommandResponseV221)); saveSchema("ocpi.2_2_1.commands.result", CommandResultV221); +/* Commands 2.3 */ +saveSchema("ocpi.2_3.commands.reserve_now.request", ReserveNowCommandV23); +saveSchema("ocpi.2_3.commands.cancel_reservation.request", CancelReservationCommandV23); +saveSchema("ocpi.2_3.commands.start_session.request", StartSessionCommandV23); +saveSchema("ocpi.2_3.commands.stop_session.request", StopSessionCommandV23); +saveSchema("ocpi.2_3.commands.unlock_connector.request", UnlockConnectorCommandV23); +saveSchema("ocpi.2_3.commands.response", ocpiSuccessResponse(CommandResponseV23)); +saveSchema("ocpi.2_3.commands.result", CommandResultV23); + +/* Payments 2.3 */ +saveSchema("ocpi.2_3.payments.terminals", TerminalsV23); +saveSchema("ocpi.2_3.payments.terminals.response", ocpiSuccessResponse(TerminalsV23)); +saveSchema("ocpi.2_3.payments.terminal", TerminalV23); +saveSchema("ocpi.2_3.payments.terminal_with_optional_id", TerminalWithOptionalIdV23); +saveSchema("ocpi.2_3.payments.terminal.partial", ocpiDeepPartial(TerminalV23)); +saveSchema("ocpi.2_3.payments.terminal.response", ocpiSuccessResponse(TerminalV23)); + +saveSchema("ocpi.2_3.payments.financial_advice_confirmations", FinancialAdviceConfirmationsV23); +saveSchema("ocpi.2_3.payments.financial_advice_confirmations.response", ocpiSuccessResponse(FinancialAdviceConfirmationsV23)); +saveSchema("ocpi.2_3.payments.financial_advice_confirmation", FinancialAdviceConfirmationV23); +saveSchema("ocpi.2_3.payments.financial_advice_confirmation.partial", ocpiDeepPartial(FinancialAdviceConfirmationV23)); +saveSchema("ocpi.2_3.payments.financial_advice_confirmation.response", ocpiSuccessResponse(FinancialAdviceConfirmationV23)); + + + /* Errors */ saveSchema("ocpi.error", OcpiErrorResponse); diff --git a/ocpi.hubclientinfo.v23.ts b/ocpi.hubclientinfo.v23.ts new file mode 100644 index 0000000..b477d02 --- /dev/null +++ b/ocpi.hubclientinfo.v23.ts @@ -0,0 +1,19 @@ +import {z} from "zod"; +import {Role} from "./ocpi.credentials.v23"; + +const ConnectionStatus = z.enum([ + "CONNECTED", + "OFFLINE", + "PLANNED", + "SUSPENDED" +]); + +export const ClientInfo = z.object({ + party_id: z.string().max(3), + country_code: z.string().length(2), + role: Role, + status: ConnectionStatus, + last_updated: z.date(), +}); + +export const ClientInfos = z.array(ClientInfo).nullish(); diff --git a/ocpi.location.v23.ts b/ocpi.location.v23.ts new file mode 100644 index 0000000..c48717a --- /dev/null +++ b/ocpi.location.v23.ts @@ -0,0 +1,344 @@ +import { z } from "zod"; +import { + BusinessDetails, + DisplayText, + Image, +} from "./ocpi.common"; +import { TokenType } from "./ocpi.common.v221"; +import { createOpenEnum } from "./ocpi.common.v23"; + +export const GeoLocation = z.object({ + latitude: z + .string() + .max(10) + .regex(/-?[0-9]{1,2}\.[0-9]{5,7}/), + longitude: z + .string() + .max(11) + .regex(/-?[0-9]{1,3}\.[0-9]{5,7}/), +}); + +export const ConnectorFormat = z.enum(["SOCKET", "CABLE"]); +export const PowerType = z.enum([ + "AC_1_PHASE", + "AC_2_PHASE", + "AC_2_PHASE_SPLIT", + "AC_3_PHASE", + "DC" +]); +export const ConnectorType = createOpenEnum([ + "CHADEMO", + "CHAOJI", + "DOMESTIC_A", + "DOMESTIC_B", + "DOMESTIC_C", + "DOMESTIC_D", + "DOMESTIC_E", + "DOMESTIC_F", + "DOMESTIC_G", + "DOMESTIC_H", + "DOMESTIC_I", + "DOMESTIC_J", + "DOMESTIC_K", + "DOMESTIC_L", + "DOMESTIC_M", + "DOMESTIC_N", + "DOMESTIC_O", + "GBT_AC", + "GBT_DC", + "IEC_60309_2_single_16", + "IEC_60309_2_three_16", + "IEC_60309_2_three_32", + "IEC_60309_2_three_64", + "IEC_62196_T1", + "IEC_62196_T1_COMBO", + "IEC_62196_T2", + "IEC_62196_T2_COMBO", + "IEC_62196_T3A", + "IEC_62196_T3C", + "MCS", + "NEMA_5_20", + "NEMA_6_30", + "NEMA_6_50", + "NEMA_10_30", + "NEMA_10_50", + "NEMA_14_30", + "NEMA_14_50", + "PANTOGRAPH_BOTTOM_UP", + "PANTOGRAPH_TOP_DOWN", + "SAE_J3400", + "TESLA_R", +]); +export const ConnectorCapability = createOpenEnum([ + "ISO_15118_2_PLUG_AND_CHARGE", + "ISO_15118_20_PLUG_AND_CHARGE", +]); + +export const Connector = z.object({ + id: z.string().max(36), + standard: ConnectorType, + format: ConnectorFormat, + power_type: PowerType, + max_voltage: z.number().int(), + max_amperage: z.number().int(), + max_electric_power: z.number().int().nullish(), + tariff_ids: z.array(z.string().max(36).nullish()).nullish(), + terms_and_conditions: z.string().url().nullish(), + capabilities: z.array(ConnectorCapability).nullish(), + last_updated: z.date(), +}); + +const EvseStatus = z.enum([ + "AVAILABLE", + "BLOCKED", + "CHARGING", + "INOPERATIVE", + "OUTOFORDER", + "PLANNED", + "REMOVED", + "RESERVED", + "UNKNOWN", +]); + +const StatusSchedule = z.object({ + period_begin: z.date(), + period_end: z.date().nullable(), + status: EvseStatus, +}); + +const Capability = createOpenEnum([ + "CHARGING_PROFILE_CAPABLE", + "CHARGING_PREFERENCES_CAPABLE", + "CHIP_CARD_SUPPORT", + "CONTACTLESS_CARD_SUPPORT", + "CREDIT_CARD_PAYABLE", + "DEBIT_CARD_PAYABLE", + "PED_TERMINAL", + "REMOTE_START_STOP_CAPABLE", + "RESERVABLE", + "RFID_READER", + "START_SESSION_CONNECTOR_REQUIRED", + "TOKEN_GROUP_CAPABLE", + "UNLOCK_CAPABLE", +]); + +const ParkingRestriction = createOpenEnum([ + "CUSTOMERS", + "DISABLED", + "EMPLOYEES", + "EV_ONLY", + "MOTORCYCLES", + "PLUGGED", + "TAXIS", +]); + +export const EvsePosition = z.enum([ + "LEFT", + "RIGHT", + "CENTER", +]); + +const EvseParking = z.object({ + parking_id: z.string().max(36), + evse_position: EvsePosition.nullish(), +}); + +export const Evse = z.object({ + uid: z.string().max(36), + evse_id: z.string().max(48).regex(/^(([A-Z]{2}\*?[A-Z0-9]{3}\*?E[A-Z0-9\*]{1,30})|(\+?[0-9]{1,3}\*[0-9]{3}\*[0-9\*]{1,32}))$/).nullish(), + status: EvseStatus, + status_schedule: z.array(StatusSchedule).nullish(), + capabilities: z.array(Capability).nullish(), + connectors: z.array(Connector).nonempty(), + floor_level: z.string().max(4).nullish(), + coordinates: GeoLocation.nullish(), + physical_reference: z.string().max(16).nullish(), + directions: z.array(DisplayText).nullish(), + parking_restrictions: z.array(ParkingRestriction).nullish(), + parking: z.array(EvseParking).nullish(), + images: z.array(Image).nullish(), + accepted_service_providers: z.array(z.string().max(50).nullish()).nullish(), + last_updated: z.date(), +}); + +const ParkingType = createOpenEnum([ + "ALONG_MOTORWAY", + "ON_STREET", + "ON_DRIVEWAY", + "PARKING_GARAGE", + "UNDERGROUND_GARAGE", + "PARKING_LOT", +]); + +const AdditionalGeoLocation = z.object({ + latitude: z + .string() + .max(10) + .regex(/-?[0-9]{1,2}\.[0-9]{5,7}/), + longitude: z + .string() + .max(11) + .regex(/-?[0-9]{1,3}\.[0-9]{5,7}/), + name: DisplayText.nullish(), +}); + +const Facility = createOpenEnum([ + "HOTEL", + "RESTAURANT", + "CAFE", + "MALL", + "SUPERMARKET", + "SPORT", + "RECREATION_AREA", + "NATURE", + "MUSEUM", + "BIKE_SHARING", + "BUS_STOP", + "TAXI_STAND", + "TRAM_STOP", + "METRO_STATION", + "TRAIN_STATION", + "AIRPORT", + "PARKING_LOT", + "CARPOOL_PARKING", + "FUEL_STATION", + "WIFI", +]); + +const ExceptionalPeriod = z.object({ + period_begin: z.date(), + period_end: z.date(), +}); + +const RegularHours = z.object({ + weekday: z.number().int().min(1).max(7), + period_begin: z + .string() + .length(5) + .regex(/([0-1][0-9]|2[0-3]):[0-5][0-9]/), + period_end: z + .string() + .length(5) + .regex(/([0-1][0-9]|2[0-3]):[0-5][0-9]/), +}); + +const Hours = z.object({ + regular_hours: z.array(RegularHours).nullish(), + twentyfourseven: z.boolean(), + exceptional_openings: z.array(ExceptionalPeriod).nullish(), + exceptional_closings: z.array(ExceptionalPeriod).nullish(), +}); + +const EnergySourceCategory = z.enum([ + "NUCLEAR", + "GENERAL_FOSSIL", + "COAL", + "GAS", + "GENERAL_GREEN", + "SOLAR", + "WIND", + "WATER", +]); + +const EnergySource = z.object({ + source: EnergySourceCategory, + percentage: z.number().min(0).max(100), +}); + +const EnvironmentalImpactCategory = createOpenEnum(["NUCLEAR_WASTE", "CARBON_DIOXIDE"]); + +const EnvironmentalImpact = z.object({ + category: EnvironmentalImpactCategory, + amount: z.number(), +}); + +export const EnergyMix = z.object({ + is_green_energy: z.boolean(), + energy_sources: z.array(EnergySource).nullish(), + environ_impact: z.array(EnvironmentalImpact).nullish(), + supplier_name: z.string().max(64).nullish(), + energy_product_name: z.string().max(64).nullish(), +}); + +export const PublishTokenType = z.object({ + uid: z.string().max(36).nullish(), + type: TokenType.nullish(), + visual_number: z.string().max(64).nullish(), + issuer: z.string().max(64).nullish(), + group_id: z.string().max(36).nullish(), +}); + +export const VehicleType = createOpenEnum([ + "MOTORCYCLE", + "PERSONAL_VEHICLE", + "PERSONAL_VEHICLE_WITH_TRAILER", + "VAN", + "SEMI_TRACTOR", + "RIGID", + "TRUCK_WITH_TRAILER", + "BUS", + "DISABLED", +]); + +export const ParkingDirection = z.enum([ + "PARALLEL", + "PERPENDICULAR", + "ANGLE", +]); + +export const Parking = z.object({ + id: z.string().max(36), + physical_reference: z.string().max(12).nullish(), + vehicle_types: z.array(VehicleType), + max_vehicle_weight: z.number().nonnegative().nullish(), + max_vehicle_height: z.number().nonnegative().nullish(), + max_vehicle_length: z.number().nonnegative().nullish(), + max_vehicle_width: z.number().nonnegative().nullish(), + parking_space_length: z.number().nonnegative().nullish(), + parking_space_width: z.number().nonnegative().nullish(), + dangerous_goods_allowed: z.boolean().nullish(), + direction: ParkingDirection.nullish(), + drive_through: z.boolean().nullish(), + restricted_to_type: z.boolean(), + reservation_required: z.boolean().nullish(), + time_limit: z.number().nonnegative().nullish(), + roofed: z.boolean().nullish(), + images: z.array(Image).nullish(), + lighting: z.boolean().nullish(), + refrigeration_outlet: z.boolean().nullish(), + standards: z.array(z.string().max(36).nullish()).nullish(), + apds_reference: z.string().nullish(), +}) + +export const Location = z.object({ + country_code: z.string().length(2), + party_id: z.string().max(3), + id: z.string().max(36), + publish: z.boolean(), + publish_allowed_to: z.array(PublishTokenType).nullish(), + name: z.string().max(255).nullish(), + address: z.string().max(45), + city: z.string().max(45), + postal_code: z.string().max(10).nullish(), + state: z.string().max(20).nullish(), + country: z.string().length(3), + coordinates: GeoLocation, + related_locations: z.array(AdditionalGeoLocation).nullish(), + parking_type: ParkingType.nullish(), + evses: z.array(Evse).nullish(), + parking_places: Parking.nullish(), + directions: z.array(DisplayText).nullish(), + operator: BusinessDetails.nullish(), + suboperator: BusinessDetails.nullish(), + owner: BusinessDetails.nullish(), + facilities: z.array(Facility).nullish(), + time_zone: z.string().max(255), + opening_times: Hours.nullish(), + charging_when_closed: z.boolean().nullish(), + images: z.array(Image).nullish(), + energy_mix: EnergyMix.nullish(), + help_phone: z.string().max(25).nullish(), + last_updated: z.date(), +}); + +export const Locations = z.array(Location); diff --git a/ocpi.sessions.v23.ts b/ocpi.sessions.v23.ts new file mode 100644 index 0000000..7d3151b --- /dev/null +++ b/ocpi.sessions.v23.ts @@ -0,0 +1,59 @@ +import {z} from "zod"; +import { AuthMethod, CdrToken } from "./ocpi.cdrs.v23"; +import { Price } from "./ocpi.common.v23"; + + +export const SessionStatus = z.enum(["ACTIVE", "COMPLETED", "INVALID", "PENDING", "RESERVATION"]); +export const ProfileType = z.enum(["CHEAP", "FAST", "GREEN", "REGULAR"]); + +const CdrDimensionType = z.enum([ + "CURRENT", + "ENERGY", + "ENERGY_EXPORT", + "ENERGY_IMPORT", + "MAX_CURRENT", + "MIN_CURRENT", + "MAX_POWER", + "MIN_POWER", + "PARKING_TIME", + "POWER", + "RESERVATION_TIME", + "RESERVATION_EXPIRES", + "RESERVATION_OVERTIME", + "STATE_OF_CHARGE", + "TIME", +]); + +export const CdrDimension = z.object({ + type: CdrDimensionType, + volume: z.number(), +}); + +export const ChargingPeriod = z.object({ + start_date_time: z.date(), + dimensions: z.array(CdrDimension).nonempty(), + tariff_id: z.string().max(36).nullish(), +}); + +export const Session = z.object({ + country_code: z.string().length(2), + party_id: z.string().max(3), + id: z.string().max(36), + start_date_time: z.date(), + end_date_time: z.date().nullish(), + kwh: z.number().nonnegative(), + cdr_token: CdrToken, + auth_method: AuthMethod, + authorization_reference: z.string().max(36).nullish(), + location_id: z.string().max(36), + evse_uid: z.string().max(36), + connector_id: z.string().max(36), + meter_id: z.string().max(255).nullish(), + currency: z.string().length(3), + charging_periods: z.array(ChargingPeriod).nullish(), + total_cost: Price.nullish(), + status: SessionStatus, + last_updated: z.date(), +}); + +export const Sessions = z.array(Session); diff --git a/ocpi.tariff.v23.ts b/ocpi.tariff.v23.ts new file mode 100644 index 0000000..63b3aba --- /dev/null +++ b/ocpi.tariff.v23.ts @@ -0,0 +1,83 @@ +import { z } from "zod"; +import { DisplayText } from "./ocpi.common"; +import { Price } from "./ocpi.common.v23"; +import { EnergyMix } from "./ocpi.location.v23"; + +const TariffDimensionType = z.enum(["ENERGY", "FLAT", "PARKING_TIME", "TIME"]); + +const PriceComponent = z.object({ + type: TariffDimensionType, + price: z.number().nonnegative(), + vat: z.number().nonnegative().nullish(), + step_size: z.number().int().nonnegative(), +}); + +const DayOfWeek = z.enum([ + "MONDAY", + "TUESDAY", + "WEDNESDAY", + "THURSDAY", + "FRIDAY", + "SATURDAY", + "SUNDAY", +]); + +const ReservationRestrictionType = z.enum([ + "RESERVATION", + "RESERVATION_EXPIRES", + "RESERVATION_CANCELLATION_FEES", + "RESERVATION_OVERTIME", +]); + +const TariffRestrictions = z.object({ + start_time: z.string().length(5).regex(/([0-1][0-9]|2[0-3]):[0-5][0-9]/).nullish(), + end_time: z.string().length(5).regex(/([0-1][0-9]|2[0-3]):[0-5][0-9]/).nullish(), + start_date: z.string().length(10).regex(/([12][0-9]{3})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])/).nullish(), + end_date: z.string().length(10).regex(/([12][0-9]{3})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])/).nullish(), + min_kwh: z.number().nonnegative().nullish(), + max_kwh: z.number().nonnegative().nullish(), + min_current: z.number().nonnegative().nullish(), + max_current: z.number().nonnegative().nullish(), + min_power: z.number().nonnegative().nullish(), + max_power: z.number().nonnegative().nullish(), + min_duration: z.number().int().nonnegative().nullish(), + max_duration: z.number().int().nonnegative().nullish(), + day_of_week: z.array(DayOfWeek).nullish(), + reservation: ReservationRestrictionType.nullish() +}); + +const TariffElement = z.object({ + price_components: z.array(PriceComponent).nonempty(), + restrictions: TariffRestrictions.nullish(), +}); + +const TariffType = z.enum([ + "AD_HOC_PAYMENT", + "PROFILE_CHEAP", + "PROFILE_FAST", + "PROFILE_GREEN", + "REGULAR" +]); + +const TaxIncluded = z.enum(["YES", "NO", "N/A"]); + +export const Tariff = z.object({ + id: z.string().max(36), + country_code: z.string().length(2), + party_id: z.string().max(3), + currency: z.string().length(3), + type: TariffType.nullish(), + tariff_alt_text: z.array(DisplayText).nullish(), + tariff_alt_url: z.string().url().nullish(), + min_price: Price.nullish(), + max_price: Price.nullish(), + preauthorize_amount: z.number().nullish(), + elements: z.array(TariffElement).nonempty(), + tax_included: TaxIncluded, + start_date_time: z.date().nullish(), + end_date_time: z.date().nullish(), + energy_mix: EnergyMix.nullish(), + last_updated: z.date(), +}); + +export const Tariffs = z.array(Tariff); diff --git a/ocpi.terminal.v23.ts b/ocpi.terminal.v23.ts new file mode 100644 index 0000000..21dd6de --- /dev/null +++ b/ocpi.terminal.v23.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import { GeoLocation } from "./ocpi.location.v23"; + +export const InvoiceCreator = z.enum(["CPO", "PTP"]); + +export const Terminal = z.object({ + terminal_id: z.string().max(36), + customer_reference: z.string().max(36).nullish(), + party_id: z.string().max(3).nullish(), + country_code: z.string().max(2).nullish(), + address: z.string().max(45).nullish(), + city: z.string().max(45).nullish(), + postal_code: z.string().max(10).nullish(), + state: z.string().max(20).nullish(), + country: z.string().max(3).nullish(), + coordinates: GeoLocation.nullish(), + invoice_base_url: z.string().url().nullish(), + invoice_creator: InvoiceCreator.nullish(), + reference: z.string().max(36).nullish(), + location_ids: z.array(z.string().max(36).nullish()).nullish(), + evse_uids: z.array(z.string().max(36).nullish()).nullish(), + last_updated: z.date(), +}); + +// https://github.com/ocpi/ocpi/blob/release-2.3.0-bugfixes/mod_payments.asciidoc#request-body-1 +export const TerminalWithOptionalId = Terminal.extend({ + terminal_id: z.string().max(36).nullish(), +}); + +export const Terminals = z.array(Terminal); diff --git a/ocpi.tokens.v23.ts b/ocpi.tokens.v23.ts new file mode 100644 index 0000000..dba1e59 --- /dev/null +++ b/ocpi.tokens.v23.ts @@ -0,0 +1,34 @@ +import {z} from "zod"; +import { TokenType } from "./ocpi.common.v23"; +import { ProfileType } from "./ocpi.sessions.v23"; + +const WhitelistType = z.enum(["ALWAYS", "ALLOWED", "ALLOWED_OFFLINE", "NEVER"]); + +const EnergyContract = z.object({ + supplier_name: z.string().max(64), + contract_id: z.string().max(64).nullish(), +}); + +export const LocationReferences = z.object({ + location_id: z.string().max(36).nullish(), + evse_uids: z.array(z.string().max(36)).nullish(), +}); + +export const Token = z.object({ + country_code: z.string().length(2), + party_id: z.string().max(3), + uid: z.string().max(36), + type: TokenType, + contract_id: z.string().max(36), + visual_number: z.string().max(64).nullish(), + issuer: z.string().max(64), + group_id: z.string().max(36).nullish(), + valid: z.boolean(), + whitelist: WhitelistType, + language: z.string().length(2).nullish(), + default_profile_type: ProfileType.nullish(), + energy_contract: EnergyContract.nullish(), + last_updated: z.date(), +}); + +export const Tokens = z.array(Token); \ No newline at end of file diff --git a/ocpi.versions.v23.ts b/ocpi.versions.v23.ts new file mode 100644 index 0000000..14b543d --- /dev/null +++ b/ocpi.versions.v23.ts @@ -0,0 +1,40 @@ +import { z } from "zod"; +import { createOpenEnum } from "./ocpi.common.v23"; + +const OcpiVersionNumber = createOpenEnum(["2.0", "2.1", "2.1.1", "2.2", "2.2.1" ,"2.3"]); + +export const OcpiVersion = z.object({ + version: OcpiVersionNumber, + url: z.string().url(), +}); + +export const OcpiVersions = z.array(OcpiVersion); + +export const OcpiModuleId = createOpenEnum([ + "cdrs", + "chargingprofiles", + "commands", + "credentials", + "hubclientinfo", + "locations", + "sessions", + "tariffs", + "tokens", +]); + + +export const OcpiInterfaceRole= z.enum([ + "SENDER", + "RECEIVER", +]); + +const OcpiEndpoint = z.object({ + identifier: OcpiModuleId, + role: OcpiInterfaceRole, + url: z.string().url(), +}); + +export const OcpiVersionDetails = z.object({ + version: OcpiVersionNumber, + endpoints: z.array(OcpiEndpoint).nonempty(), +});