feat(ui): use toggle button to star/stop simulator
authorJérôme Benoit <jerome.benoit@sap.com>
Fri, 1 Mar 2024 19:09:01 +0000 (20:09 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Fri, 1 Mar 2024 19:09:01 +0000 (20:09 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/Bootstrap.ts
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts
src/charging-station/ocpp/2.0/OCPP20ResponseService.ts
src/charging-station/ui-server/ui-services/AbstractUIService.ts
src/types/UIProtocol.ts
ui/web/src/components/buttons/ToggleButton.vue
ui/web/src/composables/UIClient.ts
ui/web/src/types/UIProtocol.ts
ui/web/src/views/ChargingStationsView.vue

index 047b321b55c36c910211e381482f09439422a63b..9247ab3deb5112925a4a7f8f215c31a5823712ff 100644 (file)
@@ -118,6 +118,12 @@ export class Bootstrap extends EventEmitter {
     )
   }
 
+  public getState (): { started: boolean } {
+    return {
+      started: this.started
+    }
+  }
+
   public getLastIndex (templateName: string): number {
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     const indexes = [...this.templatesChargingStations.get(templateName)!.indexes]
index f77dfa7f8b0efa1b0ddde1c51601b80740ab3ced..1456e23758908ff1ce55a620e2456718aca5f907 100644 (file)
@@ -643,7 +643,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
         // Throw exception
         throw new OCPPError(
           ErrorType.NOT_IMPLEMENTED,
-          `${commandName} is not implemented to handle request PDU ${JSON.stringify(
+          `'${commandName}' is not implemented to handle request PDU ${JSON.stringify(
             commandPayload,
             undefined,
             2
index 691bf0f6c6b243465255c2c4f322c1c65917ffb8..3905445e02fde891034802a202403d1b0bfb396b 100644 (file)
@@ -468,7 +468,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
         // Throw exception
         throw new OCPPError(
           ErrorType.NOT_IMPLEMENTED,
-          `${commandName} is not implemented to handle response PDU ${JSON.stringify(
+          `'${commandName}' is not implemented to handle response PDU ${JSON.stringify(
             payload,
             undefined,
             2
index 32843895142ea43e136ae226d6e61cbe91eb68bd..2a561e45795a0190a951700a5e2275cfe253a3c9 100644 (file)
@@ -109,7 +109,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
         // Throw exception
         throw new OCPPError(
           ErrorType.NOT_IMPLEMENTED,
-          `${commandName} is not implemented to handle request PDU ${JSON.stringify(
+          `'${commandName}' is not implemented to handle request PDU ${JSON.stringify(
             commandPayload,
             undefined,
             2
index 09b319c9a653b9375920703f3a57cb4b073f8a13..9473324fa1b3ebcfede0fe59a37aa1699911af9c 100644 (file)
@@ -141,7 +141,7 @@ export class OCPP20ResponseService extends OCPPResponseService {
         // Throw exception
         throw new OCPPError(
           ErrorType.NOT_IMPLEMENTED,
-          `${commandName} is not implemented to handle response PDU ${JSON.stringify(
+          `'${commandName}' is not implemented to handle response PDU ${JSON.stringify(
             payload,
             undefined,
             2
index fbca016bed65fd3cf0d4a4ac3563fafa7105e514..de83c6b7e4eb85adaa3e2d5297393482c3d8feb6 100644 (file)
@@ -76,6 +76,7 @@ export abstract class AbstractUIService {
       [ProcedureName.LIST_CHARGING_STATIONS, this.handleListChargingStations.bind(this)],
       [ProcedureName.ADD_CHARGING_STATIONS, this.handleAddChargingStations.bind(this)],
       [ProcedureName.PERFORMANCE_STATISTICS, this.handlePerformanceStatistics.bind(this)],
+      [ProcedureName.SIMULATOR_STATE, this.handleSimulatorState.bind(this)],
       [ProcedureName.START_SIMULATOR, this.handleStartSimulator.bind(this)],
       [ProcedureName.STOP_SIMULATOR, this.handleStopSimulator.bind(this)]
     ])
@@ -98,7 +99,7 @@ export abstract class AbstractUIService {
 
       if (!this.requestHandlers.has(command)) {
         throw new BaseError(
-          `${command} is not implemented to handle message payload ${JSON.stringify(
+          `'${command}' is not implemented to handle message payload ${JSON.stringify(
             requestPayload,
             undefined,
             2
@@ -295,6 +296,21 @@ export abstract class AbstractUIService {
     }
   }
 
+  private handleSimulatorState (): ResponsePayload {
+    try {
+      return {
+        status: ResponseStatus.SUCCESS,
+        state: Bootstrap.getInstance().getState()
+      } satisfies ResponsePayload
+    } catch (error) {
+      return {
+        status: ResponseStatus.FAILURE,
+        errorMessage: (error as Error).message,
+        errorStack: (error as Error).stack
+      } satisfies ResponsePayload
+    }
+  }
+
   private async handleStartSimulator (): Promise<ResponsePayload> {
     try {
       await Bootstrap.getInstance().start()
index 6e120337197d3ea39b65a48517b6e9f554e9eaa2..37ec8ba924415fff78683ceebed9d2f62b2eab1f 100644 (file)
@@ -29,6 +29,7 @@ export type ProtocolRequestHandler = (
 ) => undefined | Promise<undefined> | ResponsePayload | Promise<ResponsePayload>
 
 export enum ProcedureName {
+  SIMULATOR_STATE = 'simulatorState',
   START_SIMULATOR = 'startSimulator',
   STOP_SIMULATOR = 'stopSimulator',
   LIST_TEMPLATES = 'listTemplates',
index dd1805ca11a1e43dde08d3bb726a018177f88b29..b1cab90c6154867d47dcdd23598c79464e9aefea 100644 (file)
@@ -15,6 +15,8 @@ const props = defineProps<{
   shared?: boolean
   on?: () => void
   off?: () => void
+  onStyle?: string
+  offStyle?: string
 }>()
 
 const $emit = defineEmits(['clicked'])
@@ -26,7 +28,7 @@ const state = ref({
 })
 
 const click = (): void => {
-  if (props.shared) {
+  if (props.shared === true) {
     for (const key in localStorage) {
       if (key !== id && key.startsWith('shared-toggle-button-')) {
         setToLocalStorage<boolean>(key, false)
index 023aef111f4fc41309ac3d3bf4eb915866dfda84..04b703e3e5e397eaabf4c8f37d191e9806761633 100644 (file)
@@ -53,6 +53,10 @@ export class UIClient {
     this.ws?.addEventListener(event, listener, options)
   }
 
+  public async simulatorState(): Promise<ResponsePayload> {
+    return this.sendRequest(ProcedureName.SIMULATOR_STATE, {})
+  }
+
   public async startSimulator(): Promise<ResponsePayload> {
     return this.sendRequest(ProcedureName.START_SIMULATOR, {})
   }
index afc55b03069540e6f7a48b7e86dec88f4cea1291..183098955bccf63f8b203aed569c5180bd883aea 100644 (file)
@@ -25,6 +25,7 @@ export type ProtocolRequestHandler = (
 ) => ResponsePayload | Promise<ResponsePayload>
 
 export enum ProcedureName {
+  SIMULATOR_STATE = 'simulatorState',
   START_SIMULATOR = 'startSimulator',
   STOP_SIMULATOR = 'stopSimulator',
   LIST_TEMPLATES = 'listTemplates',
index c49449f636080a922362f3618d4334ff3354bada..57ae3e20d7f0ba65d57a1933b1b483887f739189 100644 (file)
@@ -20,6 +20,7 @@
                 'open',
                 () => {
                   setToLocalStorage<number>('uiServerConfigurationIndex', state.uiServerIndex)
+                  clearToggleButtons()
                   $router.currentRoute.value.name !== 'charging-stations' &&
                     $router.push({ name: 'charging-stations' })
                 },
       </select>
     </Container>
     <Container id="buttons-container">
-      <Button @click="startSimulator()">Start Simulator</Button>
-      <Button @click="stopSimulator()">Stop Simulator</Button>
+      <ToggleButton
+        :id="'simulator'"
+        :key="state.renderSimulator"
+        :status="state.simulatorState?.started"
+        :on="() => startSimulator()"
+        :off="() => stopSimulator()"
+      >
+        {{ state.simulatorState?.started === true ? 'Stop' : 'Start' }} Simulator
+      </ToggleButton>
       <ToggleButton
         :id="'add-charging-stations'"
         :key="state.renderAddChargingStations"
@@ -105,7 +113,6 @@ import CSTable from '@/components/charging-stations/CSTable.vue'
 import type { ResponsePayload, UIServerConfigurationSection } from '@/types'
 import Container from '@/components/Container.vue'
 import ReloadButton from '@/components/buttons/ReloadButton.vue'
-import Button from '@/components/buttons/Button.vue'
 import {
   getFromLocalStorage,
   getLocalStorage,
@@ -115,6 +122,21 @@ import {
 } from '@/composables'
 import ToggleButton from '@/components/buttons/ToggleButton.vue'
 
+const state = ref<{
+  renderSimulator: `${string}-${string}-${string}-${string}-${string}`
+  renderAddChargingStations: `${string}-${string}-${string}-${string}-${string}`
+  renderChargingStations: `${string}-${string}-${string}-${string}-${string}`
+  loading: boolean
+  simulatorState?: { started: boolean }
+  uiServerIndex: number
+}>({
+  renderSimulator: randomUUID(),
+  renderAddChargingStations: randomUUID(),
+  renderChargingStations: randomUUID(),
+  loading: false,
+  uiServerIndex: getFromLocalStorage<number>('uiServerConfigurationIndex', 0)
+})
+
 const app = getCurrentInstance()
 
 const clearToggleButtons = (): void => {
@@ -126,14 +148,30 @@ const clearToggleButtons = (): void => {
 }
 
 const clearChargingStations = (): void => {
-  clearToggleButtons()
   app!.appContext.config.globalProperties.$chargingStations = []
-  state.value.renderAddChargingStations = randomUUID()
   state.value.renderChargingStations = randomUUID()
 }
 
+const uiClient = app?.appContext.config.globalProperties.$uiClient
+
+const getSimulatorState = (): void => {
+  uiClient
+    .simulatorState()
+    .then((response: ResponsePayload) => {
+      state.value.simulatorState = response.state as { started: boolean }
+    })
+    .catch((error: Error) => {
+      $toast.error('Error at fetching simulator state')
+      console.error('Error at fetching simulator state:', error)
+    })
+    .finally(() => {
+      state.value.renderSimulator = randomUUID()
+    })
+}
+
 const initializeWSEventListeners = () => {
   app?.appContext.config.globalProperties.$uiClient.registerWSEventListener('open', () => {
+    getSimulatorState()
     uiClient
       .listTemplates()
       .then((response: ResponsePayload) => {
@@ -148,8 +186,10 @@ const initializeWSEventListeners = () => {
         $toast.error('Error at fetching charging station templates')
         console.error('Error at fetching charging station templates:', error)
       })
+      .finally(() => {
+        state.value.renderAddChargingStations = randomUUID()
+      })
     loadChargingStations(() => {
-      state.value.renderAddChargingStations = randomUUID()
       state.value.renderChargingStations = randomUUID()
     })
   })
@@ -167,14 +207,6 @@ onMounted(() => {
   initializeWSEventListeners()
 })
 
-const state = ref({
-  renderAddChargingStations: randomUUID(),
-  renderChargingStations: randomUUID(),
-  loading: false,
-  uiServerIndex: getFromLocalStorage<number>('uiServerConfigurationIndex', 0)
-})
-
-const uiClient = app?.appContext.config.globalProperties.$uiClient
 const uiServerConfigurations: { configuration: UIServerConfigurationSection; index: number }[] =
   app?.appContext.config.globalProperties.$configuration.uiServer.map(
     (configuration: UIServerConfigurationSection, index: number) => ({
@@ -221,6 +253,9 @@ const startSimulator = (): void => {
       $toast.error('Error at starting simulator')
       console.error('Error at starting simulator:', error)
     })
+    .finally(() => {
+      getSimulatorState()
+    })
 }
 const stopSimulator = (): void => {
   uiClient
@@ -235,6 +270,9 @@ const stopSimulator = (): void => {
       $toast.error('Error at stopping simulator')
       console.error('Error at stopping simulator:', error)
     })
+    .finally(() => {
+      getSimulatorState()
+    })
 }
 </script>
 
@@ -280,7 +318,7 @@ const stopSimulator = (): void => {
 }
 
 #reload-button:active {
-  background-color: red;
+  background-color: seagreen;
 }
 
 #action {