diff --git a/.gitignore b/.gitignore
index 9adcc01..f46ede8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -91,7 +91,11 @@ fabric.properties
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
+# Do not add specific ide files
+.idea/*
+
config.json
.env
+# for local config
setup_env.sh
diff --git a/web-app/README.md b/web-app/README.md
index dd13668..a519bfe 100644
--- a/web-app/README.md
+++ b/web-app/README.md
@@ -1,19 +1,65 @@
# web-app
+This is a web application that uses Vue.js and Chart.js to display data from a database. The data is fetched from the database using an API and displayed in a chart.
-## Project setup
+## Usage
+The web page contains two buttons:
+- **New measurments**: fetch the data from the database and add it to the chart.
+- **Fetch measurments**: ask for a new measurement at the currrent time.
+
+It has too 3 selectors:
+- **User**: select the user to fetch the data from.
+- **Room**: select the room to fetch the data from.
+- **Device**: select the device to fetch the data from.
+
+The chart is updated when the web page is loaded with default values for the selectors.
+
+The chart can be updated by clicking on the **New measurments** button after selecting the user, room and device. But, the application doesn't care if the combination of user, room and device is valid or not. It will just fetch the data from the database and plot it on the chart.
+
+## Environment setup
+
+Install Node.js version v22.15.0 from [Node.js](https://nodejs.org/en/download/releases/) (LTS version).
+
+Verify the installation by running the following command in your terminal:
+```terminal
+node --version
+npm --version
```
+With the following output:
+```result
+v22.15.0
+10.9.2
+```
+
+Then install the project dependencies by running:
+```terminal
npm install
```
-### Compiles and hot-reloads for development
+Setup the environment:
+
+linux:
+```terminal
+export VUE_APP_API_KEY=your_api_key
+export VUE_APP_API_SECRET=your_api_secret
```
+windows:
+```terminal
+# use wsl ...
+```
+
+### Compile and run for development
+
+```terminal
npm run serve
```
-### Compiles and minifies for production
-```
+### Compile and minifie for production
+```terminal
npm run build
```
-### Customize configuration
-See [Configuration Reference](https://cli.vuejs.org/config/).
+### Links
+See [Configuration Reference](https://cli.vuejs.org/config/).\
+See [Vue.js](https://vuejs.org/guide/introduction.html).\
+See [chartjs](https://www.chartjs.org/docs/latest/).\
+See [vue-chartjs](https://vue-chartjs.org/).
diff --git a/web-app/package-lock.json b/web-app/package-lock.json
index 7688ea6..3f6e015 100644
--- a/web-app/package-lock.json
+++ b/web-app/package-lock.json
@@ -9,7 +9,8 @@
"version": "0.1.0",
"dependencies": {
"axios": "^1.9.0",
- "chartjs": "^0.3.24",
+ "chart.js": "^4.4.9",
+ "chartjs-adapter-date-fns": "^3.0.0",
"dotenv": "^16.5.0",
"vue": "^3.2.13",
"vue-chartjs": "^5.3.2",
@@ -371,8 +372,7 @@
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/@leichtgewicht/ip-codec": {
"version": "2.0.5",
@@ -2114,7 +2114,6 @@
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz",
"integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@kurkle/color": "^0.3.0"
},
@@ -2122,11 +2121,15 @@
"pnpm": ">=8"
}
},
- "node_modules/chartjs": {
- "version": "0.3.24",
- "resolved": "https://registry.npmjs.org/chartjs/-/chartjs-0.3.24.tgz",
- "integrity": "sha512-h6G9qcDqmFYnSWqjWCzQMeOLiypS+pM6Fq2Rj7LPty8Kjx5yHonwwJ7oEHImZpQ2u9Pu36XGYfardvvBiQVrhg==",
- "license": "MIT"
+ "node_modules/chartjs-adapter-date-fns": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-3.0.0.tgz",
+ "integrity": "sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "chart.js": ">=2.8.0",
+ "date-fns": ">=2.0.0"
+ }
},
"node_modules/chokidar": {
"version": "3.6.0",
@@ -2904,6 +2907,17 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
+ "node_modules/date-fns": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+ "license": "MIT",
+ "peer": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
"node_modules/debounce": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
diff --git a/web-app/package.json b/web-app/package.json
index 827a5f4..eef2374 100644
--- a/web-app/package.json
+++ b/web-app/package.json
@@ -8,7 +8,8 @@
},
"dependencies": {
"axios": "^1.9.0",
- "chartjs": "^0.3.24",
+ "chart.js": "^4.4.9",
+ "chartjs-adapter-date-fns": "^3.0.0",
"dotenv": "^16.5.0",
"vue": "^3.2.13",
"vue-chartjs": "^5.3.2",
diff --git a/web-app/src/App.vue b/web-app/src/App.vue
index 2b70f69..4ccb4fc 100644
--- a/web-app/src/App.vue
+++ b/web-app/src/App.vue
@@ -9,24 +9,12 @@
@@ -38,7 +26,7 @@ import ChartComponent from "./components/ChartComponent.vue";
import ControlPanel from "./components/ControlPanel.vue";
import { HttpClient } from "./Services/HttpClient";
import { TimeSeriesManager } from "./Measures/TimeSeriesManager";
-import { URL, USERNAME, PASSWORD, USER, ROOM, DEVICE, APP_NAME } from "./const";
+import { URL, USERNAME, PASSWORD, APP_NAME } from "./const";
import { Serie } from "./Measures/Serie";
let httpClient = new HttpClient(URL, USERNAME, PASSWORD);
@@ -53,53 +41,8 @@ export default defineComponent({
data() {
return {
manager,
- series: [] as any[],
- loading: true,
- error: undefined as string | undefined,
- showTemperature: true,
- showHumidity: true,
};
},
- methods: {
- addTemperature() {
- // Implement your functionality here
- console.log("Add temperature clicked");
- manager
- .getNewValue(USER, ROOM, DEVICE)
- .then((result) => {})
- .catch((error) => {
- console.error("Error asking data:", error);
- this.error = "Failed to load data. Please try again later.";
- this.loading = false;
- });
- },
-
- refreshData() {
- console.log("Refreshing data...");
- this.fetchData();
- },
-
- fetchData() {
- this.loading = true;
- this.error = undefined;
-
- manager
- .getTimeSeriesData(USER, ROOM, DEVICE)
- .then((result) => {
- this.series = result;
- this.loading = false;
- })
- .catch((error) => {
- console.error("Error fetching data:", error);
- this.error = "Failed to load data. Please try again later.";
- this.loading = false;
- });
- },
- },
- mounted() {
- // Fetch data when component is mounted
- this.fetchData();
- },
});
diff --git a/web-app/src/Measures/Serie.ts b/web-app/src/Measures/Serie.ts
index 9fa8c99..cb9a026 100644
--- a/web-app/src/Measures/Serie.ts
+++ b/web-app/src/Measures/Serie.ts
@@ -1,8 +1,10 @@
import { TEMPERATURE, HUMIDITY, TYPE, VALUE } from "../const";
+import { Colors } from "./Utils";
+
export class Serie {
private _type: string;
- private _data: { time: number; value: number }[];
+ private _data: { time: number; value: Date }[];
private _user: string;
private _room: string;
@@ -10,7 +12,7 @@ export class Serie {
constructor(
type: string,
- data: { time: number; value: number }[],
+ data: { time: number; value: Date }[],
user: string,
room: string,
device: string
@@ -22,8 +24,14 @@ export class Serie {
this._device = device;
}
- public getLabel(): string {
- return `${this._type} - ${this._user} - ${this._room} - ${this._device}`;
+ public getLabel(): String {
+ if (this._type === TEMPERATURE) {
+ return "Temperature [°C]";
+ } else if (this._type === HUMIDITY) {
+ return "Humidity [%]";
+ } else {
+ return "Unknown";
+ }
}
public getSerie(): any {
@@ -33,9 +41,10 @@ export class Serie {
data: this._data.map((v: any) => {
return { x: v.time, y: v.value };
}),
- borderColor: "rgba(255, 99, 132, 1)",
- backgroundColor: "rgba(255, 99, 132, 0.2)",
+ borderColor: Colors.BLUE,
+ backgroundColor: Colors.BLUE,
borderWidth: 1,
+ yAxisID: TEMPERATURE,
};
} else if (this._type === HUMIDITY) {
return {
@@ -43,9 +52,21 @@ export class Serie {
data: this._data.map((v: any) => {
return { x: v.time, y: v.value };
}),
- borderColor: "rgba(54, 162, 235, 1)",
- backgroundColor: "rgba(54, 162, 235, 0.2)",
+ borderColor: Colors.GREEN,
+ backgroundColor: Colors.GREEN,
borderWidth: 1,
+ yAxisID: HUMIDITY,
+ };
+ } else {
+ return {
+ label: "Unknown",
+ data: this._data.map((v: any) => {
+ return { x: v.time, y: v.value };
+ }),
+ borderColor: Colors.RED,
+ backgroundColor: Colors.RED,
+ borderWidth: 1,
+ yAxisID: TEMPERATURE,
};
}
}
diff --git a/web-app/src/Measures/TimeSeriesManager.ts b/web-app/src/Measures/TimeSeriesManager.ts
index f5533b7..525b1c4 100644
--- a/web-app/src/Measures/TimeSeriesManager.ts
+++ b/web-app/src/Measures/TimeSeriesManager.ts
@@ -12,12 +12,12 @@ export class TimeSeriesManager {
tag: "remi",
};
selected_room = {
- room: "Bedroom",
- tag: "Bedroom",
+ room: "Terrasse",
+ tag: "Terrasse",
};
selected_device = {
- device: "Door sensor",
- tag: "DoorSensor",
+ device: "Shed",
+ tag: "Shed",
};
user_options = [
@@ -57,16 +57,17 @@ export class TimeSeriesManager {
},
];
+ error = ref(false);
+ loading = ref("");
+
+ series = ref([]);
+
constructor(client: HttpClient) {
this.client = client;
}
- async getTimeSeriesData(
- user: string,
- room: string,
- device: string
- ): Promise {
- return this.client
+ async getTimeSeriesData() {
+ this.client
.getValues(
this.selected_user.tag,
this.selected_room.tag,
@@ -112,20 +113,20 @@ export class TimeSeriesManager {
const temperatureSerie = new Serie(
TEMPERATURE,
temperatureRecordsProcessed,
- user,
- room,
- device
+ this.selected_user.user,
+ this.selected_room.room,
+ this.selected_device.device
);
const humiditySerie = new Serie(
HUMIDITY,
humidityRecordProcessed,
- user,
- room,
- device
+ this.selected_user.user,
+ this.selected_room.room,
+ this.selected_device.device
);
- return [temperatureSerie, humiditySerie];
+ this.series.value = [temperatureSerie, humiditySerie];
})
.catch((error) => {
console.error("Error fetching time series data:", error);
@@ -133,10 +134,16 @@ export class TimeSeriesManager {
});
}
- async getNewValue(user: string, room: string, device: string) {
- this.client.newValue(user, room, device).catch((error) => {
- console.error("Error asking new values:", error);
- throw error;
- });
+ async getNewValue() {
+ this.client
+ .newValue(
+ this.selected_user.user,
+ this.selected_room.room,
+ this.selected_device.device
+ )
+ .catch((error) => {
+ console.error("Error asking new values:", this.selected_device.device);
+ throw error;
+ });
}
}
diff --git a/web-app/src/Measures/Utils.ts b/web-app/src/Measures/Utils.ts
new file mode 100644
index 0000000..89e67ad
--- /dev/null
+++ b/web-app/src/Measures/Utils.ts
@@ -0,0 +1,18 @@
+export const Colors = {
+ RED: "rgb(255, 99, 132)", // Red
+ LIGHT_RED: "rgba(255, 99, 132, 0.2)",
+
+ BLUE: "rgb(54, 162, 235)", // Blue
+ LIGHT_BLUE: "rgba(54, 162, 235, 0.2)",
+
+ YELLOW: "rgb(255, 206, 86)", // Yellow
+ LIGHT_YELLOW: "rgba(255, 206, 86, 0.2)",
+
+ GREEN: "rgb(56, 193, 114)", // Green
+ DARK_GREEN: "rgb(45, 153, 91)",
+
+ ORANGE: "rgb(246, 153, 63)", // Orange
+ DARK_ORANGE: "rgb(230, 126, 34)",
+
+ DARK_BLUE: "rgb(39, 121, 189)", // Dark Blue
+};
diff --git a/web-app/src/components/ChartComponent.vue b/web-app/src/components/ChartComponent.vue
index a2475ee..ff4ac4a 100644
--- a/web-app/src/components/ChartComponent.vue
+++ b/web-app/src/components/ChartComponent.vue
@@ -1,8 +1,8 @@
-
Loading data...
-
{{ error }}
+
Loading data...
+
{{ manager.error }}
No data to display
+
diff --git a/web-app/vue.config.js b/web-app/vue.config.js
new file mode 100644
index 0000000..a76397f
--- /dev/null
+++ b/web-app/vue.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+ devServer: {
+ allowedHosts: 'all'
+ }
+}
\ No newline at end of file