Skip to content

Instantly share code, notes, and snippets.

@svvitale
Last active July 11, 2021 09:26
Show Gist options
  • Save svvitale/20f4e7ecc30082ac27b9 to your computer and use it in GitHub Desktop.
Save svvitale/20f4e7ecc30082ac27b9 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 );
}
}
}
@bachphi
Copy link

bachphi commented Oct 19, 2018

String newCardID = pcProxDLLAPI.getCardID();
The wrapper did not define this, is there a different wrapper for this?

Mr. Vitale, can you post the RFIDeas_pcProxAPI.cs.
TIA

@bachphi
Copy link

bachphi commented Oct 28, 2018

never mind, you must be having tight pockets

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment