ΜΙΑ ΣΥΝΤΟΜΗ ΠΕΡΙΓΡΑΦΗ ΤΟΥ ΕΡΓΟΥ ΜΑΣ
Η ιδέα για το έργο μας προήλθε από τις καταστροφικές πυρκαγιές στον Έβρο το καλοκαίρι του 2023. Ένα σημαντικό μέρος της δασικής έκτασης του νομού μας καταστράφηκε και ένας λόγος ήταν ότι δεν υπήρξε έγκαιρη ειδοποίηση για την αρχή της πυρκαγιάς που ξεκίνησε στον εθνικό δρυμό της Δαδιάς σε μη κατοικημένη περιοχή.
Η ομάδα μας συνεργάστηκε για να δώσει λύσεις στα παρακάτω:
- Μπορούμε να έχουμε μια έγκαιρη ειδοποίηση για εμφάνιση φωτιάς σε δασικές περιοχές;
- Υπάρχει η δυνατότητα μια απλή εφαρμογή ανοιχτού κώδικα να δείχνει αν υπάρχει παρουσία καπνού σε συγκεκριμένες περιοχές και να στέλνει μήνυμα σε συσκευή κινητού τηλεφώνου android;
- Μπορούμε να έχουμε πολλές συσκευές/ ανιχνευτές που θα λειτουργούν παράλληλα και αυτόνομα;
- Μπορεί η συσκευή ανίχνευσης καπνού εκτός από το σήμα στην εφαρμογή να εκπέμπει επι τόπου σήμα ηχητικό;
Σχεδιάσαμε μια εφαρμογή ανίχνευσης καπνού σε εξωτερικούς και εσωτερικούς χώρους. Μέσω αυτής της εφαρμογής μπορούν να συνδεθούν πολλοί ανιχνευτές καπνού που έχουν την δυνατότητα ρύθμισης του σήματος που αποστέλλουν στο κινητό, σχετικά με την ποσότητα καπνού που ανιχνεύουν και επίσης μπορεί να γίνει επιλογή ελληνικών ή αγγλικών ως γλώσσα των ειδοποιήσεων στην εφαρμογή.
Η συσκευή ανίχνευσης καπνού (κρυμμένη μέσα σε ένα διακοσμητικό γλαστράκι) και η σύνδεσή της με εφαρμογή στο κινητό τηλέφωνο μπορεί να φανεί αποτελεσματική και στην ανίχνευση καπνού σε κλειστούς χώρους, πχ δημόσια κτήρια, εργασιακούς χώρους, εστιατόρια και χώρους διασκέδασης, όπου ενδέχεται να υπάρχει παραβίαση των αντικαπνιστικών κανονισμών.
Οι ανιχνευτές καπνού:
α. Στέλνουν ειδοποίηση σε συσκευή κινητού τηλεφώνου (push notification) σε συχνότητα 3 δευτερολέπτων από την στιγμή ανίχνευσης του καπνού
β. Έχουν την δυνατότητα να αντιδρούν αυτόνομα στον καπνό, με φως που ανάβει δίπλα στον ανιχνευτή, με buzzer (ήχο) και με μια μικρή πινακίδα που ενεργοποιείται και σηκώνεται δίπλα στον ανιχνευτή.
γ. Ο λήπτης των ειδοποιήσεων έχει τη δυνατότητα να δει ποιος ανιχνευτής στέλνει το σήμα και να παύσει ηχητικό / φωτεινό σήμα αν το θεωρεί απαραίτητο.
Ο προγραμματισμός έγινε με λογισμικό ανοικτού κώδικα ως εξής:
- C++ για το microcontroller Rashberry Pi Pico,
- C# για την εφαρμογή Unity και
- Java για το απαραίτητο plugin.
Εκπαιδευτικοί στόχοι:
- Εξοικείωση μαθητών με σύγχρονα μοντέλα εκπαίδευσης βασισμένα στην προσέγγιση S.T.E.A.M.
- καλλιέργεια της κριτικής σκέψης, υπολογιστικής σκέψης και της δημιουργικότητας των μαθητών.
- Εφαρμογή προγραμματισμού στην καθημερινότητα
- Συνεργασία και επίλυση προβλημάτων
Υλικά:
3D printer + blender για τον σχεδιασμό των εξαρτημάτων που θα εκτυπώσουμε
1 microcontroler rasberry Pi pico
1 MQ2 smoke sensor
1 hc-06
1 κόκκινο led
1 κίτρινο led
1 πράσινο led
1 breadbord
3 μπαταρίες
1 θήκη για μπαταρίες
κολλητήρι
1 5V Servo μοτερ
1 buzzers
1 resistor
1 καλωδιο ταινία
1 γλάστρα
1 πλαστικό φυτό
1ο Στάδιο
Έγινε η συναρμολόγηση των εξαρτημάτων επάνω στο breadboard. Εκτυπώθηκαν σε ψηφιακό 3D εκτυπωτή τα κομμάτια της βάσης και του πώματος στο γλάστρακι μας.
2ο Στάδιο
Έπειτα προχωρήσαμε στη συγγραφή του κώδικα στο Rashberry Pi pico και άρχισε να σχεδιάζεται το User Interface UI της εφαρμογής. Γράφτηκε το plugin σε Java χρησιμοποιώντας το Android Studio για να συνδέεται η εφαρμογή με το hc-06 (για την λήψη του bluetooth).
3ο Στάδιο
Ακολούθησε ο κώδικας της ίδιας εφαρμογής (στο Unity) και η ολοκλήρωση αυτής του Rashberry Pi. Όλες οι εξελίξεις έγιναν παράλληλα γιατί τα στάδια είναι αλληλένδετα.
Κώδικας bluetooth controller σε C#
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class BluetoothController : MonoBehaviour
{
AndroidJavaClass unityClass;
AndroidJavaObject unityActivity;
AndroidJavaObject _pluginInstance;
void Start()
{
InitializePlugin(“com.example.unitypluginn.PluginInstance”);
}
void InitializePlugin(string pluginName)
{
unityClass = new AndroidJavaClass(“com.unity3d.player.UnityPlayer”);
unityActivity = unityClass.GetStatic<AndroidJavaObject>(“currentActivity”);
_pluginInstance = new AndroidJavaObject(pluginName);
if (_pluginInstance == null)
{
Debug.Log(“Something wend wrong”);
return;
}
_pluginInstance?.CallStatic(“receiveUnityActivity”, unityActivity);
}
public void Toast(string msg)
{
_pluginInstance?.Call(“Toast”, msg);
}
public void ConnectToDevice(string deviceAddress, int deviceNum)
{
// Call the connectToDevice method of the BluetoothManager class
_pluginInstance?.Call<bool>(“connectToDevice”, deviceAddress, deviceNum);
}
public void SendData(string data, int deviceNum)
{
// Call the send method of the BluetoothManager class
_pluginInstance?.Call(“send”, data, deviceNum);
}
public string ReceiveData(int deviceNum)
{
return _pluginInstance?.Call<string>(“receiveData”, deviceNum);
}
public void CloseConnection(int deviceNum)
{
// Call the closeConnection method of the BluetoothManager class
_pluginInstance?.Call(“closeConnection”, deviceNum);
}
public string[] ScanForDevices()
{
string[] devicesInfo = _pluginInstance.CallStatic<string[]>(“getPairedDeviceNamesAndAddresses”);
if (devicesInfo != null)
{
foreach (string deviceInfo in devicesInfo)
{
Debug.Log(“Device Info: ” + deviceInfo);
}
return devicesInfo;
}
else
{
Toast(“No paired devices found.”);
Debug.Log(“No paired devices found.”);
return null;
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using TMPro;
using UnityEngine.UI;
using UnityEngine.UIElements;
using Unity.VisualScripting;
using Unity.Notifications.Android;
using UnityEngine.Localization.Settings;
public class ControlerScript : MonoBehaviour
{
public RequestPermissionScript requestPermissionScript;
public BluetoothController bluetoothController;
public GameObject ScrollV;
public TMP_Dropdown dropdown;
public string[] yourdivicesAdress = { “”, “”, “”, “”, “”, “”, “”, “”, “” };
public int selecteddrint = 0;
public GameObject glastrakiameros;
public GameObject glastraki;
public GameObject[] glastres = new GameObject[69];
public GameObject SelectedGO;
public GameObject GreenSlider;
public GameObject RedSlider;
public GameObject EditStuff;
public GameObject GlastrakiEdit;
public bool editing;
public GameObject daImage;
public TMP_InputField limit;
public UnityEngine.UI.Toggle toggle;
public GameObject BGEdit;
public TMP_InputField fileadr;
// Start is called before the first frame update
void Start()
{
LanguageSelection(0);
requestPermissionScript.AskForPermissionsNEI();
editing = false;
AndroidNotificationChannel channel = new()
{
Id = “channel1”,
Name = “Default Channel”,
Importance = Importance.High,
Description = “Generic Notification”
};
AndroidNotificationCenter.RegisterNotificationChannel(channel);
}
// Update is called once per frame
void Update()
{
}
public void BigButtonPress()
{
SelectedGO = daImage;
}
public void EditButtonPress()
{
ScrollV.GetComponent<RectTransform>().offsetMin = new(0, 400);
}
public void DeleteButtonPress()
{
if (SelectedGO != null && SelectedGO != daImage)
{
int num = SelectedGO.GetComponent<Glastraki>().num;
Destroy(SelectedGO);
bluetoothController.CloseConnection(num);
bluetoothController.Toast(“G” + num + ” has been Deleted”);
}
editing = true;
TheothereditButtonPress();
}
public void SaveButtonPress()
{
bluetoothController.Toast(“Hi!”);
}
public void ExitButtonPress()
{
ScrollV.GetComponent<RectTransform>().offsetMin = new(0, 0);
editing = false;
}
public void AddButtonPress()
{
Debug.Log(“ch0”);
string[] yourdivices = bluetoothController.ScanForDevices();
Debug.Log(“ch1”);
for (int i = 0; i < yourdivices.Length; i++)
{
Debug.Log(“ch2”);
if (yourdivices[i] != null)
for (int j = 0; j < yourdivices[i].Length; j++)
{
Debug.Log(“ch3”);
if (yourdivices[i][j] == ‘|’)
{
Debug.Log(“ch4”);
dropdown.options.Add(new TMP_Dropdown.OptionData(yourdivices[i][0..(j – 1)], null));
yourdivicesAdress[i] = yourdivices[i][(j + 1)..];
Debug.Log(“ch5”);
}
}
}
dropdown.RefreshShownValue();
}
public void WhenDropdownValueChanged(int theval) { selecteddrint = theval; }
public void TheatheraddButtonPress()
{
Debug.Log(“ch21” + yourdivicesAdress[selecteddrint]);
dropdown.ClearOptions();
Debug.Log(“ch22”);
for (int i = 0; i < glastres.Length; i++)
{
if (glastres[i] == null)
{
glastres[i] = Instantiate(glastraki, glastrakiameros.transform);
glastres[i].GetComponent<Glastraki>().num = i;
bluetoothController.ConnectToDevice(yourdivicesAdress[selecteddrint], i);
break;
}
}
Debug.Log(“ch23”);
bluetoothController.Toast(“Connecting to: ” + yourdivicesAdress.Length);
}
public void TheothereditButtonPress()
{
if (SelectedGO != null)
{
if (editing == false)
{
if (SelectedGO != daImage)
{
GreenSlider.transform.localPosition = new(SelectedGO.transform.localPosition.x, GreenSlider.transform.localPosition.y);
RedSlider.transform.localPosition = new(RedSlider.transform.localPosition.x, SelectedGO.transform.localPosition.y);
GreenSlider.GetComponent<Scrollbar>().value = (SelectedGO.transform.localPosition.y + 4850) / 9200;
RedSlider.GetComponent<Scrollbar>().value = (SelectedGO.transform.localPosition.x – 4850) / 9200;
toggle.isOn = SelectedGO.GetComponent<Glastraki>().auto;
limit.text = SelectedGO.GetComponent<Glastraki>().limit.ToString();
EditStuff.SetActive(true);
GlastrakiEdit.SetActive(true);
}
else
{
fileadr.text = null;
BGEdit.SetActive(true);
}
editing = true;
}
else
{
GlastrakiEdit.SetActive(false);
BGEdit.SetActive(false);
EditStuff.SetActive(false);
editing = false;
}
}
}
public void GreenSliderSlids(float theval)
{
if (editing && SelectedGO != daImage)
{
float thebettervall = (theval * 9200 – 4850);
SelectedGO.transform.localPosition = new(SelectedGO.transform.localPosition.x, thebettervall);
RedSlider.transform.localPosition = new(RedSlider.transform.localPosition.x, thebettervall);
}
}
public void RedSliderSlids(float theval)
{
if (editing && SelectedGO != daImage)
{
float thebettervall = 4850 – theval * 9200;
SelectedGO.transform.localPosition = new(thebettervall, SelectedGO.transform.localPosition.y);
GreenSlider.transform.localPosition = new(thebettervall, GreenSlider.transform.localPosition.y);
}
}
public void ScokeDitected(int glastranum)
{
bluetoothController.Toast(“Smoke ditected in 🙂 G” + glastranum);
AndroidNotification notification = new()
{
Title = “Smoke Detected”,
Text = “Smoke detected in G” + glastranum,
FireTime = DateTime.Now
};
AndroidNotificationCenter.SendNotification(notification, “channel1”);
}
public void NewLimit(string theval)
{
if (SelectedGO != null && SelectedGO != daImage)
{
SelectedGO.GetComponent<Glastraki>().limit = int.Parse(theval);
}
}
public void WhenAutoToglePressed(bool auto)
{
if (editing && SelectedGO != daImage)
{
SelectedGO.GetComponent<Glastraki>().auto = auto;
if (auto)
bluetoothController.SendData(“?r=0&y=0&g=0&a=1;”, SelectedGO.GetComponent<Glastraki>().num);
else
bluetoothController.SendData(“?r=0&y=0&g=0&a=0;”, SelectedGO.GetComponent<Glastraki>().num);
}
}
public void ReplacedaImage(string filePath)
{
if (File.Exists(filePath))
{
byte[] fileData = File.ReadAllBytes(filePath);
Texture2D texture = new(2, 2);
texture.LoadImage(fileData);
Rect rect = new(0, 0, texture.width, texture.height);
Sprite newSprite = Sprite.Create(texture, rect, new(0.5f, 0.5f));
daImage.GetComponent<UnityEngine.UI.Image>().sprite = newSprite;
}
else
{
Debug.LogError(“File not found at: ” + filePath);
bluetoothController.Toast(“File not found”);
}
}
public void LanguageSelection(Int32 language)
{
LocalizationSettings.SelectedLocale = LocalizationSettings.AvailableLocales.Locales[language];
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Android;
public class RequestPermissionScript : MonoBehaviour
{
public void AskForPermissionsNEI()
{
StartCoroutine(AskForPermissions());
}
private IEnumerator AskForPermissions()
{
#if UNITY_ANDROID
List<bool> permissions = new() { false, false, false, false };
List<bool> permissionsAsked = new() { false, false, false, false };
List<Action> actions = new()
{
new Action(() => {
permissions[0] = Permission.HasUserAuthorizedPermission(“android.permission.BLUETOOTH_SCAN”);
if (!permissions[0] && !permissionsAsked[0])
{
Permission.RequestUserPermission(“android.permission.BLUETOOTH_SCAN”);
permissionsAsked[0] = true;
return;
}
}),
new Action(() => {
permissions[1] = Permission.HasUserAuthorizedPermission(“android.permission.BLUETOOTH_CONNECT”);
if (!permissions[1] && !permissionsAsked[1])
{
Permission.RequestUserPermission(“android.permission.BLUETOOTH_CONNECT”);
permissionsAsked[1] = true;
return;
}
}),
new Action(() => {
permissions[2] = Permission.HasUserAuthorizedPermission(“android.permission.BLUETOOTH”);
if (!permissions[2] && !permissionsAsked[2])
{
Permission.RequestUserPermission(“android.permission.BLUETOOTH”);
permissionsAsked[2] = true;
return;
}
}),
new Action(() => {
permissions[3] = Permission.HasUserAuthorizedPermission(“android.permission.BLUETOOTH_ADMIN”);
if (!permissions[3] && !permissionsAsked[3])
{
Permission.RequestUserPermission(“android.permission.BLUETOOTH_ADMIN”);
permissionsAsked[3] = true;
return;
}
})
};
for (int i = 0; i < permissionsAsked.Count;)
{
actions[i].Invoke();
if (permissions[i])
{
++i;
}
yield return new WaitForEndOfFrame();
}
#endif
}
}