Refresh

This website github-wiki-see.page/m/rokanas/terminarium/wiki/Codebase-Documentation is currently offline. Cloudflare's Always Online™ shows a snapshot of this web page from the Internet Archive's Wayback Machine. To check for the live version, click Refresh.

Codebase Documentation - rokanas/terminarium GitHub Wiki

Codebase Documentation

This subpage of the Wiki acts as a centralized page which documents the codebase in its entirety, in order to give contributors and other interested parties an accessible overview of the codebase.

Table of Contents

Wio Terminal

This section documents the Wio Terminal codebase. Note that all .cpp source files have a corresponding .h header file. Further comprehensive line-by-line documentation can be viewed in the source code itself.

terminarium.ino

Contains the main program sketch for the Wio Terminal. It imports the code from all of the below files, initializes the peripherals (sensors, LCD screen and navigational buttons) and executes the main program loop. If network data is stored in flash storage, it also loads this data.

The program loop is further divided into sections:

  • READING: contains functions for reading the current sensor values and storing them in variables.
  • PARSING: contains functions to parse the stored raw data into humanly comprehensible values. For the temperature & humidity sensor this involves parsing into Celsius and % Relative Humidity readings. For the vibration sensor it involves parsing into an affirmative or negative String reading. For the rest of the sensors, the data is mapped to a % out of 100.
  • PRINTING: prints the parsed sensor values to the serial monitor.
  • PUBLISHING: publishes the sensor values via the MQTT broker using the appropriate topic names (defined via macros, unique to each sensor reading type).
  • DRAWING: calls the general function to draw directly on the Wio Terminal's LCD screen, which itself branches out into modular subfunctions contained in screen_draw.cpp.
  • CONNECTING: calls the functions to connect to WiFi and MQTT broker, as well as to maintain this connection by listening / sending a 'keep-alive' message.

An important aspect of the program loop is the loop interval. This interval is defined via macro in terms of milliseconds and determines the rate at which certain functions are called. Generally, Printing and Parsing will occur on every single program loop iteration, while all the rest of the functions will occur once per defined interval. This ensures that the sensors are very responsively gathering readings, but the rest of the program is running at a more humanly perceivable pace.

mqtt.cpp

Contains variables storing network information necessary for establishing MQTT connection, primarily SSID, WiFi Password, SERVER (broker address), as well as topic names for publishing and subscribing.

Also contains functions necessary for connectivity, including a function for connecting to WiFi network, connecting to MQTT broker, and a callback function that runs every time a message is received (specifically for saving incoming new sensor ranges).

Other functions called throughout the program, like the listening function to maintain MQTT connection, are imported from the rpcWifi and PubSubClient external libraries.

pins.h

Contains a list of macros denoting the pins for each sensor attached to the Wio Terminal, which are reused for readability throughout the main program .ino file.

screen_control.cpp

Defines the possible screen states by declaring representative corresponding enums. These are used throughout the program, and particularly by screen_draw.cpp, to identify or modify the current screen and conditionally trigger events specific to screen context.

Also controls the transfer from screen to screen by containing the functions executed upon physical button press, equal in number to the number of buttons in the Wio Terminal thumbstick (RIGHT, LEFT, UP, DOWN, MID). These functions contain switch cases that determine the event triggered by button press depending on the current screen state. For example, if current screen is a sensor screen, pressing the DOWN button will transfer to dashboard screen; however if current screen is the user input screen, the DOWN button will just move the highlight cursor down the onscreen keyboard.

These functions are called instantly upon button press and ignore the ordinary program loop and defined interval. This is because they are set up in Terminarium.ino as interrupt functions, which is done to ensure that the program feels smooth and responsive.

Each button function has a debouncing operation that reduces the occurrence of unwanted double /triple inputs that arise from the ordinary use of the hardware.

screen_draw.cpp

Contains all the functions for drawing onto the Wio Terminal's LCD screen, enabled by importing the TFT_eSPI library. Once every interval, general drawScreen() function is called from the Terminarium.ino main program loop, which calls specific subfunctions to draw whichever screen corresponds to the currently defined screen state.

Drawing is done directly to the LCD display (not using a buffer), so dynamic screens like dashboard and sensor screens need to clear themselves before new elements can be drawn on top. To prevent this causing a flicker on every interval: 1) the clear is done by drawing smallest possible background-colored rectangle over the changing element, and 2) old sensor values are kept track of so elements are redrawn only when they change, or when screen changes.

Functions are divided by sections:

  • GENERAL: contains the general drawScreen() function that calls further subfunctions to draw the screen according to currently defined screen state.
  • HEADER: contains functions that draw the Terminarium logo header at the top of the screen, present in every single screen, as well as icons denoting whether WiFi and MQTT connections are currently established.
  • DASHBOARD: contains functions for drawing the dashboard and its sub-elements containing readings from all sensors in parallel. Displayed sensor readings are color-coded depending on whether they fall in our out of the user-defined ranges.
  • SENSOR SCREENS: contains functions for drawing the individual sensor screens containing the corresponding reading as well and an indication whether the reading is OK, TOO HIGH or TOO LOW. Displayed readings are color-coded depending on whether they fall in our out of the user-defined ranges.
  • USER INPUT SCREENS: contains functions for drawing the user input screens, which involve drawing an onscreen keyboard, a highlight cursor that traverses the keyboard and the user inputted string in as it is being written in real-time.
  • CONNECTIVITY SCREENS: contains functions for drawing all screens relating to establishing MQTT connection, including the network info confirmation screen and the pseudo 'loading' screens (WiFi and MQTT connection screens & update screen) that contain progress bars.
  • MISC: Contains miscellaneous functions for clearing the screen (i.e. overwriting the screen with a black rectangle), calculating the starting X position for any given length string so it is drawn in the center of the screen, drawing triangle graphics (denoting screen navigation through directional button presses) and a dot...dot...dot animation (used in the Wifi and MQTT connection screens).

user_input.cpp

Contains a multidimensional array containing all the characters that are included in the onscreen keyboard in the user input screen, as well as functions that are called upon button press.

These functions either alter the position of the highlight cursor to navigate through the keyboard (directional buttons), or add to / remove from / submit the currently inputted string.

Also contains 3 variables that store the inputted strings (corresponding to each modifiable network detail, namely SSID, WiFi password and MQTT broker address) so that they can be passed on to screen_draw.cpp to be drawn as they are typed in real-time. When these modified strings have been saved, function saveData() from save_data.cpp is called to save their values in flash storage (or can be commented out to opt-out of this functionality, see below).

save_data.cpp

Contains functions for loading and saving network data (SSID, PASSWORD and SERVER) from the Wio Terminal's flash storage using emulated EEPROM, courtesy of the FlashStorage_SAMD external library. The data is stored in and retrieved from a struct that is written to memory at a defined address and contains each data in the form of a char[] array. The saveData() function specifically is called from saveUserInput() function in user_input.cpp once the user has finished inputting their custom network info from the user input screen. This permits that information to be retained after the Wio Terminal is powered off. If the user desires to opt-out of saving to flash storage to avoid expending the limited amount of writes (an estimated 10,000 for the Wio Terminal board), they can comment out line 212 of user_input.cpp.

utils.cpp

Contains utility functions used throughout the program. It is divided into sections:

  • PROGRAM INTERVAL: contains the function responsible for calculating whether the defined interval (in ms) has elapsed, returning a boolean accordingly. Various functions throughout the program rely on this return value to execute.
  • SENSOR SUBROUTINES: parsing functions specific to the functionality of the sensors. These include a read function for the temperature & humidity sensor that determines the interval at which readings should be made, parses them and stores them in an array (for which use of the DHT-Sensors-Non-Blocking library is made). It also includes parsing functions for the vibration sensor (converts int signal to a String value) and all the rest of the sensors (maps data to a % out of 100).
  • UPDATE SENSOR RANGES: contains the array that stores the upper and lower acceptable ranges for each sensor type, as well as functions that updates this array by receiving (via MQTT), validating and parsing new incoming ranges.
  • FORMAT OUTPUT: contains functions that format output data, primarily 3 overloaded toString methods used throughout the program, but also a separator macro used for formatting output to serial monitor.

Terminarium Web Application

This section documents the Terminarium Web Application codebase

Front End

This section documents the code developed for the Front-End with additional functionalities as part of the Vue components. We urge the readers to make them self familiar with vue concepts and component based architecture. The official vue team provides great documentation and tutorials Vue documentation.

Components

This section documents the Vue components.

What is a component?

Vue components are reusable, encapsulated, UI elements. They define a self-contained part of the website. Components can specify how it is structured, behaves and looks. This modular approach leads to easy to understand and well organised code.

Benefits of component based architecture during our project

Building a the UI in a component based matter is very beneficial for feature slicing as one component in many cases make up one feature. Features can be developed and tested without the need for dependencies on other components or developers, eliminating many potential bottlenecks during development.

How to Use

Note: Components can be modified or require props to function. If that is the case further steps are defined in the components specific documentation.

To use any of the vue components in the project, follow these steps:

  1. Import the component:

    import SetSensorRanges from './components/Component_Name';
  2. Register the component within your Vue instance or other component:

    export default {
      components: {
        Component_Name
      },
      // ...
    }
  3. Use the component in your template:

    <template>
      <div>
        <Component_Name />
      </div>
    </template>

App.vue

Generally speaking, the App.vue component is the entry point of the application. It is the root component that holds all other components. It is also the component that is rendered by the index.html file. Furthermore, it is the component that holds the logic of the navigation between the 'pages' of the application (i.e. the different set of components that are rendered on the screen at a given time - indicating a change of page).

Contains saveData() function that is passed sensor data as String messages received from the Wio Terminal via MQTT. These messages are parsed, processed and stored to the sensorValues map together with a formatted timestamp denoting the time that the message was received and saved.

More specifically regarding the way the sensor data is stored:

  • sensorValues: sensor data is stored in a map with 6 entries corresponding to 6 sensor types. For each entry, the key is the mqtt topic and the value is an object containing the sensor data and a timestamp of when the data is received. Having the unique sensor topics as keys allows both retrieval of specific sensor data and overwriting it when new incoming data is received.
  • vibrationCount: vibration data is a boolean value, useful for real-time monitoring but not for data analysis. Therefore we separately store the number of vibration occurrences (i.e. 'true' values) during each interval and write this number to the database instead of the boolean value stored in the sensorValues map.

In addition, at a defined time-interval, the function writeToDB() retrieves the current data stored in sensorValues and vibrationCount (including the timestamps) and passes it to the write() function in database.js to be written to the Firebase Realtime Database. This writing is cyclically repeated at the defined time-interval - DELAY is a constant defined to repeat the writing every $5 \times 60 000$ milliseconds (5 minutes).

Moreover, this component is responsible for handling the 'routing' of the website application, namely via an addEventListener() function with a "hashchange" event. This function is called whenever the URL hash changes, and it is responsible for updating the currentRoute property of the App.vue component, which is used to determine which component to render in the running Vue instance. There's an Object routes that maps an URL hash to a specific component, which is used to determine which component to render.

AccessButton.vue

This component provides a dynamic button that displays either "Log in" or "Log out" based on the user's login status.

Technical Description

The element inside the component will emit an event accessButtonEvent when clicked. Due to the nature of emitted events in Vue, the event needs to be re-emitted in each subsequent parent component until it reaches its intended destination: App.vue - otherwise the event will not propagate up the component tree. Here is where the event is finally handled and processed.

Options:

Inject

In order for this component to determine the current login status it injects a boolean property isLoggedIn, provided by the App.vue component.

Computed

The login status is checked inside a computed value accessButtonDescription, which dynamically returns the appropriate String label for the button.

Related Components

The main purpose of this component is its integration into the Header.vue component, where it serves as quick access for the user to log in or out.

Avatar.vue

Avatar.vue is used to dynamically display the correct image and text corresponding to a specific terrarium. It also enables the user to edit the name and image as well as delete unwanted instances of the component - by emitting events that can be handled by the parent component.

Technical Description

This component's <template> section defines 4 distinct elements:

  • the Avatar's image itself;

and conditionally displayed:

  • a delete button (overlaying the Avatar-image);
  • the name (underneath the Avatar-image)
    OR
  • a name input field (occupies same space as the name).

By default, the Avatar (specifically the image) will be interactable and upon clicking will invoke a method call (handleAvatarClicked(), which emits an event). Normally, the name will be displayed underneath, however, when the user has clicked the manage terrarium button - part of the SelectTerrariumPage component - instead, the name input field will be displayed in its place. This also triggers the delete button to be rendered over each Avatar-image allowing the user to delete a specific component instance (emits an event). Furthermore, when the user has input a new name to the input field and blurs it (either by clicking elsewhere on the page or by pressing the enter key) the handleNameChange() method will be invoked, and the new value will be assigned.

Mounted()

This lifecycle hook serves as a function that is executed after the component has been inserted into the DOM. In this instance it will first evaluate if the external property isManageable equates to true. Inside the if-statement's body, Vue API's nextTick() schedules a callback function after the next DOM update cycle, ensuring that the component is fully rendered before invoking the highlightSelectTextbox() (which otherwise will throw an error).

Because the isManageable boolean value is always false when the parent component is activated, it ensure that the callback function is only ever executed when a new Avatar component is added using the Add Terrarium button - never when they are rendered as part of their parent component.

data()

This field has only one property: localName - declared as an empty String. This is where an updated name is temporarily stored. After a new name has been successfully assigned this value is updated by:

watch:

This option adds a watcher to the external name property, which when triggered by a state change will assign the new value to the localName property.

Because watchers are lazy by default they don't fetch initial data - instead only fetching it when the state changes. To fix this, we declare the callback as an object with a handler() function and the immediate = true option to create an eager watcher. The result of this is that the localName property will correlate with the name property other than when the former is temporarily storing a new value.

props:

Here, there are two reuired external props declared:

  • image - this is a required String used to determine which image asset should be assigned.
  • size - String value that determines both the height and width CSS-properties. Default is "150px".
  • name - required String that determines the name of the Avatar.
computed:

Here the full file path for the correct image asset is returned using imageSrc(). The function will concatenate the value of the name property with the commonly shared part of the file path, which otherwise follows the same convention for all the Avatar image assets.

Implementation Notes

In order to import and use this component, the two required external props must be passed on from the parent component. It is also important to carefully consider the Vue-syntax and use the same name for this component's props where it is declared inside the parent component.

Related Components

The primary intended use of this component is within SelectTerrariumPage.

Background.vue

This component contains the background image that is present throughout all component pages and is therefore present in the <template> section of DefaultPageLayout.vue. The background makes use of a jpg that is styled to cover the entire page and repeat on the y-axis.

Chart.vue

The purpose of this Vue component is to render a line graph based on sensor data fetched from a database. It provides a customizable and reusable chart visualization for displaying sensor readings.

The component consists of an HTML template that includes a canvas element with a unique ID, width, and height. The ID can be automatically generated or provided by the user.

The component imports two JavaScript modules: chartGenerator.js and fetch.js. The chartGenerator.js module provides the functionality to generate a line graph using the Chart.js library, while the fetch.js module handles the fetching of sensor data from the database.

Upon mounting, the component fetches sensor data within the specified time range using the readSensorRange function from the fetch.js module. It checks if the fetched data contains entries for the specified sensor type. If the data is available, it extracts the values from the data object and generates a line graph using the generateLineGraph function from the chartGenerator.js module. The generated graph is rendered within the canvas element.

Before the component is created, it determines the canvas ID. If an ID is provided as a prop, it is used directly. Otherwise, a unique ID is generated and assigned to the canvasID property of the component.

Overall, this Vue component encapsulates the functionality to render a customizable line graph based on sensor data. It dynamically fetches the data from the database, generates the graph, and displays it within the component's template.

DefaultPageLayout.vue

The purpose of this component is to is to consolidate essential website elements into a unified container to consistently display them throughout the website. This consists of: the SiteHeader, the SiteFooter, and the Background.

Technical description

An event listener is attached to the Header.vue component that is embedded in the <template>, which when triggered will re-emit the same event as mentioned in the documentation for the AccessButton component.

Related Components

Imports and uses:

Footer.vue

This component represents the footer element that is present throughout all component pages and is therefore present in the <template> section of DefaultPageLayout.vue.

Header.vue

Description

The Header component contains a slot for a logo, a navigation bar, and a Log In button. It serves as the header section of a web page. The navigation bar differs based on if the user is logged in or not. Non logged in users can access Home, About, Help and contact. When the user is logged in additional access to Your Terrariums and settings is granted.

Technical description

The Header component contains a HTML nav element that checks the value of the prop boolean IsLoggedIn that is injected from the vue app instance. A v-if checks weather the IsLoggedIn is false, if it is links for non logged in users are the displayed. If it is true a v-else triggers and full access to all page links is granted.

How to use:

In addition 3 general steps defined for all components you can also modify the component styling.

  • Customise: The Header component includes CSS styling, which can be customised by modifying the corresponding CSS classes within the component's style scoped section. The styles being scoped means they are only applied to the elements within the component itself. Styles for link color and font is set by the global.css stylesheet found under Assets in the directory but can be overridden in the <style scoped> section of the component.

Interact with the component: The component holds a logo image that can be clicked to go back to the home screen from any other page in the application. The navbar links can also be clicked to from all screens and re routes you to the specific page that was clicked. The header contains a log in button which is a child component, the behaviour of the log in button can be found under AccessButton.

Child components

AccessButton : Communication between the parent an child components happens through @accessButtonEvent which is emitted by AccessButton.

Troubleshooting

  • Problem: I can not access the Your Terrariums page.
  • Solution: The most likely cause of this is that you are not logged in to your account. Click the log in button in the top right corner of the page. This will redirect you to the log in page where you can fill in your account credentials. When logged in the Your Terrariums page should appear as an option in the navbar. If you need further support contact the Terminarium Support Team.

Homepage.vue

This component represents the home page/landing page of the website. It presents two primary element types each contained in their own wrapper: text to the left side (that includes a heading and welcome text) and an embedded YouTube video to the right side.

The YouTube video is embedded using functionality provided by the external Vue-Plyr component.

MonitorTerrarium.vue

Description

The MonitorTerrarium components displays environment values from a enclosure fitted with the Wio terminal system that is equipped with 6 sensors. The component displays sensor types, current sensor readings, 1 hour average readings, 24 hour average readings and graphs indicating sensor reading fluctuations over time. Values in the current readings column are displayed in green (good) or red (bad) based on wether they are within the users defined environment ranges. This makes it possible for users to quickly determine if the wellness of their enclosure in real time.

Technical description

This component makes use of majority of the Back-End modules that are further documented as part of the Modules section of this document.

Further documentation

Each sensor type is represented by a row in the grid, and each row contains columns for the sensor type, current reading, 1-hour average, 24-hour average, and a graph. The current reading is displayed along with a color indicator based on whether it falls within user-defined ranges. Green indicates the reading is within the range, red indicates it's outside the range, and white is the default color.

The component uses the Chart component imported from "@/components/Chart.vue" to display the graphs for each sensor type. The Chart component is passed various props such as the sensor type, chart range, height, and width.

The component also includes a button that allows the user to set acceptable sensor ranges for the system as a whole. When clicked, it redirects the user to the SetSensorRanges component and the user can set the ranges for each sensor type.

Internally, the component makes use of the averageSensorValues function from the "@/modules/averageValues.js" module to calculate average values for each sensor type. It also uses the parseVibrationData function from the "@/modules/utils" module to parse and format vibration data.

Overall, this component provides a visually organized display of real-time sensor data with color indicators and graphs for easy monitoring and analysis.

Register.vue

This component represents a user account registration page that allows the user to input a Name, Username and Password for the first time and store the values to local storage. The component includes a form with corresponding input fields, a register/submission button, and two prominent text elements that serve as page headings.

In the computed() field of script, the isEmpty() function pays attention to whether the user has filled out all 3 forms and returns a corresponding boolean. Depending on its value, the registration button element is styled to either be grayed out and unclickable, or colored green and clickable. Clicking it when enabled saves the user input to local storage.

SelectTerrariumPage.vue

SelectTerrariumPage serves as the entry point for the user to access and manage their terrariums, displayed as a list of Avatars, alongside the connected Wio Terminal, and provides detailed information for a specific enclosure through the monitoring board.

Technical Description

This component renders a list of Avatar components in a horisontal row using a v-for loop, with the necessary data that needs to be passed on as external props stored inside a local array. Each unique Avatar instance will automatically be assigned a unique key using the index of its position in the array, which is needed for each element to be rendered properly. Thus an instance of the child component can emit an event which includes its identity for which the SelectTerrariumPage component has declared a listener. Consistency is assured by the nature of how v-for will automatically update the assigned keys whenever the internal ordering in the local array changes.

Underneath the list of terrariums, a manage terrariums button lets the user toggle the isManaging boolean value to be able to set a new image and name for the Avatars - or delete them.

An Add Terrarium button is rendered to the right of the Avatars (or as the only element in case there are none) which let's the user add a new Terrarium, displayed as an additional Avatar in the list. When such an event occurs, this component will change isManaging to true, which is also tied with the Avatar component's isEditable prop using v-bind in the component declaration - equivalent to the user manually clicking the manage terrariums button, while also focusing the name input field for the newly added Avatar. A maximum of 5 terrariums are allowed at once, and when the list is full, the last Avatar added will be displayed in place of the Add Terrarium button.

Perhaps the most important feature of this page, is the MonitorTerrarium child component. It lets the user monitor their enclosures by displaying the current readings, statistical averages, and graphs - all fetched live from the Firebase Realtime Database.

Noteworthy Options:

deactivated()

Since this component's instance is cached when this component is removed from the DOM, due to it being wrapped in the <KeepAlive> tags - where it is declared in App.vue, this option is added to ensure that the isManaging value is set to false. Otherwise it will interfere with code executed inside the mounted() option of the Avatar component.

data()

This field stores the local array terrariumList, holding necessary data for the Avatar component(s), as well as an imageAssetsList which is iterated through when the user cycles through the available images for the Avatars.

Implementation Notes

For optimal user experience, this component should be wrapped within KeepAlive tags in order to cache its instance when removed from the DOM.

Related Components

This component implements the following two child components:

SetSensorRanges.vue

The SetSensorRanges component is used to set the desired environment ranges for terrarium enclosures via the website. It allows the user to input minimum and maximum values for five different sensors: moisture, light, loudness, temperature, and humidity. The component handles validation of the ranges and publishes them to a mqtt broker. The component also saves and retrieves ranges using local storage to achieve persistent ranges between sessions in the web browser.
How to use:

In addition 3 general steps defined for all components you can also modify the component styling.

  • Customise: The SetSensorRanges component includes CSS styling, which can be customised by modifying the corresponding CSS classes in the component's style scoped section. Changes in style scoped only applies to elements within the component. style scoped overrides styles set by the global.css style sheet.

Interact with the component: The component provides input fields for setting the desired environment ranges for terrarium enclosures. The user can input the minimum and maximum values for five different sensors: moisture, light, loudness, temperature, and humidity. The component handles validation of the ranges and publishes them to an MQTT broker. The ranges are also saved in local storage for persistence.

created

This component uses the created lifecycle because it allows for the population of data before the component is mounted to the DOM this ensures that all data is displayed correctly when the component is used. To populate the data a for loop Iterates over all sensor names in the sensorNames array. During every iteration of the loop min and max is set for a sensor. The local storage object is accessed and given a key, if the key is mapped to a value in local storage the value is set to a sensors min and max data. If the key is not mapped to a value the data is set to 0.

After populating the range data the window objects scroll property is accessed so that the user enters the set sensor ranges page at the top. This is necessary as the button to enter the page from the Your terrariums page can be located far down the screen on smaller devices.

Troubleshooting

Problem: The save button does not save my ranges. Solution: The most likely cause of this is invalid ranges. If your ranges are invalid a message under the save button appears with information about why the ranges are and a error is logged in the console. Make sure all ranges comply with the rules and try to save again!

UserSettings.vue

This component represents a user settings page which allows the user to modify their Name, Username and Password and store the new values in local storage. To achieve this, the component includes a form with corresponding input fields and a save/submission button.

In the created() field of script, the component loads the existing user credentials from local storage so that they appear already in the input fields.

When user data is inputted successfully, a confirmation message appears. If no changes are detected, a negative message appears. To detect this difference, the component uses the boolean variables isEmpty (checks that at least 1 input field is not empty, so that something can be saved) and isSaved (checks if a save was successful, so that the corresponding message appears).

Modules - Backend

This section documents the modules that are part of terminarium-web

averageValues.js

The averageValues.js has the single purpose of calculating the average values for all sensors given a specific time range. It exports a function called averageSensorValues.

This is accomplished in the averageSensorValues(hours) function. The hours parameter defines how far back in hours from the current time data will be fetched. The function makes use of the readSensorRange() function in fetch.js to fetch the actual data from the Database in order to then calculate the average values.

The function returns a promise which resolves to a map containing all sensor values with the following structure: Key: Sensor name, Value: Average sensor reading. The average value in itself is calculated by summing all the values and dividing by the number of entries.

chartGenerator.js

This JavaScript file serves as a chart generator module. It exports a function called generateLineGraph that utilizes the Chart.js library to generate line graphs.

A constant graphColors is defined, which is an immutable object mapping sensor types to colors. Each sensor type is associated with a specific color in RGBA format.

The generateLineGraph function takes three parameters: chartData (an array of values to be displayed on the chart), elementID (the ID of the HTML element that will contain the chart), and sensorType (the type of sensor that the chart will display).

A configuration object is created, specifying the type of graph (line), the chart data, line and area colors, and various options for customizing the appearance of the chart. The options include hiding the legend, disabling animation, preventing resizing of the chart canvas, hiding the x and y axes, and hiding the points on the line. This way, we're able to achieve a minimalistic, clean look for the chart, just like the one in the Figma design.

Finally, a new instance of the Chart class is created using the canvas context and the configuration object, and it is returned.

config.js

This JavaScript file defines the Firebase configuration object using Vite environment variables. It securely stores the configuration values for connecting to the Firebase Realtime Database. The firebaseConfig object exports the configuration values obtained from the environment variables defined in the .env file. By separating the configuration into environment variables, it ensures security and flexibility in different environments.

database.js

Contains code that interacts with the Firebase Realtime Database. It imports necessary functions from the Firebase SDK and initializes the Firebase app using the configuration object (see below). It sets up a listener to trigger whenever connection status is established or lost.

Most importantly, it defines a function for writing data to the database. When called, the write function is passed a String representing the database address where the data should be stored, and the data value itself. The address String is exactly the MQTT topic name corresponding to the sensor data type, which is formatted in a way that conveniently matches the formatting conventions of the tree-like structure of Firebase Realtime Database (e.g. /terminarium/sensor/<sensor_type>/<timestamp>/<value>).

fetch.js

The purpose of this JavaScript file is to provide functionality for fetching (reading) data from a Firebase database. It exports a function called readSensorRange that retrieves data entries from the database for a specified number of hours.

The code begins by importing necessary Firebase modules and a configuration file. It initializes the Firebase application and obtains a reference to the database.

The readSensorRange function takes a parameter hours, which represents the number of hours to go back in time when fetching the data. It returns a promise that resolves to a Map containing the data entries in an object format. Each sensor type is represented as a key in the Map, and the corresponding value is an object that contains timestamps and their corresponding values.

The function utilizes Firebase's onValue listener to listen for changes in the specified database reference. It iterates through all the sensors and their timestamps in the database. For each timestamp, it formats the timestamp using the formatDateTime function from the utils.js module. It then calculates the time difference between the formatted timestamp and the current time.

If the time difference is less than the specified number of hours, the entry is added to the result Object. The result Object is organized based on sensor types, where a sensor type is given a Object of the timestamp and the recorded value.

Example: { 'temperature': { timestamp: , value: }, 'humidity': { timestamp: , value: }, ... }

Finally, the promise is resolved with the populated result Object.

Overall, this JavaScript file provides a convenient function to retrieve data entries from a Firebase database within a specified time range and format them for further processing or visualization, such as populating a chart.

mqtt_config.js

This JavaScript file is responsible for configuring the MQTT client for the web client's sensor readings. It establishes a connection to the MQTT broker and defines callbacks for connection events.

The onConnect function is called when the connection to the MQTT broke is successfully made. It logs the connection details and subscribes to the "/terminarium/sensor/#" topic to receive sensor readings. That is, all live sensor readings.

The onConnectionLost function is invoked when the connection is lost. It logs an error message if the loss of connection is accompanied by an error.

Overall, this JavaScript file sets up the MQTT client for the web client's sensor readings. It establishes the connection to the MQTT broker, handles connection events, and ensures the client is subscribed to the appropriate topic for receiving sensor data.

utils.js

This Utils JavaScript file contains utility functions for various purposes:

  • formatDateTime(): Formats a date and time string from a database convention (dd-mm-yyyy;hh:mm) to the default format (yyyy-mm-ddThh:mm).
  • threeDigitFormat(): Ensures that a number is always three digits long by padding it with leading zeros if necessary.
  • MQTTClientInit(): Initializes an MQTT client using the HiveMQ public broker and the Paho library. Returns a new MQTT client.
  • generateUniqueID(): Generates a pseudo-unique number to be used as a client ID for the MQTT client.
  • createTimestamp(): Formats the current time to a string in the format yyyy-mm-ddThh:mm, which is a convention used in the database to name nodes.
  • parseVibrationData(): This function simply returns 'Yes' if the "raw" value is 'true', otherwise it returns 'No'. This is because the vibration sensor returns a 'boolean' value (from the database), but we want to display 'Yes' or 'No' on the web client.

These utility functions provide convenient methods for formatting dates, generating unique IDs, and working with MQTT clients.

⚠️ **GitHub.com Fallback** ⚠️