Codebase Documentation - rokanas/terminarium GitHub Wiki
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.
- Codebase Documentation
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.
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
andMQTT
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.
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.
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.
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.
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
andMQTT
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
andMQTT
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
andMQTT
connection screens).
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).
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
.
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 aString
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.
This section documents the Terminarium Web Application codebase
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.
This section documents the Vue
components.
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.
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:
-
Import the component:
import SetSensorRanges from './components/Component_Name';
-
Register the component within your Vue instance or other component:
export default { components: { Component_Name }, // ... }
-
Use the component in your template:
<template> <div> <Component_Name /> </div> </template>
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 aboolean
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 theboolean
value stored in thesensorValues
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
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.
This component provides a dynamic button that displays either "Log in" or "Log out" based on the user's login status.
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:
In order for this component to determine the current login status it injects a boolean
property isLoggedIn
, provided by the App.vue
component.
The login status is checked inside a computed value accessButtonDescription
, which dynamically returns the appropriate String
label for the button.
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
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.
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.
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.
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:
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.
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 widthCSS
-properties. Default is"150px"
. - name - required
String
that determines the name of theAvatar
.
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.
The primary intended use of this component is within SelectTerrariumPage
.
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.
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.
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.
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.
Imports and uses:
This component represents the footer element that is present throughout all component pages and is therefore present in the <template>
section of DefaultPageLayout.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.
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.
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.
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
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.
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:
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.
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
.
This component implements the following two child components:
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.
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!
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).
This section documents the modules that are part of terminarium-web
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.
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.
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.
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>
).
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.
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.
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 anMQTT
client using theHiveMQ
public broker and thePaho
library. Returns a new MQTT client. -
generateUniqueID()
: Generates a pseudo-unique number to be used as a client ID for theMQTT
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.