Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bootnotification #24

Merged
merged 11 commits into from
May 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions app/components/evse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,26 @@ import WebSocket from './WebSocket';
import { ChargingSocket, IChargingSocket } from '../service/ocpp/connector';
import { StatusNotification } from '../service/ocpp/status.notificiation';
import { HandleOcpp } from '../service/ocpp/ocpp.handler';
import { SendBootNotification } from '../service/ocpp/command/boot-notification/bootnotification';

const defaultValue = 'ws://localhost:8080/ocpp/JwNpTpPxPm/CHR202305102';

export default function Evse() {
const [url, setUrl] = useState(defaultValue);
const [online, setOnline] = useState(false);
const writer = useRef<IWriter[]>([]);
const writer = useRef<Array<IWriter>>([]);

const chargingSocket = useRef<IChargingSocket>(
new ChargingSocket(StatusNotification.UNAVAILABLE)
);

const onlineChange: ConState = (connected: boolean, w?: IWriter) => {
console.log(chargingSocket);

setOnline(connected);
if (w != null) {
writer.current.push(w);
// send BootNotification once during Boot up
w?.Write('hello');
console.log(chargingSocket);
SendBootNotification(w);
}
};

Expand Down
15 changes: 15 additions & 0 deletions app/helper/validation.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { plainToClass } from 'class-transformer';
import { validateSync, ValidationError } from 'class-validator';

type ValidationResult<T> = [T, Array<ValidationError>];

const defaultValue = {
enableDebugMessages: false,
validationError: { target: false },
};

export default function Validate<T>(c: any, obj: unknown): ValidationResult<T> {
const result: object = plainToClass(c, obj);
const validation = validateSync(result, defaultValue);
return [result as T, validation];
}
2 changes: 0 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
'use client';

import Evse from './components/evse';

export default function Home() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {
IsNumber,
IsEnum,
Min,
Max,
MinLength,
MaxLength,
IsOptional,
IsString,
} from 'class-validator';

enum Status {
ACCEPTED = 'Accepted',
REJECTED = 'Rejected',
PENDING = 'Pending',
}

interface IBootNotification {
chargePointVendor: string;
chargePointModel: string;
chargeBoxSerialNumber: string;
chargePointSerialNumber: string;
firmwareVersion: string;
iccid: string;
imsi: string;
meterSerialNumber: string;
meterType: string;
}
interface IBootNotificationRes {
currentTime: Date;
interval: number;
status: Status;
}

class BootNotification implements IBootNotification {
@MinLength(1)
@MaxLength(20)
chargePointVendor = '';

@MinLength(1)
@MaxLength(20)
chargePointModel = '';

@MinLength(1)
@MaxLength(25)
@IsOptional()
chargeBoxSerialNumber = '';

@MinLength(1)
@MaxLength(25)
@IsOptional()
chargePointSerialNumber = '';

@MinLength(1)
@MaxLength(50)
@IsOptional()
firmwareVersion = '';

@MinLength(1)
@MaxLength(20)
@IsOptional()
iccid = '';

@MinLength(1)
@MaxLength(20)
@IsOptional()
imsi = '';

@MinLength(1)
@MaxLength(25)
@IsOptional()
meterSerialNumber = '';

@MinLength(1)
@MaxLength(25)
@IsOptional()
meterType = '';
}

class BootNotificationRes implements IBootNotificationRes {
@IsString()
currentTime: Date = new Date();

@IsNumber()
@Min(3600)
@Max(84000)
interval = 0;

@IsEnum(Status)
status = Status.PENDING;
}

export type { IBootNotification };
export { Status, BootNotification, BootNotificationRes };
81 changes: 81 additions & 0 deletions app/service/ocpp/command/boot-notification/bootnotification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { DEFAULT_TIMER, IWriter } from '../../../websocket/websocket.model';
import { Action, CreateRequestFrame, GetRequestFrame } from '../../ocpp.action';
import { IResponse } from '../../ocpp.frame';
import { NewTransaction } from '../../transaction/transaction.handler';
import {
BootNotificationRes,
IBootNotification,
Status,
} from './bootnotification.model';
import { CreateError, ErrorCode } from '../../ocpp.error';
import Validate from '@/app/helper/validation.helper';

const defaultValue: IBootNotification = {
chargePointVendor: 'EW',
chargePointModel: 'CHR22',
chargeBoxSerialNumber: 'CR-00',
chargePointSerialNumber: 'CR-00',
firmwareVersion: '',
iccid: '',
imsi: '',
meterSerialNumber: '',
meterType: 'power',
};

let id: ReturnType<typeof setTimeout>;
let success = false;

function retry(w: IWriter): void {
id = setTimeout(() => {
try {
SendBootNotification(w);
} catch (error) {
clearTimeout(id);
}
}, DEFAULT_TIMER);
}

function SendBootNotification(w: IWriter): void {
try {
clearTimeout(id);
if (success) return;
const frame = CreateRequestFrame(Action.BOOT_NOTIFICATION, defaultValue);
NewTransaction(GetRequestFrame(frame), BootNotification);
w.Write(frame);
} catch (error) {
retry(w);
}
}

function BootNotification(w: IWriter, frame: IResponse): void {
clearTimeout(id);
const [result, validation] = Validate<BootNotificationRes>(
BootNotificationRes,
frame.payload
);

console.log(validation);

if (validation.length > 0) {
w.Write(CreateError(ErrorCode.PropertyConstraintViolation, validation));
return retry(w);
}

if (result.status == Status.PENDING) {
w.Write(
CreateError(ErrorCode.NotSupported, {
err: 'Pending functionality is not supported',
})
);

return retry(w);
}

if (result.status == Status.REJECTED) {
return retry(w);
}

success = true;
}

export { SendBootNotification, BootNotification };
1 change: 1 addition & 0 deletions app/service/ocpp/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface IChargingSocket {
}

class ChargingSocket implements IChargingSocket {
booted = false;
constructor(private state: StatusNotification) {}

ChangeState(state: StatusNotification): void {
Expand Down
44 changes: 35 additions & 9 deletions app/service/ocpp/ocpp.action.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { IRequest, IResponse } from './ocpp.frame';
import { v4 } from 'uuid';
import {
BaseTuple,
CallType,
IRequest,
IResponse,
RequestTuple,
ResponseTuple,
UniqueID,
} from './ocpp.frame';
import { ErrorCode } from './ocpp.error';

enum Action {
BOOT_NOTIFICATION = 'BootNotification',
Expand All @@ -10,20 +20,36 @@ enum Action {
REMOTE_STOP_TRANSACTION = 'RemoteStopTransaction',
}

function CreateRequestFrame(): void {
throw new Error('Not implemented');
function CreateRequestFrame<T>(action: Action, payload: T): RequestTuple {
return [CallType.CALL, v4(), action, payload];
}

function GetRequestFrame(): IRequest {
throw new Error('Not implemented');
function GetRequestFrame(payload: RequestTuple | BaseTuple): IRequest {
if (payload.length < 4) throw new Error(ErrorCode.FormationViolation);
if (payload[0] != CallType.CALL) throw new Error(ErrorCode.ProtocolError);

return {
messageTypeID: payload[0],
uuid: payload[1],
action: payload[2]!,
payload: payload[3],
};
}

function CreateResponseFrame(): void {
throw new Error('Not implemented');
function CreateResponseFrame<T>(uuid: UniqueID, payload: T): ResponseTuple {
return [CallType.CALL_RESULT, uuid, payload];
}

function GetResponseFrame(): IResponse {
throw new Error('Not implemented');
function GetResponseFrame(payload: ResponseTuple | BaseTuple): IResponse {
if (payload.length < 3) throw new Error(ErrorCode.FormationViolation);
if (payload[0] != CallType.CALL_RESULT)
throw new Error(ErrorCode.ProtocolError);

return {
messageTypeID: payload[0],
uuid: payload[1],
payload: payload[2],
};
}

export {
Expand Down
4 changes: 2 additions & 2 deletions app/service/ocpp/ocpp.error.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BaseTuple, CallType, ErrorFrame, ErrorTuple } from './ocpp.frame';
import { BaseTuple, CallType, IErrorFrame, ErrorTuple } from './ocpp.frame';
import { v4 } from 'uuid';

type ErrorDescription = string;
Expand Down Expand Up @@ -32,7 +32,7 @@ function CreateError(errCode: ErrorCode, details: ErrorDetails): ErrorTuple {
return [CallType.CALL_ERROR, v4(), code, errCode, details];
}

function GetError(payload: ErrorTuple | BaseTuple): ErrorFrame {
function GetError(payload: ErrorTuple | BaseTuple): IErrorFrame {
if (payload.length != 5) throw new Error(ErrorCode.FormationViolation);
if (payload[0] != CallType.CALL_ERROR)
throw new Error(ErrorCode.FormationViolation);
Expand Down
4 changes: 2 additions & 2 deletions app/service/ocpp/ocpp.frame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface IPayload {
payload: Payload;
}

interface ErrorFrame extends IFrame {
interface IErrorFrame extends IFrame {
errorCode: ErrorCode;
errorDescription: ErrorDescription;
errorDetails: unknown;
Expand All @@ -40,7 +40,7 @@ interface IFrame {
export type {
IRequest,
IResponse,
ErrorFrame,
IErrorFrame,
Action,
BaseTuple,
UniqueID,
Expand Down
Loading
Loading