C# Task<> make the events binded to an object doesnt fire - c#

So... I'm working with a third-party library (zkemkeeper.dll) to make a winform applicaction to manage attendance devices. I'm trying to get several devices connected and attach some events to listen for some actions they will fire. Everithing it's fine running all sync. I run some methods and for each device I try to connect like this:
public bool connect(Device myDevice)
{
bool result=false;
if (!myDevice.isConn)
{
myDevice.zkDevice = null;
myDevice.zkDevice = new CZKEMClass();
myDevice.zkDevice.MachineNumber = myDevice.id;
if (myDevice.password.HasValue)
{
myDevice.zkDevice.SetCommPassword(myDevice.password.Value);
}
try
{
result = myDevice.zkDevice.Connect_Net(myDevice.ip, myDevice.port);
myDevice.error = result ? "" : "Could not connect to device";
}
catch (Exception ex)
{
myDevice.error = ex.Message;
result = false;
}
if(result)
{
//Bind events
if (myDevice.zkDevice.RegEvent(myDevice.id, 1))
{
myDevice.zkDevice.OnAttTransactionEx += new _IZKEMEvents_OnAttTransactionExEventHandler(device_OnTransactionEx);
}
}
myDevice.zkDevice.EnableDevice(myDevice.id, true);
myDevice.zkDevice.EnableClock(1);
}
return result;
}
My main problem is that this take a couple of seconds to connect to each device depending on the state of the network, so if I have 50 or 100 device the interface will freeze every time a device is connected or reconnected and this will make a huge delay.(besides that the application must always be functional)
Ok to solve that I use this:
private async Task connectAllAsync()
{
List<Task<bool>> lstTasks = new List<Task<bool>>();
foreach (var device in lstDevices)
{
lstTasks.Add(Task.Run(() => connect(device)));
}
var arrayComplete =await Task.WhenAll(lstTasks);
int countErr=arrayComplete.ToList().Where(n => n == false).Count();
if(countErr>0)
{
timerReconnect.Enabled = true;
}
}
After this the interface doesn't freeze,my connection to all devices is faster,even if I try to interrogate any device for information the device respond BUT when I try to trigger any event these doesnt fire, I dont know where is my mistake I think it could be related to atach the event on another thread or something like that... Help me give me some way to go, thanks in advance.
Edit: Im also tried making the "connect" function async an make it await for the Connect_Net response (which is part of the third party code)

You have an uncaptured closure. Try this:
foreach (var device in lstDevices)
{
var captured = device;
lstTasks.Add(Task.Run(() => connect(captured)));
}
Or just use LINQ to do it all:
lstTasks = lstDevices.Select
(
device => Task.Run
(
() => connect(device)
)
);
A totally different approach would get rid of Task.Run() and use Parallel.ForEach instead. If you use this approach, you'd have to store the results in a thread-safe container like ConcurrentBag<>.
var results = new ConcurrentBag<bool>();
Parallel.ForEach
(
lstDevices,
device =>
{
results.Add(connect(device));
}
);
int countErr = results.Count( x => x == false );
Or you could just use a counter:
volatile int countErr = 0;
Parallel.ForEach
(
lstDevices,
device =>
{
var ok = results.Add(connect(device));
if (!ok) Interlocked.Increment(ref countErr);
}
);

Related

C# Windows 10 Bluetooth LE can't connect to server

I'm developing a c# desktop api with forms where I want to receive ACC data from a BLE server und display them in a chart.
So I'm running in a connection problem and I can't find any solution.
I can find my LE server Device with the watcher.
DevicePairingResult dpr = await device.DeviceInformation.Pairing.PairAsync(DevicePairingProtectionLevel.Encryption);
returns me "AlreadyPaired"
But when I do
device = await BluetoothLEDevice.FromBluetoothAddressAsync(bluetoothAddress: eventArgs.BluetoothAddress);
mGattService = device.GetGattService(MotionService_GUID);
mCharacteristic = mGattService.GetCharacteristics(ACC_Characteristic_GUID)[0];
and then
var con = device.ConnectionStatus;
I receive "Disconnected" in con.
I am bound with de device on windows( I searched for it in Windows and entered the Code) but I am not connected(based on the Status in the windows info center).
I've read in another Thread in the windows c# developer page that it should not be necessary anymore to pair the device manually.
I'm pretty shure that the rest of my code works because sometimes I can get a connection( pretty confusing for me) and see the right Data in my chart.
Right now I just want to reach a stable connection before changing other part of my code.
Anyone any idea how to solve this?
Thx medTech
Edit:
Here is part of the Code:
Scanning for BLE
private void button1_Click(object sender, EventArgs e)
{
// Create Bluetooth Listener
var watcher = new BluetoothLEAdvertisementWatcher();
watcher.ScanningMode = BluetoothLEScanningMode.Active;
// Register callback for when we see an advertisements
watcher.Received += OnAdvertisementReceivedAsync;
// Wait 5 seconds to make sure the device is really out of range
watcher.SignalStrengthFilter.OutOfRangeTimeout = TimeSpan.FromMilliseconds(5000);
watcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(2000);
// Starting watching for advertisements
watcher.Start();
}
Connect to Server:
private async void OnAdvertisementReceivedAsync(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs)
{
// Filter for specific Device
if (eventArgs.Advertisement.LocalName == "MYDEVICE")
{
watcher.Stop();
var MotionService_GUID = new Guid("00002000-0000-1000-8000-00805F9B34FB");
var ACC_Characteristic_GUID = new Guid("00002001-0000-1000-8000-00805F9B34FB");
device = await BluetoothLEDevice.FromBluetoothAddressAsync(bluetoothAddress: eventArgs.BluetoothAddress);
DevicePairingResult dpr = await device.DeviceInformation.Pairing.PairAsync(DevicePairingProtectionLevel.Encryption);
mGattService = device.GetGattService(MotionService_GUID);
mCharacteristic = mGattService.GetCharacteristics(ACC_Characteristic_GUID)[0];
GattDeviceServicesResult result = await device.GetGattServicesAsync();
GattCommunicationStatus status1 = await ReadFromCharacteristicAsync(mCharacteristic);
var con = device.ConnectionStatus;
while (status1 == GattCommunicationStatus.Success)
{
try
{
status1 = await ReadFromCharacteristicAsync(mCharacteristic);
}
catch
{
Console.WriteLine("ERROR");
status1 = GattCommunicationStatus.Unreachable;
}
}
}
}
Read from Characteristic:
async Task ReadFromCharacteristicAsync(GattCharacteristic mCharacteristic)
{
GattReadResult readResult = await mCharacteristic.ReadValueAsync(BluetoothCacheMode.Uncached);
if (readResult.Status == GattCommunicationStatus.Success)
{
byte[] data = new byte[readResult.Value.Length];
DataReader.FromBuffer(readResult.Value).ReadBytes(data);
if (chart1.IsHandleCreated)
{
this.Invoke((MethodInvoker)delegate { updateChart(data); });
}
return readResult.Status;
}
return readResult.Status;
}
Terminate Connection
private async Task<bool> ClearBluetoothLEDeviceAsync()
{
mCharacteristic.Service.Dispose();
mGattService.Dispose();
await device.DeviceInformation.Pairing.UnpairAsync();
device?.Dispose();
device = null;
GC.Collect();
return true;
}
SO now when I connect the first time to the Server, I only receive zeros which shows me that the there might be a authentication Error.
After that I always receive this Error:
"System.ArgumentException" in mscorlib.dll with a notification that there is noch executable Code left because all Threads are doing some asynchronous stuff.
This Error gets thrown when I try to read from the Characteristic.
I never coded in c# before so I am not shure if there is an error in my asynchronous part oder the communication part.
Thanks you
Pairing is not the same as connecting!
I really advise using the BLE-advertisementWatcher to select and connect to your device.
The reason is that many BLE-devices don't save their pairing status.
In windows device-watcher once paired, the device stays paired even if it is switched off or out of reach.
Also many times the connection status is kept, unless the device is unpaired and disposed in code or removed in windows settings.
All BLE-devices that I know of start advertising as soon as there is no connection for some time.
This time depends on the device, but most of the time within seconds.
So don't pair but just connect if the device is advertising.

Redirect to a different aspx page and run the next code in background (.NET 4.5.2)

I am working on an ASP.NET Webform project (legacy code).On my button_click event i am sending sms message to all the datas populated in this.
var customerSMS = BusinessLayer.SMS.SmsSetup.GetAllCustomerSMS(OfficeId);
This takes around 15seconds to do all the computing and get the data(1000rows)
from the Db.And for each data it runs through the loop and does validation and
sends the sms and it does take time.I want to do this task in background and
redirect the user to the index page and the background process continues till it
gets out of the loop.I am new to this and still learning this beautiful
language C#.I did go through this amazing Asynchronous Programming async/await
and Multithreading approach and got hold of it only in simple WindowsForm
applications.Any reference/code snippet/best approach with a simple explanation for my case would be helpful.
My button click event code :
protected void ReturntoDashboard_Click(object sender, EventArgs e)
{
sms = Everest.Net.BusinessLayer.SMS.SmsSetup.GetSmsSetUp(OfficeId);
if (sms.EnableSmsData && sms.SmsCount > 0)
{
#region Loan Section
var smsLoan = Everest.Net.BusinessLayer.SMS.SmsSetup.GetLoanId(s.Sms_AccountNumber);
var loanId =
BusinessLayer.SMS.SmsSetup.GetLoanIdValue(s.Sms_AccountNumber);
var dateexceeded =
BusinessLayer.SMS.SmsSetup.IsDateExceeded(loanId);
if (smsLoan != null && dateexceeded == true)
{
foreach (Common.SMS.SMSSetup sm in smsLoan)
{
var smsClosingBalanceLoan = BusinessLayer.SMS.SmsSetup.GetAmountForLoanAlert( sm.LoanId,
BusinessLayer.Core.DateConversion
.GetCurrentServerDate()
.AddDays(sms.DaysbeforeLoanalerts).ToString());
if (smsClosingBalanceLoan != null)
{
if (smsClosingBalanceLoan.LoanAmountToPay > 0)
{
int smsSentAlertCount = sms.LoanAlertCount;
var logCount = BusinessLayer.SMS.SmsSetup.GetLoanSmsAlertSentCount(DateTime.Now.AddDays(-smsSentAlertCount).ToString("yyyy-MM-dd"), DateTime.Now.ToString("yyyy-MM-dd"), sm.LoanAccountNumber);
if (logCount < smsSentAlertCount)
{
smsLog = new Everest.Net.Common.SMS.SMSSetup();
finalMessage = "Dear Member, Your Loan accnt " + sm.LoanAccountNumber + " with Principal"+ "+" + "Int Amnt: Rs." + smsClosingBalanceLoan.LoanAmountToPay + " need to be payed.Thank You," + officeName.OfficeName;
smsLog.LogServiceType = "Loan";
smsLog.LogSmsType = s.Sms_SmsType;
smsLog.LogSmsMessage = finalMessage;
smsLog.LogCustomerId = s.CustomerId.ToString();
smsLog.LogAccountNumber = s.Sms_AccountNumber;
smsLog.LogAccountType = s.Sms_AccountType;
smsLog.LogSmsSentDate = BusinessLayer.Core.DateConversion.GetCurrentServerDate();
smsLog.LogSmsFailedDate = "";
smsLog.LogSentStatus = true;
smsLog.LogUserId = UserId;
smsLog.LogSmsFailedMessage = "";
try
{
var result = Everest.Net.BusinessLayer.SMS.smsParameters.SendSMS(sms.FromNum, sms.Token, sms.Url, cellNum, finalMessage);
}
catch (Exception ex)
{
smsLog.LogSmsFailedDate = System.DateTime.Now.ToString("MM/dd/yyyy HHmmss");
smsLog.LogSentStatus = false;
smsLog.LogSmsFailedMessage = ex.Message;
Everest.Net.BusinessLayer.SMS.SmsSetup.InsertSMSLog(smsLog);
}
sms = Everest.Net.BusinessLayer.SMS.SmsSetup.GetSmsSetUp(OfficeId);
sms.SmsCount = sms.SmsCount - 1;
Everest.Net.BusinessLayer.SMS.SmsSetup.UpdateSmsSetup(sms);
Everest.Net.BusinessLayer.SMS.SmsSetup.InsertSMSLog(smsLog);
}
}
}
}
}
}
}
}
catch (Exception ex)
The ideal solution would remove the responsibility of sending the SMS from the web application itself. Instead, the web application should create a database record containing the message and recipient addresses, and a separate background job (e.g. a Windows Service) should poll the database and send SMS messages when neeeded. This is the best solution in terms of fault tolerance and auditability, because there is a permanent record of the messaging job which can be resumed if the system fails.
That being said, maybe you don't want to go to all that trouble. If you feel strongly that you wish to send the SMS directly from the ASP.NET application, you will need to create a Task and queue it to run using QueueBackgroundWorkitem. You will need to refactor your code a bit.
Move all the logic for sending the SMS into a separate function that accepts all the information needed as parameters. For example,
static void SendSMS(string[] addresses, string messagetext)
{
//Put your SMS code here
}
When you need to call the function, queue it as a background item
HostingEnvironment.QueueBackgroundWorkItem(a => SendSMS(addresses, messageText));
If your worker task needs to access its own cancellation token (e.g. if it is supposed to loop until cancelled), it is passed as an argument to the lambda expression. So you could modify the prototype
static void SendSMS(string[] addresses, string messagetext, CancellationToken token)
{
while (!token.IsCancellationRequested)
{
//Put your code here
}
}
and pass it thus:
HostingEnvironment.QueueBackgroundWorkItem(token => SendSMS(addresses, messageText, token));
Placing the task in the background queue ensures that ASP.NET keeps track of the thread, doesn't try to garbage collect it, and shuts it down properly when the application pool needs to shut down.
After queuing the background operation, your page can render is content per usual and conclude the HTTP response while the task continues to execute.

BackgroundTask geofence triggering

I am trying to showToast when the phone leaves or enter the geofenced location (which is set elsewhere and passed in). The issue is that when the app is in the background the trigger does not occur and I don't see the showToast message. I am changing the location manually using an emulator on my PC.
Background Tasks> Location is set under the app manifest.
This is the code I am using to build the Geofence and backgroundtask
//Creates Geofence and names it "PetsnikkerVacationFence"
public static async Task SetupGeofence(double lat, double lon)
{
await RegisterBackgroundTasks();
if (IsTaskRegistered())
{
BasicGeoposition position = new BasicGeoposition();
position.Latitude = lat;
position.Longitude = lon;
double radius = 8046.72; //5 miles in meters
Geocircle geocircle = new Geocircle(position, radius);
MonitoredGeofenceStates monitoredStates = MonitoredGeofenceStates.Entered | MonitoredGeofenceStates.Exited;
Geofence geofence = new Geofence("PetsnikkerVacationFence", geocircle, monitoredStates, false);
GeofenceMonitor monitor = GeofenceMonitor.Current;
var existingFence = monitor.Geofences.SingleOrDefault(f => f.Id == "PetsnikkerVacationFence");
if (existingFence != null)
monitor.Geofences.Remove(existingFence);
monitor.Geofences.Add(geofence);
}
}
//Registers the background task with a LocationTrigger
static async Task RegisterBackgroundTasks()
{
var access = await BackgroundExecutionManager.RequestAccessAsync();
if (access == BackgroundAccessStatus.Denied)
{
}
else
{
var taskBuilder = new BackgroundTaskBuilder();
taskBuilder.Name = "PetsnikkerVacationFence";
taskBuilder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable));
taskBuilder.SetTrigger(new LocationTrigger(LocationTriggerType.Geofence));
taskBuilder.TaskEntryPoint = typeof(Petsnikker.Windows.Background.GeofenceTask).FullName;
var registration = taskBuilder.Register();
registration.Completed += (sender, e) =>
{
try
{
e.CheckResult();
}
catch (Exception error)
{
Debug.WriteLine(error);
}
};
}
}
static bool IsTaskRegistered()
{
var Registered = false;
var entry = BackgroundTaskRegistration.AllTasks.FirstOrDefault(keyval => keyval.Value.Name == "PetsnikkerVacationFence");
if (entry.Value != null)
Registered = true;
return Registered;
}
}
}
This code is where I monitor the state of the geofence.
This is where the Entry point in the appxmanifest is pointing
public sealed class GeofenceTask : IBackgroundTask
{
public void Run(IBackgroundTaskInstance taskInstance)
{
var monitor = GeofenceMonitor.Current;
if (monitor.Geofences.Any())
{
var reports = monitor.ReadReports();
foreach (var report in reports)
{
switch (report.NewState)
{
case GeofenceState.Entered:
{
ShowToast("Approaching Home",":-)");
break;
}
case GeofenceState.Exited:
{
ShowToast("Leaving Home", ":-)");
break;
}
}
}
}
//deferral.Complete();
}
private static void ShowToast(string firstLine, string secondLine)
{
var toastXmlContent =
ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02);
var txtNodes = toastXmlContent.GetElementsByTagName("text");
txtNodes[0].AppendChild(toastXmlContent.CreateTextNode(firstLine));
txtNodes[1].AppendChild(toastXmlContent.CreateTextNode(secondLine));
var toast = new ToastNotification(toastXmlContent);
var toastNotifier = ToastNotificationManager.CreateToastNotifier();
toastNotifier.Show(toast);
Debug.WriteLine("Toast: {0} {1}", firstLine, secondLine);
}
}
After looking at your code, it seems that your code is correct.
In order to fire the Geofence Backgroundtask to show the toast information, please make sure the following things:
1) Please make sure that you have done all the necessary configuration in the Package.appxmanifest for registering the BackgroundTask, for example you have set the correct EntryPoint and added the “Location” capabilities.
For the detailed information, you can try to compare your Package.appxmanifest with the official sample Geolocation’s Package.appxmanifest.
Please also check: Create and register a background task and Declare background tasks in the application manifest.
2) Please make sure that you know how to set the location in the Emulator manually for simulating the phone leave or enter the geofenced location. For more information about how to set location in the emulator, please check the following article:
https://msdn.microsoft.com/library/windows/apps/dn629629.aspx#location_and_driving .
3) Please make sure that your second position in your emulator is not really far away from the geofences that you have defined in the first time, because the emulator behaves like a real device, and the device doesn’t expect to suddenly move from New York to Seattle. Or the BackgroundTask will not be fire immediately.
4) Background tasks for geofencing cannot launch more frequently than every 2 minutes. If you test geofences in the background, the emulator is capable of automatically starting background tasks. But for the next subsequent background tasks, you need to wait for more than 2 minutes.
Besides, I will recommend you refer to the following article about how to use the Windows Phone Emulator for testing apps with geofencing:
https://blogs.windows.com/buildingapps/2014/05/28/using-the-windows-phone-emulator-for-testing-apps-with-geofencing/ .
Thanks.

using EasyNetQ multiple Handler for one consumer does not work

We are using RabbitMQ for queuing messages in C# .Net (EasyNetQ Client).
i want one consumer app (C# Console App) listen to one queue and provide multiple handlers for each message type.
I implemented this scenario and my code is here :
using (var advancedBus = RabbitHutch.CreateBus("host=localhost;prefetchcount=100")
.Advanced)
{
var queue = advancedBus.QueueDeclare("MyQueue");
advancedBus.Consume(queue, x => x
.Add<MessageType1>((message, info) =>
{
Console.WriteLine("MessageType1 Body : " + message.Body.Body);
})
.Add<MessageType2>((message, info) =>
{
Console.WriteLine(" MessageType2 Body: " + message.Body.Body);
}).ThrowOnNoMatchingHandler = false);
}
My Problem :
But when i execute this consumer it does nothing. do not any thing happen.
i publish messages to that queue like this :
using (var advancedBus = RabbitHutch.CreateBus("host=localhost").Advanced)
{
var queue = advancedBus.QueueDeclare("MyQueue");
if (advancedBus.IsConnected)
advancedBus.Publish(Exchange.GetDefault(), queue.Name, false, false,
new Message<MessageType1>(change));
else
result = false;
}
What is the problem.
Ok, after testing this code, these are the problems:
First of all, you're disposing your advancedBus right after you register for consumption. You need to remember that when you invoke IAdvanceBus.Consume, you're only registering a callback for each message. If you dispose the bus immediately after registration, your delegate can't be invoked since the connection was already closed. So, you'll to remove the using statement around the rabbit declaration (don't forget to dispose it when you're done):
var advancedBus = RabbitHutch.CreateBus("host=localhost;prefetchcount=100").Advanced
Second, the immediate flag has been deprecated and shouldn't be used, the message doesn't seem to be getting to the queue. Change Publish to:
advancedBus.Publish(Exchange.GetDefault(), queue.Name, true, false,
new Message<MessageType1>(change));
Also, if you're running this from a console application, don't forget to use Console.ReadKey so your main thread doesn't terminate.
Here's a working code sample:
static void Main()
{
var change = new MessageType1();
var advancedBus = RabbitHutch.CreateBus("host=localhost").Advanced;
ConsumeMessage(advancedBus);
var queue = advancedBus.QueueDeclare("MyQueue");
if (advancedBus.IsConnected)
{
advancedBus.Publish(Exchange.GetDefault(), queue.Name, true, false,
new Message<MessageType1>(change));
}
else
{
Console.WriteLine("Can't connect");
}
Console.ReadKey();
}
private static void ConsumeMessage(IAdvancedBus advancedBus)
{
var queue = advancedBus.QueueDeclare("MyQueue");
advancedBus.Consume(queue, registration =>
{
registration.Add<MessageType1>((message, info) =>
{
Console.WriteLine("Body: {0}", message.Body);
});
});
}

Misbehaving Service behviors

Basically I'm making a program to simulate a petrol station system.
My problem is that I'm trying to send a request through a WCF service such as this:
User Requests Pump to be activated ----> WCF SERVICE ----> Point of Sale
User starts pumping petrol<---- WCF SERVICE <---- Point of Sale Accepts
At the moment it works, but only sometimes.
This is how I try to get a response:
while(PumpserviceClient.getRequestedAcceptedStatusFromPos().Accepted == false)
{
PumpserviceClient.RequestPump(int.Parse(PumpID));
// needs to wait for pump to be activated
if (PumpserviceClient.getRequestedAcceptedStatusFromPos().Accepted == true /*&& PumpserviceClient.getRequestedAcceptedStatusFromPos().PumpNo == int.Parse(PumpID)*/)
{
MessageBox.Show(" The Pos has accepted your pump request");
// if its accepted you call
Customer.ActivatePump();
}
And these are the methods in the service:
bool Accepted= false;
bool Requested=false;
public void AcceptPump(int PumpNumber)
{
Accepted = true;
Requested = false;
int pumpnumber = PumpNumber;
PumpRequest.Accepted = Accepted;
PumpRequest.Requested = Requested;
}
public void RequestPump(int PumpNumber)
{
int pumpnumber = PumpNumber;
Requested = true;
Accepted = false;
PumpRequest.Accepted = Accepted;
PumpRequest.PumpNo = PumpNumber;
PumpRequest.Requested = Requested;
}
public void ResetRequest(int PumpNumber)
{
int pumpnumber = PumpNumber;
Requested = false;
Accepted = false;
PumpRequest.Accepted = Accepted;
PumpRequest.PumpNo = 0;
PumpRequest.Requested = Requested;
}
public Message getRequestedStatusFromPump()
{
return PumpRequest;
}
public Message getRequestedAcceptedStatusFromPos()
{
return PumpRequest;
}
}
and the point of sale system accepts the requests by:
if (Client.getRequestedStatusFromPump().Requested == true)
{
MessageBox.Show("Pump Number: "+Client.getRequestedStatusFromPump().PumpNo + " Is waiting to be accepted");
// need to press a button or something
Client.AcceptPump(Client.getRequestedStatusFromPump().PumpNo);
}
Code here http://www.pastebucket.com/8642
I read the code posted. You use the following attribute:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
This means your code will not multi-thread. But there is no guarantee multiple sessions won't make requests and "interrupt" each other's workflow.
For example:
Client A calls request pump
Client B calls reset pump
Client A reads... client A wonders why pump was reset.
Your code is written expecting the object to be by session. I'd suggest using this context mode and seeing if you have better luck.
The other option is to add session information to your model. I can't imagine why this would be useful. It certainly won't be easy.
The only way i found around this problem, without changing service behaviors was to create a new list
public void CreatePumpList()
{
WaitingPumps = new List<WaitingPumps>();
for (int i = 0; i < PumpLimit+1 ; i++)
{
WaitingPumps.Add(new WaitingPumps());
}
}
Then just use the pump Number as the index in this list so they don't get confused with each other.

Categories