Merge branch 'master' of git.reim.ar:ReiMerc/temperature-alarm
This commit is contained in:
commit
6b4445aa2f
41
README.md
41
README.md
@ -1,2 +1,43 @@
|
|||||||
# Temperature Alarm
|
# Temperature Alarm
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
20
backend/Api/appsettings.example.json
Normal file
20
backend/Api/appsettings.example.json
Normal 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": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
@ -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
BIN
docs/img/beaglebone.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 498 KiB |
BIN
docs/img/beaglebone_small.jpg
Normal file
BIN
docs/img/beaglebone_small.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
BIN
docs/img/dashboard.png
Normal file
BIN
docs/img/dashboard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 192 KiB |
BIN
docs/img/dashboard_small.png
Normal file
BIN
docs/img/dashboard_small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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) {
|
||||||
|
@ -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,
|
||||||
});
|
});
|
||||||
|
@ -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() {
|
||||||
|
@ -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)
|
||||||
) {
|
) {
|
||||||
|
Loading…
Reference in New Issue
Block a user