Added sane window start positions and states. 0.9.9
[VOID.git] / VOID_DataLogger.cs
blob:a/VOID_DataLogger.cs -> blob:b/VOID_DataLogger.cs
// // VOID
// VOID_Orbital.cs //
// // VOID_DataLogger.cs
// Author: //
// toadicus <> // Copyright © 2014, toadicus
// // All rights reserved.
// Copyright (c) 2013 toadicus //
// // Redistribution and use in source and binary forms, with or without modification,
// This program is free software: you can redistribute it and/or modify // are permitted provided that the following conditions are met:
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation, either version 3 of the License, or // 1. Redistributions of source code must retain the above copyright notice,
// (at your option) any later version. // this list of conditions and the following disclaimer.
// //
// This program is distributed in the hope that it will be useful, // 2. Redistributions in binary form must reproduce the above copyright notice,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // this list of conditions and the following disclaimer in the documentation and/or other
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // materials provided with the distribution.
// GNU General Public License for more details. //
// // 3. Neither the name of the copyright holder nor the names of its contributors may be used
// You should have received a copy of the GNU General Public License // to endorse or promote products derived from this software without specific prior written permission.
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
  using System.IO;
  using System.Text;
  using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace VOID namespace VOID
{ {
public class VOID_DataLogger : VOID_WindowModule, IVOID_BehaviorModule public class VOID_DataLogger : VOID_WindowModule, IVOID_BehaviorModule
{ {
/* /*
* Fields * Fields
* */ * */
protected bool stopwatch1_running = false; #region Fields
   
protected bool csv_logging = false; protected bool _loggingActive;
protected bool first_write = true; protected bool firstWrite;
   
protected double stopwatch1 = 0; [AVOID_SaveValue("waitForLaunch")]
  protected VOID_SaveValue<bool> waitForLaunch;
protected string csv_log_interval_str = "0.5";  
  [AVOID_SaveValue("logInterval")]
protected float csv_log_interval; protected VOID_SaveValue<float> logInterval;
  protected string logIntervalStr;
protected double csvWriteTimer = 0;  
protected double csvCollectTimer = 0; protected float csvCollectTimer;
   
protected List<string> csvList = new List<string>(); protected List<byte> csvBytes;
   
  protected string _fileName;
  protected FileStream _outputFile;
   
  protected uint outstandingWrites;
   
  protected System.Text.UTF8Encoding _utf8Encoding;
   
  #endregion
   
/* /*
* Properties * Properties
* */ * */
   
  #region Properties
   
  // TODO: Add configurable or incremental file names.
  protected bool loggingActive
  {
  get
  {
  return this._loggingActive;
  }
  set
  {
  if (value != this._loggingActive)
  {
  if (value)
  {
  this.csvCollectTimer = 0f;
  }
  else
  {
  this.CloseFileIfOpen();
  }
   
  this._loggingActive = value;
  }
  }
  }
   
  protected string fileName
  {
  get
  {
  if (this._fileName == null || this._fileName == string.Empty)
  {
  this._fileName = KSP.IO.IOUtils.GetFilePathFor(
  typeof(VOIDCore),
  string.Format(
  "{0}_{1}",
  this.vessel.vesselName,
  "data.csv"
  ),
  null
  );
  }
   
  return this._fileName;
  }
  }
   
  protected FileStream outputFile
  {
  get
  {
  if (this._outputFile == null)
  {
  Tools.DebugLogger logger = Tools.DebugLogger.New(this);
  logger.AppendFormat("Initializing output file '{0}' with mode ", this.fileName);
   
  if (File.Exists(this.fileName))
  {
  logger.Append("append");
  this._outputFile = new FileStream(
  this.fileName,
  FileMode.Append,
  FileAccess.Write,
  FileShare.Read,
  512,
  true
  );
  }
  else
  {
  logger.Append("create");
  this._outputFile = new FileStream(
  this.fileName,
  FileMode.Create,
  FileAccess.Write,
  FileShare.Read,
  512,
  true
  );
   
  byte[] byteOrderMark = utf8Encoding.GetPreamble();
   
  logger.Append(" and writing preamble");
  this._outputFile.Write(byteOrderMark, 0, byteOrderMark.Length);
  }
   
  logger.Append('.');
   
  logger.AppendFormat(" File is {0}opened asynchronously.", this._outputFile.IsAsync ? "" : "not ");
   
  logger.Print();
  }
   
  return this._outputFile;
  }
  }
   
  public UTF8Encoding utf8Encoding
  {
  get
  {
  if (this._utf8Encoding == null)
  {
  this._utf8Encoding = new UTF8Encoding(true);
  }
   
  return this._utf8Encoding;
  }
  }
   
  #endregion
   
/* /*
* Methods * Methods
* */ * */
public VOID_DataLogger() #region Monobehaviour Lifecycle
{  
this._Name = "CSV Data Logger";  
   
this.WindowPos.x = Screen.width - 520;  
this.WindowPos.y = 85;  
}  
   
public override void ModuleWindow(int _)  
{  
GUIStyle txt_white = new GUIStyle(GUI.skin.label);  
txt_white.normal.textColor = txt_white.focused.textColor = Color.white;  
txt_white.alignment = TextAnchor.UpperRight;  
GUIStyle txt_green = new GUIStyle(GUI.skin.label);  
txt_green.normal.textColor = txt_green.focused.textColor = Color.green;  
txt_green.alignment = TextAnchor.UpperRight;  
GUIStyle txt_yellow = new GUIStyle(GUI.skin.label);  
txt_yellow.normal.textColor = txt_yellow.focused.textColor = Color.yellow;  
txt_yellow.alignment = TextAnchor.UpperRight;  
   
GUILayout.BeginVertical();  
   
GUILayout.Label("System time: " + DateTime.Now.ToString("HH:mm:ss"));  
GUILayout.Label(Tools.ConvertInterval(stopwatch1));  
   
GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));  
if (GUILayout.Button("Start"))  
{  
if (stopwatch1_running == false) stopwatch1_running = true;  
}  
if (GUILayout.Button("Stop"))  
{  
if (stopwatch1_running == true) stopwatch1_running = false;  
}  
if (GUILayout.Button("Reset"))  
{  
if (stopwatch1_running == true) stopwatch1_running = false;  
stopwatch1 = 0;  
}  
GUILayout.EndHorizontal();  
   
GUIStyle label_style = txt_white;  
string log_label = "Inactive";  
if (csv_logging && vessel.situation.ToString() == "PRELAUNCH")  
{  
log_label = "Awaiting launch";  
label_style = txt_yellow;  
}  
if (csv_logging && vessel.situation.ToString() != "PRELAUNCH")  
{  
log_label = "Active";  
label_style = txt_green;  
}  
GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));  
csv_logging = GUILayout.Toggle(csv_logging, "Data logging: ", GUILayout.ExpandWidth(false));  
GUILayout.Label(log_label, label_style, GUILayout.ExpandWidth(true));  
GUILayout.EndHorizontal();  
   
GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));  
GUILayout.Label("Interval: ", GUILayout.ExpandWidth(false));  
csv_log_interval_str = GUILayout.TextField(csv_log_interval_str, GUILayout.ExpandWidth(true));  
GUILayout.Label("s", GUILayout.ExpandWidth(false));  
GUILayout.EndHorizontal();  
   
float new_log_interval;  
if (Single.TryParse(csv_log_interval_str, out new_log_interval)) csv_log_interval = new_log_interval;  
   
GUILayout.EndVertical();  
GUI.DragWindow();  
}  
   
public void Update() public void Update()
{ {
  if (this.csvBytes != null && this.csvBytes.Count > 0)
  {
  // csvList is not empty, write it
  this.AsyncWriteData();
  }
   
// CSV Logging // CSV Logging
// from ISA MapSat // from ISA MapSat
if (csv_logging) if (loggingActive && (!waitForLaunch || this.vessel.situation != Vessel.Situations.PRELAUNCH))
{ {
//data logging is on //data logging is on
//increment timers //increment timers
csvWriteTimer += Time.deltaTime; this.csvCollectTimer += Time.deltaTime;
csvCollectTimer += Time.deltaTime;  
  if (this.csvCollectTimer >= this.logInterval)
if (csvCollectTimer >= csv_log_interval && vessel.situation != Vessel.Situations.PRELAUNCH)  
{ {
//data logging is on, vessel is not prelaunch, and interval has passed //data logging is on, vessel is not prelaunch, and interval has passed
//write a line to the list //write a line to the list
line_to_csvList(); //write to the csv this.CollectLogData();
} }
   
if (csvList.Count != 0 && csvWriteTimer >= 15f)  
{  
// csvList is not empty and interval between writings to file has elapsed  
//write it  
string[] csvData;  
csvData = (string[])csvList.ToArray();  
Innsewerants_writeData(csvData);  
csvList.Clear();  
csvWriteTimer = 0f;  
}  
}  
else  
{  
//data logging is off  
//reset any timers and clear anything from csvList  
csvWriteTimer = 0f;  
csvCollectTimer = 0f;  
if (csvList.Count > 0) csvList.Clear();  
}  
   
if (stopwatch1_running)  
{  
stopwatch1 += Time.deltaTime;  
} }
} }
   
public void FixedUpdate() {} public void FixedUpdate() {}
   
private void Innsewerants_writeData(string[] csvArray) public void OnDestroy()
{ {
var efile = KSP.IO.File.AppendText<VOID_Core>(vessel.vesselName + "_data.csv", null); Tools.DebugLogger logger = Tools.DebugLogger.New(this);
foreach (string line in csvArray)  
{ logger.Append("Destroying...");
efile.Write(line);  
} this.CloseFileIfOpen();
efile.Close();  
} logger.Append(" Done.");
  logger.Print(false);
private void line_to_csvList() }
{  
  #endregion
   
  #region VOID_Module Overrides
   
  public override void LoadConfig()
  {
  base.LoadConfig();
   
  this.logIntervalStr = this.logInterval.value.ToString("#.0##");
  }
   
  public override void ModuleWindow(int id)
  {
  GUILayout.BeginVertical();
   
  GUILayout.Label(
  string.Format("System time: {0}", DateTime.Now.ToString("HH:mm:ss")),
  GUILayout.ExpandWidth(true)
  );
  GUILayout.Label(
  string.Format("Kerbin time: {0}", VOID_Tools.FormatDate(Planetarium.GetUniversalTime())),
  GUILayout.ExpandWidth(true)
  );
   
  GUIStyle activeLabelStyle = VOID_Styles.labelRed;
  string activeLabelText = "Inactive";
  if (loggingActive)
  {
  activeLabelText = "Active";
  activeLabelStyle = VOID_Styles.labelGreen;
  }
   
  this.loggingActive = GUITools.Toggle(
  loggingActive,
  string.Format("Data logging: {0}", activeLabelText),
  null,
  activeLabelStyle
  );
   
  this.waitForLaunch.value = GUITools.Toggle(
  this.waitForLaunch,
  "Wait for launch"
  );
   
  GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
   
  GUILayout.Label("Interval: ", GUILayout.ExpandWidth(false));
   
  logIntervalStr = GUILayout.TextField(logIntervalStr, GUILayout.ExpandWidth(true));
  GUILayout.Label("s", GUILayout.ExpandWidth(false));
   
  GUILayout.EndHorizontal();
   
  float newLogInterval;
  if (float.TryParse(logIntervalStr, out newLogInterval))
  {
  logInterval.value = newLogInterval;
  this.logIntervalStr = this.logInterval.value.ToString("#.0##");
  }
   
  GUILayout.EndVertical();
   
  base.ModuleWindow(id);
  }
   
  #endregion
   
  #region Data Collection
   
  private void CollectLogData()
  {
  if (this.csvBytes == null)
  {
  this.csvBytes = new List<byte>();
  }
   
//called if logging is on and interval has passed //called if logging is on and interval has passed
//writes one line to the csvList //writes one line to the csvList
   
string line = ""; StringBuilder line = new StringBuilder();
if (first_write && !KSP.IO.File.Exists<VOID_Core>(vessel.vesselName + "_data.csv", null))  
{ if (firstWrite)
first_write = false; {
line += "Mission Elapsed Time (s);Altitude ASL (m);Altitude above terrain (m);Orbital Velocity (m/s);Surface Velocity (m/s);Vertical Speed (m/s);Horizontal Speed (m/s);Gee Force (gees);Temperature (°C);Gravity (m/s²);Atmosphere Density (g/m³);\n"; firstWrite = false;
} line.Append(
  "\"Kerbin Universal Time (s)\"," +
  "\"Mission Elapsed Time (s)\t\"," +
  "\"Altitude ASL (m)\"," +
  "\"Altitude above terrain (m)\"," +
  "\"Surface Latitude (°)\"," +
  "\"Surface Longitude (°)\"," +
  "\"Orbital Velocity (m/s)\"," +
  "\"Surface Velocity (m/s)\"," +
  "\"Vertical Speed (m/s)\"," +
  "\"Horizontal Speed (m/s)\"," +
  "\"Gee Force (gees)\"," +
  "\"Temperature (°C)\"," +
  "\"Gravity (m/s²)\"," +
  "\"Atmosphere Density (g/m³)\"," +
  "\"Downrange Distance (m)\"," +
  "\n"
  );
  }
   
  // Universal time
  line.Append(Planetarium.GetUniversalTime().ToString("F2"));
  line.Append(',');
   
//Mission time //Mission time
line += vessel.missionTime.ToString("F3") + ";"; line.Append(vessel.missionTime.ToString("F3"));
  line.Append(',');
   
//Altitude ASL //Altitude ASL
line += vessel.orbit.altitude.ToString("F3") + ";"; line.Append(VOID_Data.orbitAltitude.Value.ToString("F3"));
  line.Append(',');
   
//Altitude (true) //Altitude (true)
double alt_true = vessel.orbit.altitude - vessel.terrainAltitude; line.Append(VOID_Data.trueAltitude.Value.ToString("F3"));
if (vessel.terrainAltitude < 0) alt_true = vessel.orbit.altitude; line.Append(',');
line += alt_true.ToString("F3") + ";";  
  // Surface Latitude
  line.Append('"');
  line.Append(VOID_Data.surfLatitude.Value);
  line.Append('"');
  line.Append(',');
   
  // Surface Longitude
  line.Append('"');
  line.Append(VOID_Data.surfLongitude.Value);
  line.Append('"');
  line.Append(',');
   
//Orbital velocity //Orbital velocity
line += vessel.orbit.vel.magnitude.ToString("F3") + ";"; line.Append(VOID_Data.orbitVelocity.Value.ToString("F3"));
  line.Append(',');
   
//surface velocity //surface velocity
line += vessel.srf_velocity.magnitude.ToString("F3") + ";"; line.Append(VOID_Data.surfVelocity.Value.ToString("F3"));
  line.Append(',');
   
//vertical speed //vertical speed
line += vessel.verticalSpeed.ToString("F3") + ";"; line.Append(VOID_Data.vertVelocity.Value.ToString("F3"));
  line.Append(',');
   
//horizontal speed //horizontal speed
line += vessel.horizontalSrfSpeed.ToString("F3") + ";"; line.Append(VOID_Data.horzVelocity.Value.ToString("F3"));
  line.Append(',');
   
//gee force //gee force
line += vessel.geeForce.ToString("F3") + ";"; line.Append(VOID_Data.geeForce.Value.ToString("F3"));
  line.Append(',');
   
//temperature //temperature
line += vessel.flightIntegrator.getExternalTemperature().ToString("F2") + ";"; line.Append(VOID_Data.temperature.Value.ToString("F2"));
  line.Append(',');
   
//gravity //gravity
double r_vessel = vessel.mainBody.Radius + vessel.mainBody.GetAltitude(vessel.findWorldCenterOfMass()); line.Append(VOID_Data.gravityAccel.Value.ToString("F3"));
double g_vessel = (VOID_Core.Constant_G * vessel.mainBody.Mass) / Math.Pow(r_vessel, 2); line.Append(',');
line += g_vessel.ToString("F3") + ";";  
//atm density //atm density
line += (vessel.atmDensity * 1000).ToString("F3") + ";"; line.Append(VOID_Data.atmDensity.Value.ToString("G3"));
line += "\n"; line.Append(',');
if (csvList.Contains(line) == false) csvList.Add(line);  
csvCollectTimer = 0f; // Downrange Distance
} line.Append((VOID_Data.downrangeDistance.Value.ToString("G3")));
   
  line.Append('\n');
   
  csvBytes.AddRange(this.utf8Encoding.GetBytes(line.ToString()));
   
  this.csvCollectTimer = 0f;
  }
   
  #endregion
   
  #region File IO Methods
   
  protected void AsyncWriteCallback(IAsyncResult result)
  {
  Tools.PostDebugMessage(this, "Got async callback, IsCompleted = {0}", result.IsCompleted);
   
  this.outputFile.EndWrite(result);
  this.outstandingWrites--;
  }
   
  private void AsyncWriteData()
  {
  WriteState state = new WriteState();
   
  state.bytes = this.csvBytes.ToArray();
  state.stream = this.outputFile;
   
  this.outstandingWrites++;
  var writeCallback = new AsyncCallback(this.AsyncWriteCallback);
   
  this.outputFile.BeginWrite(state.bytes, 0, state.bytes.Length, writeCallback, state);
   
  this.csvBytes.Clear();
  }
   
  private void CloseFileIfOpen()
  {
  Tools.DebugLogger logger = Tools.DebugLogger.New(this);
   
  logger.AppendFormat("Cleaning up file {0}...", this.fileName);
   
  if (this.csvBytes != null && this.csvBytes.Count > 0)
  {
  logger.Append(" Writing remaining data...");
  this.AsyncWriteData();
  }
   
  logger.Append(" Waiting for writes to finish.");
  while (this.outstandingWrites > 0)
  {
  logger.Append('.');
  System.Threading.Thread.Sleep(10);
  }
   
  if (this._outputFile != null)
  {
  this._outputFile.Close();
  this._outputFile = null;
  logger.Append(" File closed.");
  }
   
  logger.Print(false);
  }
   
  #endregion
   
  #region Constructors & Destructors
   
  public VOID_DataLogger()
  {
  this.Name = "CSV Data Logger";
   
  this.loggingActive = false;
  this.firstWrite = true;
   
  this.waitForLaunch = (VOID_SaveValue<bool>)true;
   
  this.logInterval = (VOID_SaveValue<float>)0.5f;
  this.csvCollectTimer = (VOID_SaveValue<float>)0f;
   
  this.outstandingWrites = 0;
   
  this.WindowPos.x = Screen.width - 520f;
  this.WindowPos.y = 85f;
   
  this.core.onApplicationQuit += delegate(object sender)
  {
  this.CloseFileIfOpen();
  };
  }
   
  ~VOID_DataLogger()
  {
  this.OnDestroy();
  }
   
  #endregion
   
  #region Subclasses
   
  private class WriteState
  {
  public byte[] bytes;
  public FileStream stream;
  }
   
  #endregion
} }
} }