Clear(refresh) just one line but in two different tasks - c#

When I run with 2 tasks, sometimes write datas in 3 (have to write always in 2 lines max)lines but idk why. Why is it do that? How can I fix it? Otherwise if I run with 1 task it is working well. I tried to commented the code.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace LifeBar
{
class Program
{
public static readonly int FullHealth = 200;
static void Main(string[] args)
{
List<Task> tasks = new List<Task>();
Console.Write("What is the player ID: "); int playerId = int.Parse(Console.ReadLine());
//playerID is equals with the line where is writed
var t1 = Task.Run(() =>
{
for (int i = FullHealth; i >= 0; i--)
{
WhatDraw(i, playerId);
System.Threading.Thread.Sleep(150);
}
});
//tasks.Add(t1);
//var t2 = Task.Run(() =>
//{
// for (int i = 200; i >= 0; i--)
// {
// WhatDraw(i, playerId + 1); (+1 cus I would like to write it to the next line)
// System.Threading.Thread.Sleep(200);
// }
//});
//tasks.Add(t2);
Task.WaitAll(tasks.ToArray());
Console.ReadKey();
}
this is a line deleter
public static void ClearCurrentConsoleLine(int playerId)
{
Task.Factory.StartNew(() =>
{
Console.SetCursorPosition(playerId, Console.CursorTop);
Console.Write(new string(' ', Console.WindowWidth));
Console.SetCursorPosition(playerId, Console.CursorTop);
});
}
they are retunrnig with the lifebar
/// <summary>
/// Draw healt bar
/// </summary>
/// <param name = life> i </param>
/// <returns>Health bar value</returns>
static string DrawHealth(int life)
{
string health = String.Empty;
if (life == 0) return "Frank dead";
else if (life < 10) return "< 10";
for (int i = 0; i < life / 10; i++)
{
health += " |"; // 1 amout of | equal with 10 health
}
return health;
}
/// <summary>
/// Draw armour bar
/// </summary>
/// <param name="life"> (i)</param>
/// <returns>Armour bar value</returns>
static string DrawArmour(int life)
{
string armour = "| | | | | | | | | |";
for (int i = 0; i < life / 10; i++)
{
armour += " |:|"; // 1 amout of |:| equal with 10 armour
}
return armour;
}
this is a depender
/// <summary>
/// Health or Armour draw
/// </summary>
/// <param name="fullLife">(i)</param>
/// <param name="playerId">playerId</param>
static void WhatDraw(int fullLife, int playerId)
{
Console.SetCursorPosition(0, playerId);
if (fullLife > 100)
{
double percent = fullLife / Convert.ToDouble(FullHealth);
Console.WriteLine($"Frank ({Math.Round(percent * 100)})% " + DrawArmour(fullLife - 100));
if (fullLife % 10 == 0)
{
Console.SetCursorPosition(playerId, Console.CursorTop-1);
ClearCurrentConsoleLine(playerId);
}
}
else
{
double percent = fullLife / Convert.ToDouble(FullHealth);
Console.WriteLine($"Frank ({Math.Round(percent * 100)}%) " + DrawHealth(fullLife));
if (fullLife % 10 == 0 && fullLife!=0)
{
Console.SetCursorPosition(playerId, Console.CursorTop-1);
ClearCurrentConsoleLine(playerId);
}
}
}
}
}

You are hitting a race condition. Consider this order of events:
// Thread 1
/* a */ Console.SetCursorPosition
/* b */
/* c */ Console.Write
// Thread 2
/* d */ Console.SetCursorPosition
/* e */
/* f */ Console.Write
a - Your first background task/thread runs and it sets the current cursor position
b - It's not yet run the next command, so we can consider it "sitting" at line b.
d - The second task sets the cursor position
c - The first task writes to the console.
Well, the second task has changed the position!! All of the properties on the Console class are static so the first task is no longer writing to the location where it moved the cursor, but rather where the second task did.
The timing could be even worse; for example the cursor position could be moved at the same time (a and d execute simultaneously) or the second task could move the cursor position while the first task is writing (d executes after c starts but before it completes). If both lines c and f are invoked at the same time, you might expect garbled output.
Of course this is a dumbed down explanation that applies more generally when concurrency is involved. The Console class itself is synchronized to prevent some of these cases, but it does not/cannot when a single "operation" involves multiple commands (set position + write). This coordination is up to you.
We can achieve this easily with C#'s lock statement (a shorthand for Monitor.Enter/Monitor.Exit) to ensure that our operations against the console cannot overlap:
private static readonly object lockobj = new object();
public static void ClearCurrentConsoleLine(int playerId)
{
lock(lockobj)
{
Console.SetCursorPosition(playerId, Console.CursorTop);
Console.Write(new string(' ', Console.WindowWidth));
Console.SetCursorPosition(playerId, Console.CursorTop);
}
}
static void WhatDraw(int fullLife, int playerId)
{
lock(lockobj)
{
Console.SetCursorPosition(0, playerId);
if (fullLife > 100)
{
double percent = fullLife / Convert.ToDouble(FullHealth);
Console.WriteLine($"Frank ({Math.Round(percent * 100)})% " + DrawArmour(fullLife - 100));
if (fullLife % 10 == 0)
{
Console.SetCursorPosition(playerId, Console.CursorTop-1);
ClearCurrentConsoleLine(playerId);
}
}
else
{
double percent = fullLife / Convert.ToDouble(FullHealth);
Console.WriteLine($"Frank ({Math.Round(percent * 100)}%) " + DrawHealth(fullLife));
if (fullLife % 10 == 0 && fullLife!=0)
{
Console.SetCursorPosition(playerId, Console.CursorTop-1);
ClearCurrentConsoleLine(playerId);
}
}
}
}
Because we need to synchronize a set of operations against the Console we need to surround all of those operations in a lock. This will force any concurrent operations to wait until the resource is no longer in use. The lock is reentrant on the same thread so when WhatDraw has the lock it can safely call ClearCurrentConsoleLine without needing to wait. Once you've locked access to a resource, best practices dictate that you should always lock around it, even for a single operation:
lock(lockobj)
Console.WriteLine("Single write");
I've also removed the Task.Run from the ClearCurrentConsoleLine method as it's pointless and adds unnecessary complexity, especially since we need to synchronize these operations. Remember that a Task is a promise to complete at some later point. By the time that actually happens you may not actually want to clear the line anymore! Better to just clear it synchronously when and as necessary.
Also note that lock cannot be used safely in async contexts and you'll need to use other synchronization primitives such as a SemaphoreSlim.

Related

c# Call a method every x seconds but only a certain amount of times

Im looking to call the same method every x seconds in my c# console app, but I also want to only call this method a certain number of times (say 5).
I need each method to run after each other (cant have them over lapping)
My biggest issue is the console app closing before being completed
The current code I have works but is kinda messy (the while loop)
static void Main(string[] args)
{
for (int t = 0; t < 3; t++)
{
InitTimer(t);
}
}
public static void InitTimer(int t)
{
Console.WriteLine("Init" + t);
int x = 0;
var timer = new System.Threading.Timer(
e => x = MyMethod(x),
null,
TimeSpan.Zero,
//delay between seconds
TimeSpan.FromSeconds(5));
//number of times called
while (x < 5)
{
}
timer.Dispose();
}
public static int MyMethod(int x)
{
Console.WriteLine("Test" + x);
//call post method
x += 1;
return x;
}
}
Is there a neater way to create the same functionality ?

Report ProgressChange to UI only sometimes

Suppose I have some UI, and some Asynchronous Task handler. I can use callback methods/events that link back to the UI and tell it to update its progress display. This is all fine.
But what if I want to change progress on the UI only sometimes? As in, every 10%, I should get a single console output/UI change that would tell me the total/completed, percentage, and the elapsed time. This would require a simple class:
class Progress
{
int completed;
int total;
public Progress(int t)
{
total = t;
completed = 0;
}
bool ShouldReportProgress()
{
if((int)(total/completed) * 100) % 10 == 0)
return true;
return false;
}
}
I need help with 2 things: the method "ShouldReportProgress()" is naive and works incorrectly for various reasons (outputs more than once per step, can skip steps entirely), so I'm hoping there's a better way to do that.
I also am assuming that a class like this MUST exist somewhere already. I could write it myself if it doesn't, but it just seems like a logical conclusion of this issue.
Here's one idea of how you could create your class. I've added a field that the user can set that specifies the ReportIncrement, which is basically stating how often the progress should be reported. There is also an enum for Increment, which specifies whether or not ReportIncrement is a fixed number (i.e. report every 15th completion) or a percentage (i.e. report ever time we complete 10% of the total).
Every time the Completed field is changed, a call is made to ReportProgress, which then checks to see if progress should actually be reported (and it also takes an argument to force reporting). The checking is done in the ShouldReport method, which determines if the current progress is greater than or equal to the last reported progress plus the increment amount:
class Progress
{
public enum Increment
{
Percent,
FixedAmount
}
public Increment IncrementType { get; set; }
public int ReportIncrement { get; set; }
public int Total { get; }
private double completed;
public double Completed
{
get { return completed; }
set
{
completed = value;
ReportProgress();
}
}
public void ReportProgress(bool onlyIfShould = true)
{
if (!onlyIfShould || ShouldReport())
{
Console.WriteLine(
$"{Completed / Total * 100:0}% complete ({Completed} / {Total})");
lastReportedAmount = Completed;
}
}
public Progress(int total)
{
Total = total;
}
private double lastReportedAmount;
private bool ShouldReport()
{
if (Completed >= Total) return true;
switch (IncrementType)
{
case Increment.FixedAmount:
return lastReportedAmount + ReportIncrement <= Completed;
case Increment.Percent:
return lastReportedAmount / Total * 100 +
ReportIncrement <= Completed / Total * 100;
default:
return true;
}
}
}
Then, for example, this class can then be used to report every 10%:
private static void Main()
{
var progress = new Progress(50)
{
ReportIncrement = 10,
IncrementType = Progress.Increment.Percent
};
Console.WriteLine("Starting");
for (int i = 0; i < progress.Total; i++)
{
Console.Write('.');
Thread.Sleep(100);
progress.Completed++;
}
Console.WriteLine("\nDone!\nPress any key to exit...");
Console.ReadKey();
}
Or every 10th completion:
// Same code as above, only this time we specify 'FixedAmount'
var progress = new Progress(50)
{
ReportIncrement = 10,
IncrementType = Progress.Increment.FixedAmount
};

How to add a pause between executing tasks in C#

I am currently writing a program which requires me to have a pause between executing tasks.
So I have 4 things.
Read Limit
Delay Between Each Read
Total Reads
Global delay (pause the program for 'x' seconds after a task is finished)
Basically, one task is considered the "Read Limit". So, for example, if I have these settings:
Read Limit (10)
Delay Between Each Read (20)
Total Reads (100)
Global Delay (30)
The program has to read 10 lines from the file based on "Read Limit" and between reading each line, there is a delay of 20 seconds based on "Delay Between Each Read". After it reads 10 lines, it is paused for 30 seconds based on "Global Delay". When the global delay is over, it starts again where it stopped and continues doing this until the limit of 100 is reached based on "Total Reads".
I have tried using System.Threading.Thread.Sleep() but I couldn't make it work. How can I achieve this with C#?
Thanks in advance.
//update with some of my code.
I load the file like this:
private void btnLoadFile_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
if (ofd.ShowDialog() == DialogResult.OK)
{
string[] lines = System.IO.File.ReadAllLines(ofd.FileName);
}
}
I have 4 global variables:
public int readLimit = 0;
public int delayBetweenRead = 0;
public int totalReads = 0;
public int globalDelay = 0;
public int linesRead = 0;
And I want to make the function like this:
private void doTask()
{
while (linesRead <= readLimit)
{
readLine(); // read one line
doDelay(); // delay between each line
readLine(); // read another line and so on, until readLimit or totalReads is reached
globalDelay(); // after readLimit is reached, call globalDelay to wait
linesRead++;
}
}
This might be of interest - here's the way to do this with Microsoft's Reactive Framework (NuGet "Rx-Main").
int readLimit = 10;
int delayBetweenRead = 20;
int globalDelay = 30;
int linesRead = 100;
var subscription =
Observable
.Generate(0, n => n < linesRead, n => n + 1, n => n,
n => TimeSpan.FromSeconds(n % readLimit == 0 ? globalDelay : delayBetweenRead))
.Zip(System.IO.File.ReadLines(ofd.FileName), (n, line) => line)
.Subscribe(line =>
{
/* do something with each line */
});
If you need to stop the reading before it finishes naturally just call subscription.Dispose();.
What you do you mean by
I have tried using System.Threading.Thread.Sleep() but I couldn't make it work
Here is an example of achieving what you described with Thread.Sleep:
using (var fs = new FileStream("C:\\test.txt", FileMode.Open, FileAccess.Read))
{
using (var sr = new StreamReader(fs))
{
int nRead = 0;
while (nRead < settings.Total)
{
for (int i = 0; i < settings.ReadLimit && nRead < settings.Total; ++i, nRead++)
{
Console.WriteLine(sr.ReadLine());
if (i + 1 < settings.ReadLimit)
{
Thread.Sleep(settings.Delay * 1000);
}
}
if (nRead < settings.Total)
{
Thread.Sleep(settings.GlobalDelay * 1000);
}
}
}
}

c# 2 events with same nameļ¼Œ got chaos at running time, how should i avoid this?

I have a method "Add2List", who creates a ManualResetEvent and stores it in SortedList of a instance, then waits for signaling, then do some work and dispose the event.
I have another method "DoSomething", who listens to remote server and then signals the stored manual events according to Guid.
in the multithreading context, multi threads calls method "Add2List", so in the sortedlist there may have several manual event with same name at the same moment. But this may cause chaos. How should i avoid this?
To be simpler, i wrote this test code:
Class Program
{
static void Main(string[] args)
{
StringBuilder str = new StringBuilder();//a string to record what happened
//test iteratively
for(int i=0;i<100;i++)
{
EventHolder holder = new EventHolder();
Signaler ob2 = new Signaler();
Thread th1 = new Thread(holder.Add2List);
Thread th2 = new Thread(holder.Add2List);
Thread th3 = new Thread(ob2.DoSomething);
th1.Start(1);
th2.Start(2);
th3.Start();
//Make sure all thread is ended before the next iteration.
while(th1.IsAlive){ Thread.Sleep(200); }
while(th2.IsAlive){ Thread.Sleep(200); }
while(th3.IsAlive){ Thread.Sleep(200); }
}
Console.Read();
}
public class EventHolder
{
static SortedList<int, ManualResetEvent> MyManualEventList = new SortedList<int, ManualResetEvent>();
public EventHolder()
{
MyManualEventList = new SortedList<int, ManualResetEvent>();
Signaler.SignalMyManualEvent += OnSignalMyManualEvent;
}
void OnSignalMyManualEvent(int listindex)
{
try { MyManualEventList[listindex].Set(); }
catch(Exception e)
{
Console.WriteLine("Exception throws at " + System.DateTime.Now.ToString() +" Exception Message:"
Console.WriteLine(e.Message);
int temp = 0; //*Here is a breakpoint! To watch local variables when exception happens.
}
}
public void Add2List(object listindex)
{
ManualResetEvent MyManualEvent = new ManualResetEvent(false);
MyManualEvent.Reset();
MyManualEventList.Add((int)listindex, eve);
//in this test, this countdownevent need to be signaled twice, for it has to wait until all 2 event been added to MyManualEventList
Signaler.StartTrySignal.Signal();
MyManualEvent.WaitOne();
Console.WriteLine("Event" + ((int)listindex).ToString() + " been detected at " + System.DateTime.Now.Tostring());
MyManualEvent.Dispose();
}
}
public class Signaler
{
public delegate void Signalhandler(int listindex);
public static event Signalhandler SignalMyManualEvent;
public static CountDownEvent StartTrySignal = new CountDownEvent(2); // signaled twice so that the 2 manual events were added to sortedlist
void RaiseSignalMyManualEvent(int listindex)
{
var vr = SignalMyManualEvent;
if(vr != null)
vr(listindex);
}
int i = 0, j = 0, k = 0;
// here i use 2 prime numbers to simulate the happening of 2 random events
public Signaler()
{
StartTrySignal.Reset();
}
public void DoSomething()
{
StartTrySignal.Wait(); // wait for the 2 manual events been added to sortedlist
//To signal MyManualEventList[1] or MyManualEventList[2]
while(i + j == 0)
{
Random rnd = new Random();
k = rnd.Next();
if(k % 613 == 0) { i = 1; Console.WriteLine("Event1 Raised!"; RaiseSignalMyManualEvent(1); }
else if(k % 617 == 0) { j = 1; Console.WriteLine("Event1 Raised!"; RaiseSignalMyManualEvent(2); }
}
//if MyManualEventList[1] has not been signaled, wait something to happen, and signal it.
while(i == 0)
{
Random rnd = new Random();
k = rnd.Next();
if(k % 613 == 0)
{
i = 1;
if(j>0)
{
m++;
Console.WriteLine("All 2 Events Raised! - iteration " + m.ToString());
}
RaiseSignalMyManualEvent(1);
}
}
//if MyManualEventList[2] has not been signaled, wait something to happen, and signal it.
while(j == 0)
{
Random rnd = new Random();
k = rnd.Next();
if(k % 617 == 0)
{
j = 1;
m++;
Console.WriteLine("All 2 Events Raised! - iteration " + m.ToString());
RaiseSignalMyManualEvent(2);
}
}
}
}
public class Counter //Provide a number to record iteration
{
public static int m = 0;
}
}
Result:
Sorry for do not have enough reputation to post images.
At the line where there's a breakpoint, system throws exception " the given key is not in dictionary". this exception happens randomly, sometimes because th1 disposed <2, MyManualEvent> or th2 disposed <1, MyManualEvent> , sometimes none has been disposed but it just cannot find anyone.
I run this program 3 times, exception happens at iteration12, iteration45, and iteration0 (at the beginning).
OK 2 answers
1: Your code returns "Event 1" after "All events", because the two console.writelines are in a race condition (the last while loop is never iterated)
2: the 'System' distingushes between the two ManualResetEvent objects becuase it references the SortedList you put them in. ie.
static SortedList<int, ManualResetEvent> MyManualEventList
= new SortedList<int, ManualResetEvent>();
public EventHolder() { Signaler.SignalMyManualEvent
+= OnSignalMyManualEvent; }
void OnSignalMyManualEvent(int listindex)
{
MyManualEventList[listindex].Set();
}
when you raise event 1 you call set on Item 1 in the SortedList and when you raise Event 2 you call set on Item 2 in the list.
this is bad because the calling code has no idea which thread it is allowing to continue and you could well get a null exception

Proper Threading Technique in C# (using Timer + Receive Handler + BackgroundWorker)

I'm new to threading, and I am trying to use it in a program (front end for a hardware test), but running into heavy time delays - clearly I'm doing something wrong.
My Setup:
I have a hardware controller hooked up to my PC. I am communicating with it via serial to read values from 4 sensors. This communication sits within receiveHandler,
private void receiveHandler(object sender, DataStreamEventArgs e)
{
byte[] dataBytes = e.Response;
//throws it into a string
//parses the string with delimiters
//adds all items to a linked-list "queue"
}
I have a timer (System.Timers.Timer) that will count the duration of the hardware test, do one calculation and ask the controller for the updated state of each sensor.
private void OnTimedEvent(object sender, EventArgs e)
{
test.duration = ++tickCounter;
test.ampHoursOut = (test.ampHoursOut * 3600 + test.amperage * 1) / 3600;
sendToController("CH2.GETANALOG"); //repeat this line once for each sensor
}
I currently have a BackgroundWorker that will take the data collected from the controller (that I stuck in a "queue" in receiveHandler) and comprehend it + do some calculations + update the UI.
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
while (!worker.CancellationPending)
{
//do a whole lot of logic
}
}
My Problem:
One of my controller inputs is reading the state of a hardware switch. I notice that there is a 5 second delay between when I hit the switch and when it's state changes on the UI.
There must be a heavy delay in my processing. I imagine BackgroundWorker isn't catching up with all of the information coming in. I'm asking for information every second (via the timer), so maybe this needs to slow down - but I would like to have information update every second to have an accurate test.
Am I using the timer, backgroundworker, and receive handler correctly? What can be done to speed up my communication + processing?
I'm a self-taught programmer and don't necessarily (as you can probably see) know all of the basic concepts. Any advice would be appreciated :)
Thanks, Julia
EDIT: Code:
//variables used (added relevant ones so code is better understood)
private static System.Timers.Timer timer;
LinkedList<string> incomingData = new LinkedList<string>();
string lastDatum = "";
/// <summary>
/// Method that is called when serial data is received from the controller.
/// The data is received in bytes, converted to a string and parsed at each "::" which represents a new message.
/// The last 'item' from the data is saved and added to the front of the next set of data, in case it is incomplete.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void receiveHandler(object sender, DataStreamEventArgs e)
{
byte[] dataBytes = e.Response;
string data = lastDatum + System.Text.Encoding.Default.GetString(dataBytes); // reads the next available line coming from the serial port
UpdateDataTextbox(data); // prints it out for debuging purposes
lastDatum = ""; // just in case
char[] delimiters = new char[] { ':' }; // splits the data at the ":" symbol, deleting empty entries.
Queue<string> dataQueue = new Queue<string>(data.Split(delimiters,
StringSplitOptions.RemoveEmptyEntries));
while(dataQueue.Count > 1)
{
string str = dataQueue.Dequeue().Replace("\r", string.Empty).Replace("\n", string.Empty).Replace(":", string.Empty).Trim(); // remove all useless characters
if (str != "")
{
incomingData.AddLast(str); // add to a queue that can be accessed by the background worker for processing & outputting.
}
}
lastDatum = dataQueue.Dequeue(); // Last data item in a transmission may be incomplete "CH1.GETDA" and thus it is appended to the front of the next list of data.
}
/// <summary>
/// Background Worker thread will be used to continue testing the connection to the controller,
/// process messages in the incoming message queue (actually a linked list),
/// and sends new messages for updated data.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
while (!worker.CancellationPending)
{
if (!relayBoard.OpenConn())
{
MessageBox.Show("Connection to controller has been lost.");
testInterrupted = "Lost connection. Time = " + test.duration;
UpdateStatusText(false);
ShutErDown();
}
//update test time & Ah out
timeSpan = TimeSpan.FromSeconds(tickCounter);
UpdateDurationTextbox(timeSpan.ToString()); // update display with duration
UpdateAhTextbox(test.ampHoursOut.ToString());
/////////////////////////////////////////////////////
if (incomingData.Count > 0)
{
string dataLine = "";
try
{
dataLine = incomingData.First();
}
catch (System.InvalidOperationException emai)
{
break; //data hasn't come in yet, it will by the time this runs next.
}
incomingData.RemoveFirst();
if (dataLine.Contains("GET")) // some sensor values (analog/temp/digital) has come in
{
if (dataLine.Contains("GETANALOG")) // an analog value has come in
{
int index = dataLine.IndexOf("CH");
int pin = (int)Char.GetNumericValue(dataLine[index + 2]);
double value = 0;
int dataLineLength = dataLine.Length;
if (dataLineLength > 13) // value is appended to end of line
{
try
{
value = Convert.ToDouble(dataLine.Substring(13));
}
catch // can't convert to double
{
int index2 = dataLine.IndexOf("CH", 3);
if (index2 != -1) // there happen to be two sets of commands stuck together into one
{
string secondHalf = dataLine.Substring(index2);
incomingData.AddFirst(secondHalf);
}
}
}
else // value is on the next line
{
try
{
value = Convert.ToDouble(incomingData.First());
incomingData.RemoveFirst();
}
catch // can't convert to double
{
MessageBox.Show("Error occured: " + dataLine);
}
}
switch (pin)
{
case 1:
ReadVoltage(value);
break;
case 2:
ReadAmperage(value);
break;
}
}
else if (dataLine.Contains("GETTEMP")) // received reply with temperature data
{
int index = dataLine.IndexOf("CH");
int pin = (int)Char.GetNumericValue(dataLine[index + 2]); // using index of CH, retrieve which pin this message is coming from
double value = 0;
int dataLineLength = dataLine.Length;
if (dataLineLength > 11) // value is appended to end of line
{
try
{
value = Convert.ToDouble(dataLine.Substring(11));
}
catch // can't convert to double
{
int index2 = dataLine.IndexOf("CH", 3);
if (index2 != -1) // there happen to be two sets of commands stuck together into one
{
string secondHalf = dataLine.Substring(index2);
incomingData.AddFirst(secondHalf);
}
}
}
else // value is on the next line
{
value = Convert.ToDouble(incomingData.First());
incomingData.RemoveFirst();
}
ReadTemperature(pin, value);
}
else // must be CH3.GET
{
int index = dataLine.IndexOf("CH");
int pin = (int)Char.GetNumericValue(dataLine[index + 2]); // using index of CH, retrieve which pin this message is coming from
if (pin == 3) // only care if it's pin 3 (BMS), otherwise it's a mistake
{
double value = 0;
int dataLineLength = dataLine.Length;
if (dataLineLength > 7) // value is appended to end of line
{
try
{
value = Convert.ToDouble(dataLine.Substring(7));
}
catch // can't convert to double
{
int index2 = dataLine.IndexOf("CH", 3);
if (index2 != -1) // there happen to be two sets of commands stuck together into one
{
string secondHalf = dataLine.Substring(index2);
incomingData.AddFirst(secondHalf);
}
}
}
else // value is on the next line
{
value = Convert.ToDouble(incomingData.First());
incomingData.RemoveFirst();
}
ReadBMS(value);
}
}
}
else if (dataLine.Contains("REL")) // received reply about relay turning on or off.
{
if (dataLine.Contains("RELS")) // all relays turning on/off
{
if (dataLine.Contains("ON"))
{
for (int pin = 1; pin <= 4; pin++)
{
test.contactors[pin] = true;
}
}
else // "OFF"
{
for (int pin = 1; pin <= 4; pin++)
{
test.contactors[pin] = false;
}
}
}
else // single relay is turning on/off
{
int index = dataLine.IndexOf("REL");
int pin = (int)Char.GetNumericValue(dataLine[index + 3]);
if (dataLine.Contains("ON"))
{
test.contactors[pin] = true;
}
else if (dataLine.Contains("OFF"))
{
test.contactors[pin] = false;
}
else if (dataLine.Contains("GET"))
{
if (Convert.ToInt32(incomingData.First()) == 1)
test.contactors[pin] = true;
else
test.contactors[pin] = false;
incomingData.RemoveFirst();
}
}
}
}
}
}
/// <summary>
/// Main timer used to log the duration of the test, calculate amp hours
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnTimedEvent(object sender, EventArgs e)
{
test.duration = ++tickCounter;
// calculate Ah
test.ampHoursOut = (test.ampHoursOut * 3600 + test.amperage * 1) / 3600;
//read & output v, a, bms state
//sendToController("CH1.GETANALOG"); // get voltage
sendToController("CH2.GETANALOG"); // get amperage
sendToController("CH3.GET"); // get BMS state
//read & output temperature
sendToController("CH4.GETTEMP"); // get temperature
sendToController("CH5.GETTEMP");
}

Categories