Skip to content

Latest commit

 

History

History
201 lines (130 loc) · 11.8 KB

CLIENT.md

File metadata and controls

201 lines (130 loc) · 11.8 KB

last updated March 29th, 2025

Client Folder Documentation

client folder docuemntation i love frotned devleopment - burton

How do we acutally receive the realtime data on the frontend?

Back to the topic of sockets. We are the client in thise case, and we receive broadcasted data from the server

Different settings

Demo switch

All of this is just dummy data being generated by a library that we use called faker.js. The function that generates that data can be found here

Basically, when we don't actually have the car running or anything connected with MQTT we aren't receiving data to be shown. So, we created a demo tab to show what the site should look like even when no data is being sent.

Network switch

Wtf is a package json script

Find it here

"scripts": {
    "postinstall": "husky install",
    "start": "lerna run start --stream",
    "build": "lerna run build",
    "dev": "lerna run dev --stream",
    "dev:client": "lerna run dev --scope=client --stream",
    "dev:server": "lerna run dev --scope=server --stream",
    "build:client": "lerna run build --scope=client --stream",
    "build:server": "lerna run build --scope=server --stream",
    "start:client": "lerna run start --scope=client --stream",
    "start:server": "lerna run start --scope=server --stream",
    "lint": "lerna run lint",
    "lintf": "lerna run lintf",
    "test": "lerna run test"
  },

Basically when you run these in a terminal, different commands will actually be run - the only ones that you need to worry about are these ones:

note that npm run and yarn are interchangeable, but please if you have time just install yarn so that there are no lock file conflicts

# Will run both the client and the server
yarn dev

One thing to note about this (hopefully, if it's implemented after I write this) is that when you run the server and the client there is no latency and connection to the car symbol. This is because no connection to the car (MQTT connection) right now. For a way to simulate having a car, go here

# Will only run the server
yarn dev:server
# Will only run the client
yarn dev:client

One thing to note is that when you only run the client, you are not actually connected to the backend, which is a reason why you see X's during the latency

The other scripts are used for other things, you won't need to use them usually.

wtf is a context icl ts pmo sybau

You can either read documentation about what a context is, or I can tell you with an example in this repository.

I am just going to give an example instead of a high level explanation because it's probably better. You do need a general understanding about what props are in React though.

The best explanation is in the hero container, where we use the app state here

Basically, a context wraps a bunch of components inside of it and gives the components that are wrapped in it context of the others. And you might be thinking, why can't you just use props to solve this problem??? The problem is prop drilling, where you have a parent component that passes too many props down too many levels of components. Having a parent pass a child a prop is usually fine, but what happens when you give the child's child's child's child a prop? It gives kind of tiring, and this is known as prop drilling.

You can imagine that the variables that are in the context global variables that are accessible by every single component that the context wraps.

The best example in this repository is the AppStateContext, which gives every component context of the others. For example, in the settings, if I switched to dark mode, every component needs to be aware of this, which is why we have the context.

Here is one example usage with the playback functionality.

  1. In the status component, we have the switch that switches from race mode to playback mode where we can view past data. We somehow need to let the rest of the components know that we did switch to this mode. You can see if you clicked the link it brought you to exactly where it is, and the current app state is set when the switch is pressed.

  2. After the switch is pressed, the logic is processed in another file where the data is now set to be in playback mode which is here in the packet context. Basically what this is doing is filling it with a filler packet until playback data is loaded

You can see that there was no prop drilling needed at all when a context is used. You can also see that the currentAppState.playbackSwitch is also used in other places, which signifies the importance of using contexts.

Different contexts that we have

You can find them in the contexts folder here

After writing this documentation, here are some modifications to consider to make it more readable and compact:

  • There is a chance that the lapdata context isn't really needed, and we can instead replace that logic and refactor it to the socket context instead

App state context

This context gives context of the fields below that are relatively important, in terms of hierarchy, you can place this at the top of all the contexts.

const [currentAppState, setCurrentAppState] = useState<IAppState>({
    appUnits: APPUNITS.METRIC,
    carLatency: 0,
    connectionType: CONNECTIONTYPES.DEMO,
    displayLoading: true,
    error: false,
    favourites: [],
    lapCoords: { lat: 51.081021, long: -114.136084 },
    loading: true,
    playbackSwitch: false,
    radioConnected: false,
    socketConnected: false,
    userLatency: 0,
  });

You can see that these are all fields that need to be known around the whole entire codebase, like what current connection type we are currently on to switch from demo mode to network, etc.

Packet context

Handles what data is being shown on the website currently, between:

  • the demo mode - shows fake data, and generates it every 0.5seconds which replicates what technically should be happening as well during race
  • the network mode - shows realtime data as long as you have a webserver test or the car running
  • the playback mode - shows past data based on a certain time range set in the settings
  • radio mode - not implemented as of when this was written

Lap data context

For the race tab, we fetch the laps through the socket to display them in the race tab.

Socket context

Used to calculate the latency as well as to see if the socket is actually connected to the backend.

Faults context and logic

There are some things beforehand, and you can find them here

Probably one of the complicated and unreadable files due to the logic that's happening here. I will try and explain this with the situation below:

  1. A packet is received, and there is a fault.
  • Because there is a fault, the processFaultSection() will mark it as true (meaning that a fault has been noticed) and add it to the currentFaults Map object.
  • A timer for 10ish 'packets' start, or the equivalent of 5 seconds as 10 packets every 0.5 seconds = 5 total seconds
  • Current timer: 0
  1. Another packet is received, and that fault is turned to false (meaning that the fault is no longer there, meaning it's been fixed or it was a malfunction)
  • The next time the processFaultSection() function runs, it will notice that this fault is no longer there. Because of this, the incrementOrDropFaultTimer function will increment this timer by 1.
  • Current timer: 1
  1. Let's now say that 3 more packets are received, and the fault is still marked as false. You can imagine what happens:
  • The timer will now increase 3 more times
  • Current timer: 4
  1. A packet is recieved where the fault is back again and is true.
  • The same process happens, but now the timer is reset back to 0.
  • Current timer: 0

Basically, when the timer finally reaches 10, the fault will be removed from the Map object and removed from the frontend as the fault is no longer being detected.

Favorites Logic

HOLY O(N^4) GG ITS SO COOKED

Generate Fake Data

We generate fake data in the shared folder because this function is used in multiple places. You can find that function here.

This fake data is based off the actual HeliosTelemetry data type found here in the types file.

PIS logic

There are currently 4 PIS hooks that generate the data we use to actually show on the frotend, namely:

  1. PIS.battery.tsx
  2. PIS.fault.tsx
  3. PIS.motor.tsx
  4. PIS.mppt.tsx

You can look through the files and they are pretty straightforward, as it basically just accesses the fields that we want from the packet and packages into JSON object that we can parse anyway we want, which is what we do in the PISTransformer.tsx file.

If we actually look through this file, it may look complicated, but I can try and break down each function to make it easier. You can treat this transformer file as a giant JSON parser.

  • FieldUnitsHandler(): Handles different types of units to display. Straightforward, and uses an ENUM to distinguish between imperial vs. metric units.
  • RangeCheckFieldData(): We show all types of different data from booleans, numbers, and strings/text. This is a function to basically see which type the field is trying to display, and making sure that the correct symbol or number is shown (if its true, we show T, etc.)
  • FormatString(): Formats the string. Nice!
  • FieldDataFormatter(): Basically, if the field does have an fstring then it will format the string based on the fstring. You will notice that in the interface, the fstring is actually declared as an optional variable so there are cases when there is no fstring (it's just a boolean or just a number.) Here is an example of what the fstring field might look like in the PIS here
  • FieldPrinter(): There's some logic here for actually adding the favorites here but that's pretty self explanatory. This function is for printing the name of the field as well as the number (which is explained in the FieldDataFormatter() function)
  • FieldsPrinter(): Instead of printing the field, this maps the object and prints the fields... Yeah
  • PISTransformer(): This one is probably the most important one. Since the packet object that we are parsing is not consistently nested, meaning that there could be arrays in arrays in objects, etc, we use the PISTransformer() as a recursive loop to actually see when we want to print the fields. We use this using the depth variable, so we pass in props and use recursion to pass depth + 1 to increase the depth. Based on the depth as well, the text size will get smaller and smaller when printing the keys of the fields. Here is an example:

motor faults example motor faults web example

The top image is the nested structure of the Right motors warning flags that you can find here. The bottom image is what is actually shown on the website. You can see that the titles (keys) of the object are actually getting smaller with how deep you parse the packet object. If you still don't get it, ask a tech lead.git