/***
*
* Parts of this work are derived from sample code included in
* https://developer.impinj.com/modules/PDdownloads/visit.php?cid=6&lid=45
* and copyright 2007 by Impinj, Inc. That code is licensed under the Apache License, Version 2.0, and available at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Parts of this work use ZedGraph:
* ZedGraph is licensed under the Lesser or Library General Public License.
***/
/*
Copyright (c) 2009, University of Washington
Copyright (c) 2009, Intel Corporation
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following
disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the University of Washington nor Intel Corporation nor the names of its contributors may be
used to endorse or promote products derived from this software without specific prior written permission.
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 OWNER 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.
*/
/*
***************************************************************************
* Theory of Operation:
*
* Sorry, this is a bit out of date. Check the wiki for more current documentation.
*
* Reader runs autonomously, generates a report every 100ms.
*
* RFIDReader.UpdateROReport(...) thread parses the reports and
* calls MainFrm.HandleTagReceived(...) with each tag's information,
* encapsulated in a MyTag object.
* MainFrm.HandleTagReceived does sensor data extraction, etc., but doesn't touch GUI!
*
* timerUpdateGUI runs at 10 to 20 hz, and puts data from HandleTagReceived onto the GUI.
*
***************************************************************************
*/
/*
***************************************************************************
* Operating Modes:
*
* App has various modes (defined in enum GuiModes):
* a. Idle (reader disconnected)
* b. Ready (reader connected, not running)
* 1. User Inventory
*
*
*
***************************************************************************
*/
/*
***************************************************************************
* Adding a new sensor demo:
*
* Sorry, this is a bit out of date. Check the wiki for more current documentation.
*
* First, provide parsing and identifier information to the MyTag class
*
* Second, generate a new handler method in this class. Ex, see HandleAccelTagStats
*
* Third, edit the HandleTagReceived function to call your handler method upon seeing your sensor
*
* Fourth, edit timerUpdateGUI to display your data on the GUI.
*
* Do not add GUI code to the handler method, this can cause instability on some computers.
*
***************************************************************************
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Soap;
using System.Runtime.InteropServices;
using System.Threading;
using System.IO;
using System.Net;
using System.Diagnostics;
using ZedGraph;
using SaturnDemo;
using MIDI_Control_Demo;
using ReaderLibrary;
using Logging;
using BinkBonk;
//using AttenuatorTest;
namespace WISPDemo
{
public partial class MainFrm : Form, ReaderLibrary.IRFIDGUI
{
private SaturnDemo.Saturn saturn;
private MIDI_Control_Demo.Midi midi;
private BinkBonk.BinkBonk_Demo binkBonk;
//private RFIDReader reader;
private ReaderManager readerMgr;
private TagStats stats;
private WispHandleTags handleTags;
// Keep the default size information so we know by how much to stretch
// the tag list data grid view
private int formInitialHeight;
private int pnlTagListInitialHeight;
private Form setting;
private Form loggingForm;
private LoggingManager log;
public MainFrm()
{
InitializeComponent();
}
private void MainFrm_Load(object sender, EventArgs e)
{
// reader is our handle to the physical reader.
//reader = new RFIDReader(this);
handleTags = new WispHandleTags();
readerMgr = new ReaderManager(this, handleTags);
log = new WispLoggingManager();
// init the saturn object.
saturn = new SaturnDemo.Saturn();
// Init midi config
midi = new MIDI_Control_Demo.Midi();
// Init bink bonk
binkBonk = new BinkBonk.BinkBonk_Demo();
handleTags.setBinkBonkCallback(binkBonk);
// Setup axis labels for the various graphs.
InitSOCGraph();
InitTempGraph();
// Other init code
InitTagStats();
// Grab initial component sizes for easy resize
formInitialHeight = this.Height;
pnlTagListInitialHeight = dgvTagStats.Height;
// Init GUI operational mode to idle (disconnected)
SetMode(ReaderManager.GuiModes.Idle);
}
private string debugTextBoxContent = "";
/// <summary>
/// Appends text to the "LLRP Messages" text box
/// </summary>
/// <param name="appendToTop">Text to append</param>
public void AppendToDebugTextBox(string appendToTop)
{
debugTextBoxContent = appendToTop + "\r\n" + debugTextBoxContent.Substring(0, Math.Min(debugTextBoxContent.Length, 2000));
}
private string tagTextBoxContent = "";
/// <summary>
/// Appends text to the running Tag List text box
/// </summary>
/// <param name="appendToTop">Text to append</param>
public void AppendToMainTextBox(string appendToTop)
{
tagTextBoxContent = appendToTop + "\r\n" + tagTextBoxContent.Substring(0, Math.Min(tagTextBoxContent.Length, 2000));
}
//Handle went here! - ?
private void ClearGUIAll()
{
tagTextBoxContent = "";
debugTextBoxContent = "";
// Set the default values for max and min
ClearMaxMin();
ClearSOC();
stats.Clear();
ClearHandlerTimes();
ClearSOC();
}
#region Accelerometer
private double xMax = 0;
private double xMin = 100;
private double yMax = 0;
private double yMin = 100;
private double zMax = 0;
private double zMin = 100;
public void ClearMaxMin()
{
xMax = 0;
xMin = 1024;
yMax = 0;
yMin = 1024;
zMax = 0;
zMin = 1024;
}
private void UpdateGraphicsOnSaturn()
{
double xac, yac, zac;
xac = handleTags.GetCurrentX();
yac = handleTags.GetCurrentY();
zac = handleTags.GetCurrentZ();
if (chkFlipX.Checked) xac = 100 - xac;
if (chkFlipY.Checked) yac = 100 - yac;
saturn.ModelData(xac, yac, zac);
}
private void UpdateMidiGui()
{
double xac, yac, zac;
xac = handleTags.GetCurrentX();
yac = handleTags.GetCurrentY();
zac = handleTags.GetCurrentZ();
if (chkFlipX.Checked) xac = 100 - xac;
if (chkFlipY.Checked) yac = 100 - yac;
this.midi.ReOpenMidiConfig();
this.midi.updateMidi(xac, yac, zac);
}
private void UpdateAccelerometerGUI()
{
// actually do the work of updating the accelerometer gui elements
UpdateTiltBarsAndLabels(handleTags.GetCurrentX(), handleTags.GetCurrentY(), handleTags.GetCurrentZ());
GetMaxMinValues(); // accel min max
// Update saturn
if (chkSaturn.Checked)
{
UpdateGraphicsOnSaturn();
}
else
{
this.saturn.DisposeSaturn();
}
saturnFrames++;
// Update MIDI out if checked
if (chkMIDI.Checked)
{
UpdateMidiGui();
}
}
private void GetMaxMinValues()
{
lblXMax.Text = (Math.Round(xMax, 3)).ToString();
lblXMin.Text = (Math.Round(xMin, 3)).ToString();
lblDx.Text = (Math.Round(handleTags.GetDeltaX(), 3)).ToString();
lblYMax.Text = (Math.Round(yMax, 3)).ToString();
lblYMin.Text = (Math.Round(yMin, 3)).ToString();
lblDy.Text = (Math.Round(handleTags.GetDeltaY(), 3)).ToString();
lblZMax.Text = (Math.Round(zMax, 3)).ToString();
lblZMin.Text = (Math.Round(zMin, 3)).ToString();
lblDz.Text = (Math.Round(handleTags.GetDeltaZ(), 3)).ToString();
}
private void UpdateTiltBarsAndLabels(double xac, double yac, double zac)
{
double senseangleX = 0, senseangleY = 0;
if (zac != 0.0)
{
senseangleX = (float)((180 / 3.14149) * Math.Atan(xac / zac));
senseangleY = (float)(Math.Sign(zac) * (180 / 3.14149) * Math.Atan(yac / zac));
}
// Update Tilt bars and labels in GUI
if ((xac < 101) && (yac < 101) && (zac < 101))
{
if (xac > 0)
{
tbarX.Value = (int)xac;
lblTiltX.Text = (Math.Round(xac, 3)).ToString();
}
if (yac > 0)
{
tbarY.Value = (int)yac;
lblTiltY.Text = (Math.Round(yac, 3)).ToString();
}
if (zac > 0)
{
tbarZ.Value = (int)zac;
lblTiltZ.Text = (Math.Round(zac, 3)).ToString();
}
}
}
private void btnCalAccel_Click(object sender, EventArgs e)
{
// Fix x
double xcorr = MyTag.GetAccelCorrection("x");
xcorr = 50.0 / (handleTags.GetCurrentX() / xcorr);
Trace.WriteLine("New X correction Factor: " + xcorr.ToString());
// Fix y
double ycorr = MyTag.GetAccelCorrection("y");
ycorr = 50.0 / (handleTags.GetCurrentY() / ycorr);
Trace.WriteLine("New Y correction Factor: " + ycorr.ToString());
// Fix z
double zcorr = MyTag.GetAccelCorrection("z");
zcorr = 41.0 / (handleTags.GetCurrentZ() / zcorr);
Trace.WriteLine("New Z correction Factor: " + zcorr.ToString());
MyTag.SetAccelCorrection(xcorr, ycorr, zcorr);
}
private int saturnFrames = 0;
#endregion Accelerometer Variables
#region SOC Region
private void ClearSOC()
{
handleTags.ClearSOC();
updateSOCGraph();
}
private void InitSOCGraph()
{
// setup the axis titles, etc.
// get a reference to the GraphPane
GraphPane myPane = graphSOC.GraphPane;
// Set the Titles
myPane.Title.Text = "SOC WISP Data";
myPane.XAxis.Title.Text = "Sample";
myPane.YAxis.Title.Text = "ADC Output";
// Generate the line
LineItem myCurve = myPane.AddCurve(null,
handleTags.GetSOCData(), Color.Red, SymbolType.Diamond);
// Tell ZedGraph to refigure the
// axes since the data have changed
graphSOC.AxisChange();
}
private void updateSOCGraph()
{
// Tell ZedGraph to refigure the
// axes since the data have changed
PointPairList socData = handleTags.GetSOCData();
lock (socData)
{
graphSOC.AxisChange();
// don't let graph zoom too close.
tempPane = graphSOC.GraphPane;
//if (tempPane.YAxis.Scale.Max - tempPane.YAxis.Scale.Min < 6)
//{
// tempPane.YAxis.Scale.Max += 3;
// tempPane.YAxis.Scale.Min -= 3;
//}
graphSOC.Refresh();
}
}
private void UpdateSOCGUI()
{
updateSOCGraph();
lblSocADC.Text = "ADC = " + Math.Round(handleTags.GetSocFilteredValue(), 2);
lblSocTemperature.Text = handleTags.GetSocTemperature() + " C";
lblSocLowPassFilter.Text = "Filter: " + Math.Round(100.0 * tbarSocFilter.Value / tbarSocFilter.Maximum, 2) + " percent of new value";
}
#endregion SOC Region
#region Temperature
private void ClearTemperature()
{
handleTags.ClearTemperature();
}
GraphPane tempPane;
private void InitTempGraph()
{
// setup the axis titles, etc.
// get a reference to the GraphPane
GraphPane tempPane = graphTemperature.GraphPane;
// Set the Titles
tempPane.Title.Text = "Temperature Data";
tempPane.XAxis.Title.Text = "Sample Number";
tempPane.YAxis.Title.Text = "Temperature (Celsius)";
// Generate the line
LineItem myCurve = tempPane.AddCurve(null,
handleTags.GetTemperatureData(), Color.Red, SymbolType.Diamond);
// Tell ZedGraph to refigure the
// axes since the data have changed
graphTemperature.AxisChange();
}
// Update temperature-related stuff on the gui.
private void UpdateTemperatureGUI()
{
if (handleTags.GetHasNewTempData())
{
handleTags.ClearHasNewTempData();
lblTemperature.Text = handleTags.GetTemperatureCelsius().ToString("###.0") + " C";
lblTemperatureSource.Text = handleTags.GetTemperatureSource();
handleTags.GetTemperatureData().Add(handleTags.GetTemperatureDataCount(), handleTags.GetTemperatureCelsius());
if (handleTags.GetTemperatureData().Count > 500)
handleTags.GetTemperatureData().RemoveAt(0);
graphTemperature.AxisChange();
tempPane = graphTemperature.GraphPane;
if (tempPane.YAxis.Scale.Min > 10)
tempPane.YAxis.Scale.Min = 10;
if (tempPane.YAxis.Scale.Max < 40)
tempPane.YAxis.Scale.Max = 40;
graphTemperature.Refresh();
}
handleTags.incrementTemperatureDataCount();
}
#endregion
#region Other Sensors And Statistics
private void InitTagStats()
{
stats = new TagStats(dgvTagStats);
}
private void ProcessTagStats()
{
// get the list of new tags
ArrayList newTags = handleTags.GetNewTags();
lock (newTags)
{
stats.AddTags(newTags);
log.WriteToLog(newTags);
newTags.Clear();
}
}
static string currTime = ((DateTime.Now.ToString()).Replace(":", ".")).Replace("/", "-");
#endregion
#region GUI Update
public Accel accelInfo = new Accel();
Stopwatch swGUI = new Stopwatch();
long peakGuiTime = 0;
long peakHandlerTime = 0;
public struct Accel
{
public bool filterChkd;
public double alpha;
}
public Accel getAccelInfo()
{
return accelInfo;
}
private void timerUpdateGUI_Tick(object sender, EventArgs e)
{
timerUpdateGUI.Enabled = false; // we don't want timer to start this thread twice
swGUI.Reset(); // some timers to keep track of how long gui updates are taking.
swGUI.Start();
// update this timer's refresh rate from text box
int newInterval = Convert.ToInt16(Convert.ToDouble(txtSaturnRefreshMs.Text));
if (newInterval > 10000 || newInterval < 1) newInterval = 100;
timerUpdateGUI.Interval = newInterval;
// Tell the Tag Handler what's on the GUI
SetHandleTagSettings();
// Update Text Boxes
txtBoxTags.Text = tagTextBoxContent;
txtDebugMessages.Text = debugTextBoxContent;
// Update Saturn checkbox
if (!saturn.IsSaturnOpen())
{
chkSaturn.Checked = false;
}
// Update MIDI checkbox
if (!midi.IsMidiConfigOpen())
{
chkMIDI.Checked = false;
}
// Update Bink Bonk checkbox
if (!binkBonk.isBinkBonkOpen())
{
chkBinkBonk.Checked = false;
}
// ***** this is the heart of it ****** //
// Update the relevant sensor gui sections:
if (readerMgr.IsInventoryRunning())
{
// Sensor Processing
UpdateAccelerometerGUI();
UpdateTemperatureGUI();
UpdateSOCGUI();
}
// Update tag statistics - this does the grid view
ArrayList newTags = handleTags.GetNewTags();
if (newTags.Count > 0)
ProcessTagStats();
// Check if Read is enabled and reader is connected. If so, enable read on the Reader.
if (!chkReadDisabled.Checked && readerMgr.IsConnected())
{
if (chkReadAll.Checked)
{
readerMgr.RestartRead(new ReaderManager.ReadCmdSettings("0000", "0000000000000000", 1, 0));
}
else if (chkReadSelected.Checked)
{
// Refresh which tag we are performing Read command on
// via the selected tag in the grid view.
string selectedTag = stats.GetSelectedTagID();
string mask = "";
if (selectedTag != null)
{
mask = mask.PadRight(selectedTag.Length * 4, '1');
readerMgr.RestartRead(new ReaderManager.ReadCmdSettings(selectedTag, mask, 1, 0));
}
else
{
readerMgr.StopRead();
}
}
}
else if (readerMgr.IsConnected())
readerMgr.StopRead();
// Update time info
swGUI.Stop();
long picosecPerTick = (1000L * 1000L * 1000L * 1000L) / Stopwatch.Frequency;
long microsecGUIrate = swGUI.ElapsedTicks * picosecPerTick / (1000L * 1000L * 1000L);
//long microsecHandlerrate = swHandler.ElapsedTicks * picosecPerTick / (1000L * 1000L * 1000L);
if (microsecGUIrate > peakGuiTime) peakGuiTime = microsecGUIrate;
//if (microsecHandlerrate > peakHandlerTime) peakHandlerTime = microsecHandlerrate;
lblGUITime.Text = "GUI Time: " + peakGuiTime.ToString() + " ms";
//lblHandlerTime.Text = "Handler Time: " + peakHandlerTime.ToString() + " ms";
//Application.DoEvents(); // removing this cut the gui handler down from 55ms to 20ms peak time
// with Saturn open, and with no effect on gui responsiveness!
timerUpdateGUI.Enabled = true; // re-enable this timer.
}
private void SetHandleTagSettings()
{
// Acceleromter stuff
accelInfo.filterChkd = false;
if (chkFilter.Checked)
{
accelInfo.filterChkd = true;
accelInfo.alpha = tbarLPFilter.Value;
}
// SOC WISP Stuff
if (chkSOCV1V2.Checked)
handleTags.SetSOCVersion(2, accelInfo);
else
handleTags.SetSOCVersion(1, accelInfo);
// filtering
handleTags.SetSOCAlpha(Math.Round(1.0 * tbarSocFilter.Value / tbarSocFilter.Maximum, 2));
// adc to temperature conversion
double test = Double.Parse(txtSocCalTemp2.Text);
try
{
double slope = (Double.Parse(txtSocCalTemp2.Text) - Double.Parse(txtSocCalTemp1.Text)) /
(Double.Parse(txtSocCalAdc2.Text) - Double.Parse(txtSocCalAdc1.Text));
double intercept = Double.Parse(txtSocCalTemp2.Text) - Double.Parse(txtSocCalAdc2.Text) * slope;
handleTags.SetSOCIntercept(intercept);
handleTags.SetSOCSlope(slope);
lblSocCalOk.Text = "Cal ok.";
}
catch
{
lblSocCalOk.Text = "Parse Error.";
}
handleTags.SetSOCPlotTemp(chkSocPlotTemp.Checked);
}
private void ClearHandlerTimes()
{
peakHandlerTime = 0;
peakGuiTime = 0;
}
private void btnClear_Click(object sender, EventArgs e)
{
ClearGUIAll();
}
#endregion
#region Tag Rates and Frame Rates
private void timerFrameRateMeasure_Tick(object sender, EventArgs e)
{
if (chkSaturn.Checked)
{
double rate = (double)saturnFrames / ((double)timerFrameRateMeasure.Interval / 1000.0);
Trace.WriteLine(rate.ToString() + " frames per second");
}
saturnFrames = 0;
}
private double tagReadRate;
private void timerTagRateMeasure_Tick(object sender, EventArgs e)
{
// update this timer's refresh rate from text box
if (txtTagRateWindowSeconds.Text == "") return;
int newInterval = Convert.ToInt32(1000 * Convert.ToDouble(txtTagRateWindowSeconds.Text));
if (newInterval < 100) newInterval = 1000;
if (newInterval != timerTagRateMeasure.Interval)
{
timerTagRateMeasure.Interval = newInterval;
lblTagsPerSecond.Text = "Measuring";
handleTags.clearTagCount();
}
else
{
double timeInMs = (double)timerTagRateMeasure.Interval / 1000.0;
accelInfo.alpha = (double)tbarTagsPerSecFilter.Value / 100;
tagReadRate = accelInfo.alpha * tagReadRate + (1 - accelInfo.alpha) * handleTags.GetTagCount() / timeInMs;
handleTags.clearTagCount();
lblTagsPerSecond.Text = tagReadRate.ToString("#0.0");
}
}
private void txtTagRateWindowSeconds_TextChanged(object sender, EventArgs e)
{
timerTagRateMeasure.Interval = 100; // set it off now when txt changed.
}
#endregion
#region LLRP and Reader Control
private void SetMode(ReaderManager.GuiModes newMode)
{
// Switch performs the action
try
{
readerMgr.SetMode(newMode, txtIPAddress.Text);
}
catch (Exception e)
{
// todo: handle exception
MessageBox.Show(e.ToString());
}
ReaderManager.GuiModes currentMode = readerMgr.getCurrentMode();
//MessageBox.Show(currentMode.ToString());
// Lastly, update button enables:
// Connect button
if (currentMode == ReaderManager.GuiModes.Idle)
{
txtMessages.Text = "Disconnected.";
lblStatus.Text = "";
btnConnect.Text = "Connect";
}
else
{
txtMessages.Text = "Disconnected.";
lblStatus.Text = "Connected to IP Address: " + txtIPAddress.Text;
btnConnect.Text = "Disconnect";
}
btnConnect.Enabled = true;
// User inventory start / stop buttons
btnInv.Enabled = (currentMode != ReaderManager.GuiModes.Idle);
if (currentMode == ReaderManager.GuiModes.Ready)
btnInv.Text = "Inventory";
else
btnInv.Text = "Stop";
// settings button
btnSettings.Enabled = (currentMode == ReaderManager.GuiModes.Ready || currentMode == ReaderManager.GuiModes.UserInventory);
// Set the mode label to our current mode
lblMode.Text = "Mode: " + currentMode.ToString();
}
private void btnInv_Click(object sender, EventArgs e)
{
if (btnInv.Text == "Inventory") //FIXME: Hack alert. Use getCurrentMode instead.
SetMode(ReaderManager.GuiModes.UserInventory); // Start Inventory.
else
SetMode(ReaderManager.GuiModes.Ready); // Stop Inventory
}
private void btnConnect_Click(object sender, EventArgs e)
{
btnConnect.Enabled = false;
if (btnConnect.Text == "Connect") //FIXME: Hack alert. Use getCurrentMode instead.
SetMode(ReaderManager.GuiModes.Ready);
else
SetMode(ReaderManager.GuiModes.Idle);
}
private void MainFrm_FormClosing(object sender, FormClosingEventArgs e)
{
if (readerMgr.IsConnected())
{
readerMgr.Disconnect();
}
log.CloseAllLogs();
}
#endregion
private void btnSettings_Click(object sender, EventArgs e)
{
if (setting == null || setting.IsDisposed)
{
setting = new ReaderLibrary.SettingsForm(readerMgr);
}
setting.Show();
}
private void chkSaturn_CheckedChanged(object sender, EventArgs e)
{
UpdateAccelerometerGUI();
}
private void btnLogging_Click(object sender, EventArgs e)
{
if (loggingForm == null || loggingForm.IsDisposed)
{
loggingForm = new LoggingForm(log);
}
loggingForm.Show();
}
private void chkMIDI_CheckedChanged(object sender, EventArgs e)
{
if (chkMIDI.Checked)
{
this.midi.ReOpenMidiConfig();
UpdateAccelerometerGUI();
}
else
{
this.midi.DisposeMidiConfig();
}
}
private void MainFrm_ResizeEnd(object sender, EventArgs e)
{
// Update height of tag panel when resize occurs
dgvTagStats.Height = pnlTagListInitialHeight + (this.Height - formInitialHeight);
}
private void chkBinkBonk_CheckedChanged(object sender, EventArgs e)
{
if (chkBinkBonk.Checked)
{
this.binkBonk.reOpenBinkBonk();
}
else
{
this.binkBonk.disposeBinkBonk();
}
}
} // end class
} // end namespace