Merge branch 'master' of git.reim.ar:ReiMerc/temperature-alarm

This commit is contained in:
Jeas0001 2025-04-10 12:22:26 +02:00
commit 6b4445aa2f
16 changed files with 154 additions and 143 deletions

View File

@ -1,2 +1,43 @@
# Temperature Alarm # Temperature Alarm
![Picture of the BeagleBone](docs/img/beaglebone_small.jpg)
![Screenshot of the dashboard](docs/img/dashboard_small.png)
## Setup
In the `backend/Api` folder, copy `appsettings.example.json` into `appsettings.json` and fill out the values.
In the `frontend` folder, copy `shared/constants.example.js` into `shared/constants.js` and fill out the values.
In the `iot` folder, copy `config.example.h` into `config.h` and fill out the values.
## Running
To run the backend, type `dotnet run` within the `backend/Api` folder. Make sure you have the .NET 8 SDK installed.
The frontend should just be hosted with a static web server. To run locally, you may e.g. use PHP: `php -S 0.0.0.0:80`
The IoT code should be run on a BeagleBone Black with:
- A Grove Base Cape, set to 5V
- A JHD1313 Grove-LCD RGB Backlight display in one of the I2C\_2 ports
- An MCP9808 Grove High Accuracy Temperature Sensor in one of the I2C\_2 ports
- A Grove Buzzer in the GPIO\_51 port
The following library packages are required by the IoT code and should be installed on the BeagleBone:
`apt install libmosquitto-dev librabbitmq-dev libi2c-dev libgpiod-dev libcjson-dev`
> [!NOTE]
> The gpiod library must be below version 2.0. Debian Bookworm still distributes the old version of the library.
> [!NOTE]
> The RabbitMQ apt package is missing the header files - They can be copied from [here](https://github.com/alanxz/rabbitmq-c/tree/master/include/rabbitmq-c) into the `/usr/include/rabbitmq-c` folder
Copy the source files onto the BeagleBone and run the following command to compile and run the code:
`make && ./a.out`
To run in the background, a program such as GNU Screen can be used.

View File

@ -41,7 +41,7 @@ namespace Api.AMQPReciever
var messageReceive = JsonSerializer.Deserialize<MessageReceive>(message); var messageReceive = JsonSerializer.Deserialize<MessageReceive>(message);
// Checks if the message has the data we need // Checks if the message has the data we need
if (messageReceive == null || messageReceive.device_id == null || messageReceive.timestamp == 0) if (messageReceive == null || messageReceive.device_id == null || messageReceive.timestamp == 0 || messageReceive.temperature > 200.0)
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@ -0,0 +1,20 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Database": "Data Source=database.sqlite3"
},
"JwtSettings": {
"Issuer": "TemperatureAlarmApi",
"Audience": "Customers",
"Key": ""
}
}

View File

@ -1,36 +0,0 @@
using MQTTnet;
var mqttFactory = new MqttClientFactory();
IMqttClient mqttClient;
using (mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer($"10.135.51.116", 1883)
.WithCredentials($"h5", $"Merc1234")
.WithCleanSession()
.Build();
// Setup message handling before connecting so that queued messages
// are also handled properly. When there is no event handler attached all
// received messages get lost.
mqttClient.ApplicationMessageReceivedAsync += e =>
{
Console.WriteLine("Received application message.");
return Task.CompletedTask;
};
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
Console.WriteLine("mqttClient");
//var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(topic).Build();
await mqttClient.SubscribeAsync("temperature");
Console.WriteLine("MQTT client subscribed to topic.");
Console.WriteLine("Press enter to exit.");
Console.ReadLine();
}

View File

@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MQTTnet" Version="5.0.1.1416" />
</ItemGroup>
</Project>

BIN
docs/img/beaglebone.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
docs/img/dashboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -4,6 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>Register - Temperature Alarm</title> <title>Register - Temperature Alarm</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/styles/common.css">
<link rel="stylesheet" href="/styles/auth.css"> <link rel="stylesheet" href="/styles/auth.css">
<link rel="stylesheet" href="/styles/profile.css"> <link rel="stylesheet" href="/styles/profile.css">
<script defer type="module" src="/scripts/profile.js"></script> <script defer type="module" src="/scripts/profile.js"></script>
@ -67,7 +68,7 @@
<button type="submit">Change Password</button> <button type="submit">Change Password</button>
<div id="form-error"></div> <div id="password-error" class="error"></div>
</div> </div>
</form> </form>
</div> </div>

View File

@ -10,14 +10,15 @@ document.getElementById("loginForm").addEventListener("submit", function(event)
login(emailOrUsername, password) login(emailOrUsername, password)
.then(response => { .then(response => {
document.cookie = `auth-token=${response.token}; Path=/`; if(response.token && response.refreshToken){
document.cookie = `refresh-token=${response.refreshToken}; Path=/`; document.cookie = `auth-token=${response.token}; Path=/`;
localStorage.setItem("user", JSON.stringify({ document.cookie = `refresh-token=${response.refreshToken}; Path=/`;
id: response.id, localStorage.setItem("user", JSON.stringify({
username: response.userName, id: response.id,
})); username: response.userName,
}));
location.href = "/home"; location.href = "/home";
}
}) })
.catch(error => { .catch(error => {
document.getElementById("form-error").innerText = error; document.getElementById("form-error").innerText = error;

View File

@ -8,10 +8,10 @@ var username;
var email; var email;
get().then((res) => { get().then((res) => {
username = res.userName; username = res.userName;
email = res.email; email = res.email;
var table = document.getElementById(`profileCard`); var table = document.getElementById(`profileCard`);
table.innerHTML += ` table.innerHTML += `
<div class="pfp"> <div class="pfp">
<img style="width:200px; height:200px" src="${profileData.pfp}"> <img style="width:200px; height:200px" src="${profileData.pfp}">
</div> </div>
@ -43,79 +43,76 @@ usernameInput.addEventListener("input", checkForChanges);
// Open modals // Open modals
editIconbtn.onclick = () => { editIconbtn.onclick = () => {
document.getElementById("uname").value = username; document.getElementById("uname").value = username;
document.getElementById("email").value = email; document.getElementById("email").value = email;
submitBtn.disabled = true; submitBtn.disabled = true;
editModal.style.display = "block"; editModal.style.display = "block";
}; };
passwordBtn.onclick = () => (pswModal.style.display = "block"); passwordBtn.onclick = () => (pswModal.style.display = "block");
// Close modals when clicking on any close button // Close modals when clicking on any close button
document.querySelectorAll(".close").forEach((closeBtn) => { document.querySelectorAll(".close").forEach((closeBtn) => {
closeBtn.onclick = () => { closeBtn.onclick = () => {
pswModal.style.display = "none"; pswModal.style.display = "none";
editModal.style.display = "none"; editModal.style.display = "none";
document.getElementById("form-error").innerText = ""; document.getElementById("form-error").innerText = "";
document.getElementById("form-error").style.display = "none"; document.getElementById("form-error").style.display = "none";
}; };
}); });
// Close modals when clicking outside // Close modals when clicking outside
window.onclick = (event) => { window.onclick = (event) => {
if (event.target == pswModal || event.target == editModal) { if (event.target == pswModal || event.target == editModal) {
pswModal.style.display = "none"; pswModal.style.display = "none";
editModal.style.display = "none"; editModal.style.display = "none";
document.getElementById("form-error").innerText = ""; document.getElementById("form-error").innerText = "";
document.getElementById("form-error").style.display = "none"; document.getElementById("form-error").style.display = "none";
} }
}; };
document document
.getElementById("editForm") .getElementById("editForm")
.addEventListener("submit", function (event) { .addEventListener("submit", function (event) {
event.preventDefault(); // Prevents default form submission event.preventDefault(); // Prevents default form submission
document.getElementById("form-error").style.display = "none"; document.getElementById("form-error").style.display = "none";
// Call function with form values // Call function with form values
update(emailInput.value, usernameInput.value).then((response) => { update(emailInput.value, usernameInput.value).then((response) => {
if (response?.error) { if (response?.error) {
document.getElementById("form-error").innerText = response.error; document.getElementById("form-error").innerText = response.error;
document.getElementById("form-error").style.display = "block"; document.getElementById("form-error").style.display = "block";
return; return;
} }
location.href = "/profile"; location.href = "/profile";
});
}); });
});
document document
.getElementById("PasswordForm") .getElementById("PasswordForm")
.addEventListener("submit", function (event) { .addEventListener("submit", function (event) {
event.preventDefault(); // Prevents default form submission event.preventDefault(); // Prevents default form submission
document.getElementById("form-error").style.display = "none"; document.getElementById("password-error").style.display = "none";
const oldPassword = document.getElementById("oldpsw").value; const oldPassword = document.getElementById("oldpsw").value;
const newPassword = document.getElementById("psw").value; const newPassword = document.getElementById("psw").value;
const repeatPassword = document.getElementById("rpsw").value; const repeatPassword = document.getElementById("rpsw").value;
if (newPassword !== repeatPassword) { if (newPassword !== repeatPassword) {
let errorDiv = document.getElementById("form-error"); document.getElementById("password-error").style.display = "block";
errorDiv.style.display = "block"; document.getElementById("password-error").innerText = "Passwords do not match!";
errorDiv.innerText = "Passwords do not match!"; return;
return; }
}
updatePassword(oldPassword, newPassword).then((response) => { updatePassword(oldPassword, newPassword)
//error messages do not work .then(() => location.reload())
if (response.error) { .catch(error => {
document.getElementById("form-error").innerText = response.message; document.getElementById("password-error").innerText = error;
document.getElementById("form-error").style.display = "block"; document.getElementById("password-error").style.display = "block";
return; });
}
}); });
});
document.querySelector(".logout-container").addEventListener("click", logout); document.querySelector(".logout-container").addEventListener("click", logout);

View File

@ -5,7 +5,7 @@ export function getDevices() {
} }
export function add(referenceId) { export function add(referenceId) {
return request("POST", `/device/adddevice/${referenceId}`); return request("POST", `/device/add/${referenceId}`);
} }
export function deleteDevice(deviceId) { export function deleteDevice(deviceId) {

View File

@ -39,7 +39,7 @@ export function update(email, username){
} }
export function updatePassword(oldPassword, newPassword){ export function updatePassword(oldPassword, newPassword){
return request("PUT", "/user/update-password", { return request("PUT", "/user/change-password", {
oldPassword, oldPassword,
newPassword, newPassword,
}); });

View File

@ -41,36 +41,36 @@ export async function request(method, path, body = null) {
export function checkTokens() { export function checkTokens() {
var token = document.cookie.match(/\bauth-token=([^;\s]+)/); var token = document.cookie.match(/\bauth-token=([^;\s]+)/);
if(token != null){ if (token != null) {
return token[1] return token[1];
} }
const match = document.cookie.match(/\brefresh-token=([^;\s]+)/); const match = document.cookie.match(/\brefresh-token=([^;\s]+)/);
token = match ? match[1] : null; token = match ? match[1] : null;
console.log("refresh "+token);
if(token != null){
return fetch(`${address}/user/refreshtoken/${token}`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
})
.then(async response => {
if (!response.ok) {
window.location.href = "/login";
return;
}
const json = await response.json() if (token != null) {
return fetch(`${address}/user/refreshtoken/${token}`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
})
.then(async response => {
if (!response.ok) {
location.href = "/login";
return;
}
document.cookie = `auth-token=${json.token}; Path=/`; const json = await response.json()
document.cookie = `refresh-token=${json.refreshToken}; Path=/`;
return json.token; document.cookie = `auth-token=${json.token}; Path=/`;
}); document.cookie = `refresh-token=${json.refreshToken}; Path=/`;
}
else{ return json.token;
window.location.href = "/login"; });
} }
location.href = "/login";
} }
export function logout() { export function logout() {

View File

@ -78,6 +78,7 @@ void *watch_temperature(void *arg)
// Sound alarm if applicable // Sound alarm if applicable
if ( if (
temperature < 200.0 &&
min_temperature != NAN && max_temperature != NAN && min_temperature != NAN && max_temperature != NAN &&
(temperature < min_temperature || temperature > max_temperature) (temperature < min_temperature || temperature > max_temperature)
) { ) {