Skip to content

Instantly share code, notes, and snippets.

@uzbekdev1
Forked from svvitale/EventLog.cs
Created April 23, 2019 07:45
Show Gist options
  • Save uzbekdev1/dba56f7debe8ad8dd3973411418d938d to your computer and use it in GitHub Desktop.
Save uzbekdev1/dba56f7debe8ad8dd3973411418d938d to your computer and use it in GitHub Desktop.
C# class that reads RFID cards and inserts each new read into a local MongoDB store. Reads are then pushed to a RESTful web service in a background task.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Forms;
using RFIDeas_pcProxAPI;
using System.Net;
using System.Threading;
using System.IO;
using Newtonsoft.Json;
namespace Node {
public partial class EventLog : Form {
// Local cache task list
private List<Task> activeTasks = new List<Task>();
// Database polling task
private Task dbPollingTask;
// Cancellation Token
private CancellationTokenSource databasePollCancel = new CancellationTokenSource();
// Event grid and last card tracking by reader ID
private BindingList<EventData> eventDataGrid = new BindingList<EventData>();
private static Dictionary<short, String> lastCardIDbyDeviceID = new Dictionary<short, String>();
// Tap metrics
private int tapsRecorded = 0;
private int tapsSentToWeb = 0;
private int tapsDuplicate = 0;
// Set of devices that was present during last polling cycle.
HashSet<short> lastPolledDevices = new HashSet<short>();
public EventLog() {
InitializeComponent();
loadConfig();
// Only search for USB prox readers
pcProxDLLAPI.SetConnectProduct( pcProxDLLAPI.PRODUCT_PCPROX );
pcProxDLLAPI.SetDevTypeSrch( pcProxDLLAPI.PRXDEVTYP_USB );
// See what's out there
RefreshDevices();
// Start the read timer. Hardware limits polling to no less than 250ms between reads.
readProxTimer.Start();
dataGridView1.DataSource = eventDataGrid;
// If enabled, start polling our local database for items that need to be pushed to web services.
if ( chkWebEnabled.Checked ) {
startDatabasePolling();
}
}
private void RefreshDevices() {
// Disconnect, reconnect, and update our visible device count.
pcProxDLLAPI.USBDisconnect();
if ( pcProxDLLAPI.usbConnect() == 1 ) {
txtDeviceCount.Text = pcProxDLLAPI.GetDevCnt().ToString();
}
else {
txtDeviceCount.Text = 0.ToString();
}
}
private void EventLog_FormClosing( object sender, FormClosingEventArgs e ) {
// Shut down all active tasks gracefully.
readProxTimer.Stop();
stopDatabasePolling();
saveConfig();
Task.WaitAll( activeTasks.ToArray() );
}
private void loadConfig() {
txtUrl.Text = Properties.Settings.Default.WebServicesURL;
txtEventID.Text = Properties.Settings.Default.WebServicesEventID;
chkWebEnabled.Checked = Properties.Settings.Default.WebServicesEnabled;
}
private void saveConfig() {
Properties.Settings.Default.WebServicesURL = txtUrl.Text;
Properties.Settings.Default.WebServicesEventID = txtEventID.Text;
Properties.Settings.Default.WebServicesEnabled = chkWebEnabled.Checked;
Properties.Settings.Default.Save();
}
private void startDatabasePolling() {
// Start a task that polls the database and posts to the web service
dbPollingTask = Task.Factory.StartNew( async () => // <- marked async
{
while ( true ) {
foreach ( EventData data in Mongo.GetLocalEvents() ) {
try {
// Update event ID
data.EventID = txtEventID.Text;
// Serialize to JSON
string json = JsonConvert.SerializeObject( data );
// HTTP POST to the web service
HttpWebRequest http = WebRequest.CreateHttp( txtUrl.Text );
http.Accept = "application/json";
http.ContentType = "application/json";
http.Method = "POST";
http.ContentLength = json.Length;
http.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)";
using ( var writer = new StreamWriter( http.GetRequestStream() ) ) {
writer.Write( json );
writer.Close();
}
HttpWebResponse response = null;
try {
response = (HttpWebResponse) http.GetResponse();
// If we get this far, the web post went fine. Update the local database
data.SentToWeb = true;
}
catch ( WebException ex ) {
if ( ( (HttpWebResponse) ex.Response ).StatusCode == HttpStatusCode.Conflict ) {
// Succeeded, but the server has identified this record as a duplicate.
data.SentToWeb = true;
Interlocked.Increment( ref this.tapsDuplicate );
}
}
finally {
if ( response != null ) {
response.Close();
}
}
// Check if we need to update/save the local record.
if ( data.SentToWeb == true ) {
Mongo.UpdateEvent( data );
Interlocked.Increment( ref this.tapsSentToWeb );
}
}
catch {
// No matter what happens, keep trying to send.
}
}
await Task.Delay( 250, databasePollCancel.Token ); // <- await with cancellation
}
}, databasePollCancel.Token );
}
private void stopDatabasePolling() {
if ( dbPollingTask != null ) {
// Cancel the Polling
databasePollCancel.Cancel();
// Wait for the task to complete
try {
dbPollingTask.Wait();
}
catch ( AggregateException ) {
// Task cancelled successfully
}
}
}
private void ReportPlugStatusChanges( ref HashSet<short> polledDevices ) {
// current devices - previous devices = new devices
HashSet<short> newDevices = new HashSet<short>( polledDevices );
newDevices.ExceptWith( lastPolledDevices );
foreach ( short newLUID in newDevices ) {
HipChat.SendInfo( String.Format( "Reader #{0} attached.", newLUID ) );
}
// previous devices - current devices = removed devices
HashSet<short> removedDevices = new HashSet<short>( lastPolledDevices );
removedDevices.ExceptWith( polledDevices );
foreach ( short newLUID in removedDevices ) {
HipChat.SendError( String.Format( "Reader #{0} unplugged.", newLUID ) );
}
// Done reporting, current data becomes previous data.
lastPolledDevices = polledDevices;
}
private void readProxTimer_Tick( object sender, EventArgs e ) {
readProxTimer.Stop();
HashSet<short> polledDevices = new HashSet<short>();
for ( short devIdx = 0; devIdx < pcProxDLLAPI.GetDevCnt(); devIdx++ ) {
if ( pcProxDLLAPI.SetActDev( devIdx ) != 0 ) {
// Device is valid, try to read a card
String newCardID = pcProxDLLAPI.getCardID();
short readerID = pcProxDLLAPI.GetLUID();
// Add this reader's LUID to our hash of polled devices.
polledDevices.Add( readerID );
// Initialize the last card ID if it's not already present
if ( !lastCardIDbyDeviceID.ContainsKey( readerID ) ) {
lastCardIDbyDeviceID[readerID] = "";
}
if ( newCardID != lastCardIDbyDeviceID[readerID] ) {
if ( newCardID != "" ) {
// Create the EventData object and add it to our log
EventData data = new EventData( newCardID, readerID );
eventDataGrid.Add( data );
// Only maintain the last 50 events
if ( eventDataGrid.Count > 50 ) {
eventDataGrid.RemoveAt( 0 );
}
// Create a new task for the actual database write. i.e. do it in another thread.
activeTasks.Add( Task.Factory.StartNew( () => {
// Add to the local database
Mongo.AddEvent( data );
Interlocked.Increment( ref tapsRecorded );
} ) );
}
// Update our saved card ID
lastCardIDbyDeviceID[readerID] = newCardID;
}
}
}
statusLabel.Text = String.Format( "Recorded {0} / {1} Sent / {2} Duplicates", this.tapsRecorded, this.tapsSentToWeb, this.tapsDuplicate );
ReportPlugStatusChanges( ref polledDevices );
RefreshDevices();
readProxTimer.Start();
}
private void chkWebEnabled_CheckedChanged( object sender, EventArgs e ) {
txtEventID.Enabled = txtUrl.Enabled = !chkWebEnabled.Checked;
if ( chkWebEnabled.Checked ) {
startDatabasePolling();
}
else {
stopDatabasePolling();
}
}
private void status_DoubleClick( object sender, EventArgs e ) {
// Clear metrics
Interlocked.Exchange( ref tapsSentToWeb, 0 );
Interlocked.Exchange( ref tapsRecorded, 0 );
Interlocked.Exchange( ref tapsDuplicate, 0 );
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment