257 lines
8.3 KiB
C#
257 lines
8.3 KiB
C#
using UnityEngine;
|
|
using MQTTnet;
|
|
using MQTTnet.Client;
|
|
using System;
|
|
using System.Text;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Collections.Concurrent;
|
|
|
|
// Klasa dla danych z M5Stick (Têtno + GSR)
|
|
[Serializable]
|
|
public class M5StickData
|
|
{
|
|
public float hr;
|
|
public float gsr;
|
|
}
|
|
|
|
// Klasa dla danych z SeeedStudio (Tylko Têtno)
|
|
[Serializable]
|
|
public class SeeedData
|
|
{
|
|
public float hr;
|
|
}
|
|
|
|
public class ExperimentManager : MonoBehaviour
|
|
{
|
|
public static ExperimentManager Instance;
|
|
|
|
[Header("MQTT Settings")]
|
|
public string brokerIp = "127.0.0.1";
|
|
public int port = 1883;
|
|
public string topicM5Stick = "m5stick/state";
|
|
public string topicSeeed = "seeedstudio-mr60bha2/state";
|
|
|
|
[Header("Watchdog Settings")]
|
|
public float timeoutSeconds = 5f; // Czas w sekundach do w³¹czenia alarmu
|
|
|
|
private IMqttClient _mqttClient;
|
|
private string _subjectFolderPath;
|
|
|
|
// Œcie¿ki do plików CSV
|
|
private string _m5stickCsvFilePath;
|
|
private string _seeedCsvFilePath;
|
|
|
|
// Osobne kolejki dla obu urz¹dzeñ
|
|
private ConcurrentQueue<M5StickData> _m5stickQueue = new ConcurrentQueue<M5StickData>();
|
|
private ConcurrentQueue<SeeedData> _seeedQueue = new ConcurrentQueue<SeeedData>();
|
|
|
|
// Zmienne do pilnowania czasu (Watchdog) dla obu urz¹dzeñ
|
|
private float _timeSinceLastM5Stick = 0f;
|
|
private float _timeSinceLastSeeed = 0f;
|
|
private bool _isM5StickTimeoutWarningShown = false;
|
|
private bool _isSeeedTimeoutWarningShown = false;
|
|
|
|
void Awake()
|
|
{
|
|
if (Instance == null)
|
|
{
|
|
Instance = this;
|
|
DontDestroyOnLoad(gameObject);
|
|
}
|
|
else
|
|
{
|
|
Destroy(gameObject);
|
|
}
|
|
}
|
|
|
|
public async void InitializeSession(string subjectId)
|
|
{
|
|
string dataPath = Path.Combine(Application.dataPath, "Data");
|
|
_subjectFolderPath = Path.Combine(dataPath, subjectId);
|
|
|
|
Directory.CreateDirectory(_subjectFolderPath);
|
|
Directory.CreateDirectory(Path.Combine(_subjectFolderPath, "Neutral"));
|
|
Directory.CreateDirectory(Path.Combine(_subjectFolderPath, "Experimental"));
|
|
|
|
Debug.Log($"[ExperimentManager] Folders created for subject: {subjectId}");
|
|
|
|
await ConnectToMqttAsync();
|
|
}
|
|
|
|
public void SetPhase(string phaseName)
|
|
{
|
|
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
|
|
|
// Tworzenie nazw plików dla obu urz¹dzeñ
|
|
string m5stickFileName = $"{phaseName}_M5Stick_Data_{timestamp}.csv";
|
|
string seeedFileName = $"{phaseName}_Seeed_Data_{timestamp}.csv";
|
|
|
|
_m5stickCsvFilePath = Path.Combine(_subjectFolderPath, phaseName, m5stickFileName);
|
|
_seeedCsvFilePath = Path.Combine(_subjectFolderPath, phaseName, seeedFileName);
|
|
|
|
// Nag³ówki dostosowane do danych
|
|
if (!File.Exists(_m5stickCsvFilePath))
|
|
{
|
|
File.WriteAllText(_m5stickCsvFilePath, "Timestamp;HR;GSR\n");
|
|
}
|
|
|
|
if (!File.Exists(_seeedCsvFilePath))
|
|
{
|
|
File.WriteAllText(_seeedCsvFilePath, "Timestamp;HR\n");
|
|
}
|
|
|
|
Debug.Log($"[ExperimentManager] Logging M5Stick to: {_m5stickCsvFilePath}");
|
|
Debug.Log($"[ExperimentManager] Logging Seeed to: {_seeedCsvFilePath}");
|
|
}
|
|
|
|
public string GetCurrentFolderPath()
|
|
{
|
|
if (string.IsNullOrEmpty(_m5stickCsvFilePath)) return null;
|
|
return Path.GetDirectoryName(_m5stickCsvFilePath);
|
|
}
|
|
|
|
public void StopLogging()
|
|
{
|
|
_m5stickCsvFilePath = null;
|
|
_seeedCsvFilePath = null;
|
|
Debug.Log("[ExperimentManager] Logging stopped. Data collection finished.");
|
|
}
|
|
|
|
private async Task ConnectToMqttAsync()
|
|
{
|
|
var factory = new MqttFactory();
|
|
_mqttClient = factory.CreateMqttClient();
|
|
|
|
var options = new MqttClientOptionsBuilder()
|
|
.WithTcpServer(brokerIp, port)
|
|
.Build();
|
|
|
|
_mqttClient.ApplicationMessageReceivedAsync += e =>
|
|
{
|
|
string topic = e.ApplicationMessage.Topic;
|
|
string payload = Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment);
|
|
|
|
try
|
|
{
|
|
if (topic == topicM5Stick)
|
|
{
|
|
M5StickData data = JsonUtility.FromJson<M5StickData>(payload);
|
|
_m5stickQueue.Enqueue(data);
|
|
}
|
|
else if (topic == topicSeeed)
|
|
{
|
|
SeeedData data = JsonUtility.FromJson<SeeedData>(payload);
|
|
_seeedQueue.Enqueue(data);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning($"[MQTT] Parse error on topic {topic}: {ex.Message}");
|
|
}
|
|
return Task.CompletedTask;
|
|
};
|
|
|
|
try
|
|
{
|
|
await _mqttClient.ConnectAsync(options, CancellationToken.None);
|
|
|
|
// Subskrypcja obu tematów
|
|
var subOptions = new MqttClientSubscribeOptionsBuilder()
|
|
.WithTopicFilter(f => f.WithTopic(topicM5Stick))
|
|
.WithTopicFilter(f => f.WithTopic(topicSeeed))
|
|
.Build();
|
|
|
|
await _mqttClient.SubscribeAsync(subOptions, CancellationToken.None);
|
|
Debug.Log("<color=green>[MQTT] Connected successfully and subscribed to M5Stick & Seeed!</color>");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"<color=red>[MQTT] Connection Error: {ex.Message}</color>");
|
|
}
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
bool receivedM5StickThisFrame = false;
|
|
bool receivedSeeedThisFrame = false;
|
|
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
|
|
|
// Opró¿nianie kolejki dla M5Stick
|
|
while (_m5stickQueue.TryDequeue(out M5StickData m5Data))
|
|
{
|
|
receivedM5StickThisFrame = true;
|
|
|
|
if (!string.IsNullOrEmpty(_m5stickCsvFilePath))
|
|
{
|
|
string csvLine = $"{timestamp};{m5Data.hr};{m5Data.gsr}\n";
|
|
File.AppendAllText(_m5stickCsvFilePath, csvLine);
|
|
}
|
|
}
|
|
|
|
// Opró¿nianie kolejki dla SeeedStudio
|
|
while (_seeedQueue.TryDequeue(out SeeedData seeedData))
|
|
{
|
|
receivedSeeedThisFrame = true;
|
|
|
|
if (!string.IsNullOrEmpty(_seeedCsvFilePath))
|
|
{
|
|
string csvLine = $"{timestamp};{seeedData.hr}\n";
|
|
File.AppendAllText(_seeedCsvFilePath, csvLine);
|
|
}
|
|
}
|
|
|
|
// --- SYSTEM WATCHDOG (Pilnowanie transmisji) ---
|
|
if (_mqttClient != null && _mqttClient.IsConnected)
|
|
{
|
|
// Watchdog: M5Stick
|
|
if (receivedM5StickThisFrame)
|
|
{
|
|
_timeSinceLastM5Stick = 0f;
|
|
if (_isM5StickTimeoutWarningShown)
|
|
{
|
|
Debug.Log("<color=green>[ExperimentManager] Transmisja odzyskana! M5Stick znów nadaje.</color>");
|
|
_isM5StickTimeoutWarningShown = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_timeSinceLastM5Stick += Time.deltaTime;
|
|
if (_timeSinceLastM5Stick >= timeoutSeconds && !_isM5StickTimeoutWarningShown)
|
|
{
|
|
Debug.LogError($"<color=red><b>[UWAGA MQTT!]</b> Brak danych od M5Stick przez {timeoutSeconds} sek!</color>");
|
|
_isM5StickTimeoutWarningShown = true;
|
|
}
|
|
}
|
|
|
|
// Watchdog: SeeedStudio
|
|
if (receivedSeeedThisFrame)
|
|
{
|
|
_timeSinceLastSeeed = 0f;
|
|
if (_isSeeedTimeoutWarningShown)
|
|
{
|
|
Debug.Log("<color=green>[ExperimentManager] Transmisja odzyskana! SeeedStudio znów nadaje.</color>");
|
|
_isSeeedTimeoutWarningShown = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_timeSinceLastSeeed += Time.deltaTime;
|
|
if (_timeSinceLastSeeed >= timeoutSeconds && !_isSeeedTimeoutWarningShown)
|
|
{
|
|
Debug.LogError($"<color=red><b>[UWAGA MQTT!]</b> Brak danych od SeeedStudio przez {timeoutSeconds} sek!</color>");
|
|
_isSeeedTimeoutWarningShown = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private async void OnApplicationQuit()
|
|
{
|
|
if (_mqttClient != null && _mqttClient.IsConnected)
|
|
{
|
|
await _mqttClient.DisconnectAsync();
|
|
}
|
|
}
|
|
} |