//-----------------------------------------------------------------------
//
// Copyright © 2012 Nils Hammar. All rights reserved.
//
//-----------------------------------------------------------------------
/*
* Software to access vehicle information via the OBD-II connector.
*
* Copyright © 2012 Nils Hammar
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Alternative licensing is possible, see the licensing document.
*
* The above text may not be removed or modified.
*/
namespace Gauges
{
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using global::Gauges.TelltalePanel;
using global::SharedObjects;
using global::SharedObjects.DataMgmt;
using global::SharedObjects.Misc;
using global::SharedObjects.Misc.Objects;
using global::SharedObjects.Protocol;
using global::SharedObjects.Protocol.OBD;
using global::SharedObjects.Protocol.OBD.SSM;
///
/// Class for setting up which gauges to display.
///
public partial class GaugeManager : Form, IGaugePresentation
{
///
/// Logger instance.
///
private ILogging iLogging;
///
/// Current Messaging instance.
///
private IMessaging iMessaging;
///
/// Current Mode parser.
///
private IModeParser modeParser;
///
/// Current SSM parser.
///
private ISsmParser ssmParser;
///
/// Current Gauge Form.
///
private GaugeForm1 gaugeForm1 = null;
///
/// Current sensor map.
///
private List sensorMap;
///
/// Indicates if data needs to be saved.
///
private bool dirty = false;
///
/// List of requests to issue to get data for gauges.
///
private List requests;
///
/// Current data poll thread.
///
private Thread pollThread = null;
///
/// Current gauge update thread.
///
private Thread refreshThread = null;
///
/// Array of current gauge values.
///
private float[] values;
///
/// Array of current gauge values.
///
private float[] digitalValues;
///
/// Telltale toggle states.
///
private IDictionary telltaleValues = new Dictionary();
///
/// Time to sleep between each update.
///
private int sleepTime;
///
/// Flag indicating that values has been updated.
///
/// This is used to avoid updating the gauges if no change has occurred since it is a performance-hungry operation.
///
///
private bool updatedValues = true;
///
/// Flag indicating that values has been updated.
///
/// This is used to avoid updating the gauges if no change has occurred since it is a performance-hungry operation.
///
///
private bool updatedTelltale = true;
///
/// Poll loop active.
///
private bool doPoll = false;
///
/// Refresh loop active.
///
private bool doRefresh = false;
///
/// Lock object to avoid colliding update/read data between threads.
///
private object valueUpdateLockObject = new object();
///
/// Data source interface instance.
///
private IDataSource iDataSource;
///
/// Data file directory path.
///
private string dataFileDir;
///
/// Palette instances container.
///
private PaletteCreator paletteCreator = new PaletteCreator();
///
/// Initializes a new instance of the class.
///
/// Logger instance.
/// Data source interface instance.
/// Current Messaging instance.
/// Current Mode parser.
/// Current SSM parser.
/// Current data file directory.
public GaugeManager(
ILogging iLogging,
IDataSource iDataSource,
IMessaging iMessaging,
IModeParser modeParser,
ISsmParser ssmParser,
string dataFileDir)
{
this.iLogging = iLogging;
this.iDataSource = iDataSource;
this.iMessaging = iMessaging;
this.modeParser = modeParser;
this.ssmParser = ssmParser;
this.dataFileDir = dataFileDir;
this.InitializeComponent();
this.refreshData();
}
///
/// Stop polling for data.
///
public void stopPoll()
{
this.modeParser.pidParser.setGaugePresentation(null);
this.ssmParser.setGaugePresentation(null);
this.doPoll = false;
this.doRefresh = false;
if (this.refreshThread != null)
{
this.refreshThread.Abort();
this.refreshThread.Interrupt();
this.refreshThread.Join(4000);
this.refreshThread = null;
}
if (this.pollThread != null)
{
this.pollThread.Abort();
this.pollThread.Interrupt();
this.pollThread.Join(4000);
this.pollThread = null;
}
this.telltaleValues.Clear();
}
///
/// Set gauge value.
///
/// Mode for value
/// PID for value
/// Sensor for value
/// Value to set
/// 'true' if successfully presented the data.
public bool setGaugeValue(byte mode, uint pid, XmlClass.pidgroup.pidlist.sensordata sensor, float value)
{
bool success = false;
if (sensor != null)
{
#if TRACE
this.iLogging.appendText("");
#endif
this.handleDataSet(1, mode, pid, sensor, value);
this.handleDataSet(2, mode, pid, sensor, value);
this.handleDataSet(3, mode, pid, sensor, value);
success = true;
}
else
{
this.iLogging.appendText("'null' Sensor.\r\n");
}
return success;
}
///
/// Find the sensor for the gauge.
///
/// Sensor unit string.
/// Sensor byte offset.
/// PID with sensor list to look in.
/// Sensor bit number string.
/// Sensor description data.
private static XmlClass.pidgroup.pidlist.sensordata findSensorItem(string sensor_unit, uint sensor_offset, XmlClass.pidgroup.pidlist pid, string sensor_bit)
{
XmlClass.pidgroup.pidlist.sensordata sensor = null;
foreach (XmlClass.pidgroup.pidlist.sensordata sensorItem in pid.sensors)
{
if (string.IsNullOrEmpty(sensor_bit)
&& sensorItem.unit.Equals(sensor_unit)
&& sensorItem.offset == sensor_offset)
{
sensor = sensorItem;
break;
}
if (!string.IsNullOrEmpty(sensor_bit)
&& !string.IsNullOrEmpty(sensorItem.startbit)
&& sensorItem.unit.Equals(sensor_unit)
&& sensorItem.offset == sensor_offset
&& sensor_bit.Trim() == sensorItem.startbit.Trim())
{
sensor = sensorItem;
break;
}
}
return sensor;
}
///
/// Find the PID item given the gauge item.
///
/// PID id
/// Pid group with pid items.
/// Matching PID item or 'null' if not found.
private static XmlClass.pidgroup.pidlist findPidItem(uint pid_int, XmlClass.pidgroup pidgroup)
{
XmlClass.pidgroup.pidlist pid = null;
foreach (XmlClass.pidgroup.pidlist pidItem in pidgroup.pids)
{
if (pidItem.pid_int == pid_int)
{
pid = pidItem;
break;
}
}
return pid;
}
///
/// Get PID and sensor from the given data.
///
/// Current PID group.
/// Current PID id.
/// Current sensor unit string.
/// Current sensor offset.
/// Current sensor bit.
/// PID instance. (out)
/// Sensor instance. (out)
private static void getSensor(
XmlClass.pidgroup pidgroup,
uint pid_int,
string sensor_unit,
uint sensor_offset,
string sensor_bit,
out XmlClass.pidgroup.pidlist pid,
out XmlClass.pidgroup.pidlist.sensordata sensor)
{
pid = null;
sensor = null;
if (pidgroup != null)
{
pid = findPidItem(pid_int, pidgroup);
if (pid != null)
{
sensor = findSensorItem(sensor_unit, sensor_offset, pid, sensor_bit);
}
}
}
///
/// Handle presentation of one data set.
///
/// Data set to handle.
/// Data mode value.
/// Data PID value.
/// Sensor instance.
/// Actual value to present.
private void handleDataSet(uint dataSet, byte mode, uint pid, XmlClass.pidgroup.pidlist.sensordata sensor, float value)
{
int gauge = -1;
XmlClass.telltaleitem telltaleItemValue = null;
Image icon = null;
for (int i = 0; i < this.sensorMap.Count; i++)
{
SensorMapItem s1 = this.sensorMap[i];
if (s1.mode == (mode & 0xBF)
&& s1.pid == pid
&& s1.sensor.unit == sensor.unit
&& s1.sensor.offset == sensor.offset
&& s1.dataSet == dataSet)
{
gauge = (int)s1.gauge;
telltaleItemValue = s1.telltaleItemValue;
icon = s1.icon;
break;
}
}
if (gauge >= 0)
{
#if TRACE
this.iLogging.appendText("dataSet=" + dataSet + ", gauge=" + gauge + ", mode=0x" + mode.ToString("x2") + ", pid=0x" + pid.ToString() + ", sensor=" + sensor.unit + "\r\n");
#endif
lock (this.valueUpdateLockObject)
{
switch (dataSet)
{
case 2:
this.digitalValues[gauge] = value;
break;
case 3:
this.setTelltale(value, telltaleItemValue);
break;
default:
this.values[gauge] = value;
break;
}
this.updatedValues = true;
}
}
}
///
/// Set the telltales.
///
/// Value for telltale.
/// Which telltale.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Reviewed, intentional.")]
private void setTelltale(float value, XmlClass.telltaleitem telltaleItemValue)
{
if (telltaleItemValue != null)
{
bool active = (value > 0.5);
if (!this.telltaleValues.ContainsKey(telltaleItemValue))
{
#if TRACE
this.iLogging.appendText("New Telltale: " + telltaleItemValue.telltaleicon + "\r\n");
#endif
Image img = Image.FromFile(this.dataFileDir + "Icons\\" + telltaleItemValue.telltaleicon);
Color primaryColor = Color.FromName(telltaleItemValue.primaryColor);
Color secondaryColor = Color.FromName(telltaleItemValue.secondaryColor);
Color flashColor = Color.FromName(telltaleItemValue.flashColor);
TellTale tellTaleOn = new TellTale(img, 40, primaryColor, secondaryColor, flashColor, this.paletteCreator.paletteFlashOn, BorderStyle.None);
TellTale tellTaleOff = new TellTale(img, 40, Color.Black, Color.Black, Color.Black, this.paletteCreator.paletteFlashOn, BorderStyle.None);
TelltaleWrapper telltaleWrapper = new TelltaleWrapper(tellTaleOn, tellTaleOff, active);
this.updatedTelltale = true;
this.telltaleValues.Add(telltaleItemValue, telltaleWrapper);
}
else
{
if (this.telltaleValues[telltaleItemValue].active != active)
{
this.telltaleValues[telltaleItemValue].active = active;
this.telltaleValues[telltaleItemValue].changed = true;
this.updatedTelltale = true;
}
}
}
}
///
/// Refresh the data form.
///
private void refreshData()
{
DataGridViewRowCollection rowCollection = this.gaugesDataGridView.Rows;
rowCollection.Clear();
foreach (XmlClass.gaugeitem item in this.iDataSource.gauges)
{
string pidName = this.getPidName(item.pidgroupid, item.pid_int);
string digitalPidName = this.getPidName(item.digitalPidgroupid, item.digitalPid_int);
int n0 = rowCollection.Add(1);
DataGridViewRow row = rowCollection[n0];
DataGridViewCellCollection cellCollection = row.Cells;
cellCollection["gaugeItem"].Value = item;
cellCollection["gaugeNumber"].Value = item.gaugeNumber;
cellCollection["displayedItem"].Value = pidName;
cellCollection["gaugeText"].Value = item.gaugetext;
cellCollection["digitalItem"].Value = digitalPidName;
cellCollection["digitalText"].Value = item.digitalGaugetext;
cellCollection["digitalFormatMask"].Value = item.digitalFormat;
}
DataGridViewRowCollection rowCollection1 = this.telltalesDataGridView.Rows;
rowCollection1.Clear();
foreach (XmlClass.telltaleitem item in this.iDataSource.telltales)
{
string pidName = this.getPidName(item.pidgroupid, item.pid_int);
string sensorName = this.getSensorName(item);
int n0 = rowCollection1.Add(1);
DataGridViewRow row = rowCollection1[n0];
DataGridViewCellCollection cellCollection = row.Cells;
cellCollection["telltaleItem"].Value = item;
cellCollection["telltaleFieldNumber"].Value = item.telltaleGroupNumber;
cellCollection["telltalePrio"].Value = item.telltalePriority;
cellCollection["telltaleItemName"].Value = pidName;
cellCollection["telltaleText"].Value = item.telltaletext;
cellCollection["telltalePrimaryColor"].Style.BackColor = Color.FromName(item.primaryColor);
cellCollection["telltaleSecondaryColor"].Style.BackColor = Color.FromName(item.secondaryColor);
cellCollection["telltaleFlashColor"].Style.BackColor = Color.FromName(item.flashColor);
cellCollection["telltaleSensor"].Value = sensorName;
cellCollection["telltaleSensorBit"].Value = item.sensor_bit;
this.setImage(cellCollection, item.telltaleicon);
}
}
///
/// Fill in the icon column.
///
/// Current column collection.
/// Name of icon file.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Reviewed, intentional.")]
private void setImage(DataGridViewCellCollection dgvcc, string filename)
{
float hLimit = 30;
float wLimit = 50;
Image img = Image.FromFile(this.dataFileDir + "Icons\\" + filename);
float proportion = (float)img.Height / (float)img.Width;
float w = hLimit / proportion;
float h = wLimit * proportion;
if (h > hLimit)
{
h = hLimit;
}
else
{
w = wLimit;
}
int w0 = (int)w;
int h0 = (int)h;
Bitmap bmp = new Bitmap(w0, h0);
using (Graphics g = Graphics.FromImage(bmp))
{
g.DrawImage(img, new Rectangle(5, 5, bmp.Width - 5, bmp.Height - 5));
}
dgvcc["telltaleIcon"].Value = bmp;
}
///
/// Get PID name.
///
/// PID group to look in.
/// PID to get name for.
/// PID name.
private string getPidName(int pidgroupid, uint pid_int)
{
string name = null;
foreach (XmlClass.pidgroup pidGrp in this.iDataSource.pidgroups)
{
if (pidGrp.id.Equals(pidgroupid))
{
foreach (XmlClass.pidgroup.pidlist pid in pidGrp.pids)
{
// if (pid.pid.Equals(item.pid))
if (pid.pid_int == pid_int)
{
name = pid.name;
break;
}
}
}
if (name != null)
{
break;
}
}
if (name == null)
{
name = string.Empty;
}
return name;
}
///
/// Get sensor name.
///
/// Telltale item instance.
/// Sensor name
private string getSensorName(XmlClass.telltaleitem item)
{
string name = null;
foreach (XmlClass.pidgroup pidGrp in this.iDataSource.pidgroups)
{
if (pidGrp.id == item.pidgroupid)
{
foreach (XmlClass.pidgroup.pidlist pid in pidGrp.pids)
{
if (pid.pid_int == item.pid_int)
{
foreach (XmlClass.pidgroup.pidlist.sensordata sensor in pid.sensors)
{
int startbit = -1;
try
{
if (sensor.unit == "bit")
{
startbit = Convert.ToInt32(sensor.startbit);
}
}
catch
{
}
if (sensor.offset == item.sensor_offset
&& (item.sensor_bit == -1 || item.sensor_bit == startbit))
{
name = sensor.name;
break;
}
}
}
if (name != null)
{
break;
}
}
}
if (name != null)
{
break;
}
}
if (name == null)
{
name = string.Empty;
}
return name;
}
///
/// Handle double-click on cell.
///
/// Sending object.
/// Event data.
private void gaugesDataGridView_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
DataGridView dataGridView = (DataGridView)sender;
int ix = e.RowIndex;
DataGridViewRowCollection rows = dataGridView.Rows;
List usedGaugeList = new List();
IList allGauges = GaugeSelector.allGauges();
foreach (DataGridViewRow row in rows)
{
XmlClass.gaugeitem item = (XmlClass.gaugeitem)row.Cells["gaugeItem"].Value;
if (item != null)
{
usedGaugeList.Add(item.gaugeNumber);
allGauges.Remove(item.gaugeNumber);
}
}
uint[] usedGauges = usedGaugeList.ToArray();
try
{
if (ix >= 0 && ix < rows.Count)
{
DataGridViewRow row = rows[ix];
DataGridViewCellCollection cellCollection = row.Cells;
XmlClass.gaugeitem oldGaugeitem = (XmlClass.gaugeitem)cellCollection["gaugeItem"].Value;
bool newRow = row.IsNewRow;
uint gaugeNumber1;
if (newRow || oldGaugeitem == null)
{
if (allGauges.Count == 0)
{
throw new Exception("No available gauges.");
}
gaugeNumber1 = allGauges[0];
int n0 = rows.Add(1);
row = rows[n0];
cellCollection = row.Cells;
dataGridView.CurrentCell = cellCollection["displayedItem"];
}
else
{
gaugeNumber1 = oldGaugeitem.gaugeNumber;
}
if (oldGaugeitem != null || (newRow && allGauges.Count > 0))
{
using (GaugeDetails gaugeDetails = new GaugeDetails(this.iDataSource.restrictedPidgroups, oldGaugeitem, gaugeNumber1, usedGauges))
{
if (gaugeDetails.ShowDialog() == DialogResult.OK)
{
XmlClass.gaugeitem newGaugeitem = gaugeDetails.gaugeitem;
// newGaugeitem.gaugeNumber = (uint)ix;
cellCollection["gaugeItem"].Value = newGaugeitem;
cellCollection["gaugeNumber"].Value = newGaugeitem.gaugeNumber;
cellCollection["displayedItem"].Value = gaugeDetails.pidName;
cellCollection["gaugeText"].Value = newGaugeitem.gaugetext;
cellCollection["digitalItem"].Value = gaugeDetails.digitalPidName;
cellCollection["digitalText"].Value = newGaugeitem.digitalGaugetext;
cellCollection["digitalFormatMask"].Value = newGaugeitem.digitalFormat;
this.iDataSource.gauges.Remove(oldGaugeitem);
this.iDataSource.gauges.Add(newGaugeitem);
this.dirty = true;
}
else
{
if (newRow)
{
rows.Remove(row);
}
}
}
}
else
{
if (newRow)
{
rows.Remove(row);
}
}
}
}
catch (Exception ex)
{
MessageBox.Show(
"Unable to create/update Gauge.\r\n" + ex.Message,
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Hand);
}
}
///
/// Close button clicked.
///
/// Sending object.
/// Event data.
private void closeButton_Click(object sender, EventArgs e)
{
this.Close();
}
///
/// Save button clicked.
///
/// Sending object.
/// Event data.
private void saveButton_Click(object sender, EventArgs e)
{
this.iDataSource.saveGauges();
this.iDataSource.saveTelltales();
this.dirty = false;
}
///
/// Show gauges button clicked.
///
/// Sending object.
/// Event data.
private void showGaugesButton_Click(object sender, EventArgs e)
{
this.sensorMap = new List();
DataGridViewRowCollection gaugeRows = this.gaugesDataGridView.Rows;
DataGridViewRowCollection telltaleRows = this.telltalesDataGridView.Rows;
if (this.gaugeForm1 == null || !this.gaugeForm1.CanFocus)
{
this.gaugeForm1 = new GaugeForm1(this, this.paletteCreator);
}
this.requests = new List();
this.values = new float[8];
this.digitalValues = new float[8];
for (int i = 0; i < this.values.Length; i++)
{
this.values[i] = 0;
this.digitalValues[i] = 0;
}
foreach (DataGridViewRow row in telltaleRows)
{
DataGridViewCellCollection cellCollection = row.Cells;
if (cellCollection["telltaleItem"].Value != null)
{
XmlClass.telltaleitem telltaleItemValue = (XmlClass.telltaleitem)cellCollection["telltaleItem"].Value;
Image icon = (Image)cellCollection["telltaleIcon"].Value;
XmlClass.pidgroup pidgroup = this.getPidGroup(telltaleItemValue.pidgroupid);
XmlClass.pidgroup.pidlist pid;
XmlClass.pidgroup.pidlist.sensordata sensor;
getSensor(
pidgroup,
telltaleItemValue.pid_int,
telltaleItemValue.sensor_unit,
telltaleItemValue.sensor_offset,
telltaleItemValue.sensor_bit.ToString(),
out pid,
out sensor);
if (pid != null && sensor != null)
{
this.addSensorMapItem(3, telltaleItemValue.telltaleGroupNumber, pidgroup, pid, sensor, telltaleItemValue, icon);
this.buildRequest(null, pid, pidgroup.mode_int);
}
}
}
foreach (DataGridViewRow row in gaugeRows)
{
DataGridViewCellCollection cellCollection = row.Cells;
if (cellCollection["gaugeItem"].Value != null)
{
XmlClass.gaugeitem item = (XmlClass.gaugeitem)cellCollection["gaugeItem"].Value;
XmlClass.pidgroup pidgroup = this.getPidGroup(item.pidgroupid);
XmlClass.pidgroup digitalPidgroup = this.getPidGroup(item.digitalPidgroupid);
XmlClass.pidgroup.pidlist pid;
XmlClass.pidgroup.pidlist.sensordata sensor;
getSensor(
pidgroup,
item.pid_int,
item.sensor_unit,
item.sensor_offset,
null,
out pid,
out sensor);
if (pid != null && sensor != null)
{
this.gaugeForm1.configureGauge(item, sensor);
this.addSensorMapItem(1, item.gaugeNumber, pidgroup, pid, sensor, null, null);
this.buildRequest(null, pid, pidgroup.mode_int);
XmlClass.pidgroup.pidlist pid2;
XmlClass.pidgroup.pidlist.sensordata sensor2;
getSensor(
digitalPidgroup,
item.digitalPid_int,
item.digitalSensor_unit,
item.digitalSensor_offset,
null,
out pid2,
out sensor2);
if (pid2 != null && sensor2 != null)
{
this.addSensorMapItem(2, item.gaugeNumber, digitalPidgroup, pid2, sensor2, null, null);
this.buildRequest(null, pid2, digitalPidgroup.mode_int);
}
}
}
}
this.gaugeForm1.Show();
this.sleepTime = (int)this.pollUpDown.Value;
this.doPoll = true;
this.doRefresh = true;
this.modeParser.pidParser.setGaugePresentation(this);
this.ssmParser.setGaugePresentation(this);
this.refreshThread = new Thread(new ThreadStart(this.refreshThreadImplementation));
this.refreshThread.Name = "Gauge Draw";
this.refreshThread.Start();
// To get the threads out of sync to begin with.
Thread.Sleep(this.sleepTime / 2);
this.pollThread = new Thread(new ThreadStart(this.pollThreadImplementation));
this.pollThread.Name = "Gauge Poll";
this.pollThread.Start();
}
///
/// Add one sensor map item.
///
/// Data set number, 1=Analogue Gauge, 2=Digital Gauge, 3=Telltale
/// Gauge number.
/// PID group instance.
/// Current PID instance.
/// Current sensor instance.
/// Telltale item data.
/// Telltale icon.
private void addSensorMapItem(
uint dataSet,
uint gaugeNumber1,
XmlClass.pidgroup pidgroup,
XmlClass.pidgroup.pidlist pid,
XmlClass.pidgroup.pidlist.sensordata sensor,
XmlClass.telltaleitem telltaleItemValue,
Image icon)
{
SensorMapItem smi = new SensorMapItem(dataSet, gaugeNumber1, (byte)pidgroup.mode_int, pid.pid_int, sensor, telltaleItemValue, icon);
if (!this.sensorMap.Contains(smi))
{
this.sensorMap.Add(smi);
}
}
///
/// Get the PID group using the ID.
///
/// Current PID group ID.
/// PID group instance.
private XmlClass.pidgroup getPidGroup(int pidgroupid)
{
XmlClass.pidgroup pidgroup = null;
foreach (XmlClass.pidgroup pg in this.iDataSource.pidgroups)
{
if (pg.id.Equals(pidgroupid))
{
pidgroup = pg;
break;
}
}
return pidgroup;
}
///
/// Build a data request and add it to the list.
///
/// Destination address to send to.
/// PID item.
/// Mode byte value.
private void buildRequest(uint? destinationAddress, XmlClass.pidgroup.pidlist pid, uint modeInt)
{
IRequestData rd = null;
switch (modeInt)
{
case 0x01:
rd = new RequestData(destinationAddress, (byte)modeInt, pid, new TxMsg(new byte[] { (byte)modeInt, (byte)(pid.pid_int & 0xff) }));
break;
case 0x22:
rd = new RequestData(destinationAddress, (byte)modeInt, pid, new TxMsg(new byte[] { (byte)modeInt, (byte)((pid.pid_int >> 8) & 0xff), (byte)(pid.pid_int & 0xff) }));
break;
case 0xA0:
rd = new RequestData(destinationAddress, (byte)modeInt, pid, new TxMsg(new byte[] { (byte)modeInt, 0x00, (byte)((pid.pid_int >> 16) & 0xff), (byte)((pid.pid_int >> 8) & 0xff), (byte)(pid.pid_int & 0xff), (byte)(pid.pid_bytes) }));
break;
case 0xA8:
rd = new RequestData(destinationAddress, (byte)modeInt, pid, new TxMsg(new byte[] { (byte)modeInt, 0x00, (byte)((pid.pid_int >> 16) & 0xff), (byte)((pid.pid_int >> 8) & 0xff), (byte)(pid.pid_int & 0xff) }));
break;
}
if (rd != null && !this.requests.Contains(rd))
{
this.requests.Add(rd);
}
}
///
/// Form is closing.
///
/// Sending object.
/// Event data.
private void GaugeManager_FormClosing(object sender, FormClosingEventArgs e)
{
this.stopPoll();
if (this.dirty)
{
DialogResult res = MessageBox.Show(
"Do you want to save updated data?",
"Confirm",
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1);
switch (res)
{
case DialogResult.Yes:
this.iDataSource.saveGauges();
this.dirty = false;
break;
case DialogResult.No:
// Do nothing.
break;
case DialogResult.Cancel:
e.Cancel = true;
break;
}
}
if (this.gaugeForm1 != null)
{
this.gaugeForm1.Close();
}
}
///
/// Row deleted by user.
///
/// Sending object.
/// Event data.
private void dataGridView1_UserDeletingRow(object sender, DataGridViewRowCancelEventArgs e)
{
DataGridViewRow row = e.Row;
DataGridViewCellCollection rowcells = row.Cells;
XmlClass.gaugeitem item = (XmlClass.gaugeitem)rowcells["gaugeItem"].Value;
if (item != null)
{
string name = rowcells["gaugeText"].Value.ToString();
if (MessageBox.Show(
"Do you want to delete '" + name + "'?",
"Confirm",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning,
MessageBoxDefaultButton.Button2) == DialogResult.No)
{
e.Cancel = true;
}
else
{
this.dirty = true;
this.iDataSource.gauges.Remove(item);
}
}
}
///
/// Refresh gauges thread.
///
private void refreshThreadImplementation()
{
this.refreshThread.Priority = ThreadPriority.BelowNormal;
List refreshPanels = new List();
while (this.doRefresh && this.refreshThread != null)
{
bool needRefresh = false;
float[] values1 = null;
float[] digitalValues1 = null;
KeyValuePair[] telltales = null;
lock (this.valueUpdateLockObject)
{
if (this.updatedValues)
{
this.updatedValues = false;
if (this.values != null)
{
values1 = new float[this.values.Length];
Array.Copy(this.values, values1, this.values.Length);
}
if (this.digitalValues != null)
{
digitalValues1 = new float[this.digitalValues.Length];
Array.Copy(this.digitalValues, digitalValues1, this.digitalValues.Length);
}
}
if (this.updatedTelltale)
{
this.updatedTelltale = false;
telltales = new KeyValuePair[this.telltaleValues.Count];
this.telltaleValues.CopyTo(telltales, 0);
}
}
if (telltales != null)
{
foreach (KeyValuePair kvp in telltales)
{
TelltaleWrapper wrapper = kvp.Value;
#if TRACE
this.iLogging.appendText("Telltale=" + kvp.Key.telltaletext + ", changed=" + wrapper.changed + ", active=" + wrapper.active + "\r\n");
#endif
if (wrapper.changed)
{
wrapper.changed = false;
needRefresh = true;
XmlClass.telltaleitem item = kvp.Key;
int iconArea = (int)item.telltaleGroupNumber;
if (!refreshPanels.Contains(iconArea))
{
refreshPanels.Add(iconArea);
}
if (wrapper.active)
{
this.gaugeForm1.removeIcon(iconArea, wrapper.tellTaleOff);
this.gaugeForm1.addIcon(iconArea, wrapper.tellTaleOn);
}
else
{
this.gaugeForm1.removeIcon(iconArea, wrapper.tellTaleOn);
this.gaugeForm1.addIcon(iconArea, wrapper.tellTaleOff);
}
}
}
}
if (values1 != null && digitalValues1 != null)
{
#if TRACE
this.iLogging.appendText("Refresh Gauges.\r\n");
#endif
this.gaugeForm1.setValues(values1, digitalValues1);
}
// To redraw the panels with icons.
if (needRefresh)
{
foreach (int iconArea in refreshPanels)
{
this.gaugeForm1.refreshIconPanels(iconArea);
}
refreshPanels.Clear();
}
Thread.Sleep(this.sleepTime);
}
}
///
/// Poll thread.
///
private void pollThreadImplementation()
{
#if TRACE
this.iLogging.appendText("Poll thread start. this.requests=" + this.requests.Count + "\r\n");
#endif
this.sleepTime = (int)this.pollUpDown.Value;
uint n = 0;
while (this.doPoll && this.pollThread != null)
{
foreach (IRequestData msg in this.requests)
{
// Limit the frequency for slow changing data like ambient temperature.
if ((n % msg.divisor) == 0)
{
bool flag = this.iMessaging.queueMsg(msg);
#if TRACE
logPayloadData(msg, flag);
#endif
}
}
this.iMessaging.sendMsg(true);
Thread.Sleep(this.sleepTime);
n++;
}
this.iLogging.appendText("Poll thread end.\r\n");
}
///
/// Log sent payload data.
///
/// Message with payload data.
/// Flag to display with message, indicating if message was queued or not.
private void logPayloadData(IRequestData msg, bool flag)
{
string payloadStr = string.Empty;
foreach (byte data in msg.message.payload)
{
if (!string.IsNullOrEmpty(payloadStr))
{
payloadStr += " ";
}
payloadStr += data.ToString("x2");
}
this.iLogging.appendText("Queued msg='" + flag + "': " + payloadStr + "\r\n");
}
///
/// Poll frequency changed.
///
/// Sending object.
/// Event data.
private void pollUpDown_ValueChanged(object sender, EventArgs e)
{
NumericUpDown ud = (NumericUpDown)sender;
this.sleepTime = (int)ud.Value;
}
///
/// Handle double-click on cell.
///
/// Sending object.
/// Event data.
private void telltalesDataGridView_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
DataGridView dataGridView = (DataGridView)sender;
int ix = e.RowIndex;
DataGridViewRowCollection rows = dataGridView.Rows;
if (ix >= 0 && ix < rows.Count)
{
DataGridViewRow row = rows[ix];
DataGridViewCellCollection cellCollection = row.Cells;
XmlClass.telltaleitem oldTelltaleItem = (XmlClass.telltaleitem)cellCollection["telltaleItem"].Value;
bool newRow = row.IsNewRow;
if (newRow)
{
int n0 = rows.Add(1);
row = rows[n0];
cellCollection = row.Cells;
dataGridView.CurrentCell = cellCollection["telltaleItemName"];
}
if (oldTelltaleItem != null || newRow)
{
uint[] used = new uint[0];
using (TelltaleDetails telltaleDetails = new TelltaleDetails(this.dataFileDir, this.iDataSource, oldTelltaleItem, used, this.paletteCreator))
{
if (telltaleDetails.ShowDialog() == DialogResult.OK)
{
XmlClass.telltaleitem newTelltaleItem = telltaleDetails.telltaleitem;
cellCollection["telltaleItem"].Value = newTelltaleItem;
cellCollection["telltaleFieldNumber"].Value = newTelltaleItem.telltaleGroupNumber;
cellCollection["telltalePrio"].Value = newTelltaleItem.telltalePriority;
cellCollection["telltaleItemName"].Value = telltaleDetails.pidName;
cellCollection["telltaleText"].Value = newTelltaleItem.telltaletext;
cellCollection["telltalePrimaryColor"].Style.BackColor = Color.FromName(newTelltaleItem.primaryColor);
cellCollection["telltaleSecondaryColor"].Style.BackColor = Color.FromName(newTelltaleItem.secondaryColor);
cellCollection["telltaleFlashColor"].Style.BackColor = Color.FromName(newTelltaleItem.flashColor);
cellCollection["telltaleSensor"].Value = telltaleDetails.sensorName;
cellCollection["telltaleSensorBit"].Value = telltaleDetails.sensorBit >= 0 ? telltaleDetails.sensorBit.ToString() : string.Empty;
this.setImage(cellCollection, newTelltaleItem.telltaleicon);
if (oldTelltaleItem != null && this.iDataSource.telltales.Contains(oldTelltaleItem))
{
this.iDataSource.telltales.Remove(oldTelltaleItem);
}
this.iDataSource.telltales.Add(newTelltaleItem);
this.dirty = true;
}
else
{
if (newRow)
{
try
{
rows.Remove(row);
}
catch
{
// Ignore.
}
}
}
}
}
}
}
///
/// Handle deletion of row.
///
/// Sending object.
/// Event data.
private void telltalesDataGridView_UserDeletingRow(object sender, DataGridViewRowCancelEventArgs e)
{
DataGridViewRow row = e.Row;
DataGridViewCellCollection rowcells = row.Cells;
XmlClass.telltaleitem item = (XmlClass.telltaleitem)rowcells["telltaleItem"].Value;
if (item != null)
{
string name = rowcells["telltaleText"].Value.ToString();
if (MessageBox.Show(
"Do you want to delete '" + name + "'?",
"Confirm",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning,
MessageBoxDefaultButton.Button2) == DialogResult.No)
{
e.Cancel = true;
}
else
{
this.dirty = true;
this.iDataSource.telltales.Remove(item);
}
}
}
///
/// One sensor map item.
///
private class TelltaleWrapper
{
///
/// Gets Telltale that is 'on'.
///
public TellTale tellTaleOn { get; private set; }
///
/// Gets Telltale that is 'off'.
///
public TellTale tellTaleOff { get; private set; }
///
/// Gets or sets a value indicating whether Current telltale status is active or not, 'true' for 'on'.
///
public bool active { get; set; }
///
/// Gets or sets a value indicating whether state changed for telltale, 'true' if telltale has changed state and needs to be redrawn.
///
public bool changed { get; set; }
///
/// Initializes a new instance of the class.
///
/// Telltale that is 'on'.
/// Telltale that is 'off'.
/// Current telltale status, 'true' for 'on'.
public TelltaleWrapper(TellTale tellTaleOn, TellTale tellTaleOff, bool active)
{
this.tellTaleOn = tellTaleOn;
this.tellTaleOff = tellTaleOff;
this.active = active;
this.changed = true;
}
}
///
/// One sensor map item.
///
private class SensorMapItem
{
///
/// Gets Which data set part.
///
public uint dataSet { get; private set; }
///
/// Gets Mode for item.
///
public byte mode { get; private set; }
///
/// Gets PID for item.
///
public uint pid { get; private set; }
///
/// Gets Sensor for item.
///
public XmlClass.pidgroup.pidlist.sensordata sensor { get; private set; }
///
/// Gets Gauge number.
///
public uint gauge { get; private set; }
///
/// Gets Telltale item value.
///
public XmlClass.telltaleitem telltaleItemValue { get; private set; }
///
/// Gets Telltale icon.
///
public Image icon { get; private set; }
///
/// Initializes a new instance of the class.
///
/// Data set number. (selects between analog, digital display or telltale)
/// Gauge number.
/// Mode for item.
/// PID for item.
/// Sensor for item.
/// Telltale item description instance. (only for telltales.)
/// Assigned telltale icon. (only for telltales.)
public SensorMapItem(
uint dataSet,
uint gauge,
byte mode,
uint pid,
XmlClass.pidgroup.pidlist.sensordata sensor,
XmlClass.telltaleitem telltaleItemValue,
Image icon)
{
this.dataSet = dataSet;
this.mode = mode;
this.pid = pid;
this.sensor = sensor;
this.gauge = gauge;
this.telltaleItemValue = telltaleItemValue;
this.icon = icon;
}
///
/// Compute hashcode for item.
///
/// Calculated hashcode.
public override int GetHashCode()
{
int hc = (int)this.dataSet;
hc = hc << 4 ^ hc >> 28;
hc = hc ^ this.mode;
hc = hc << 7 ^ hc >> 25;
hc = hc ^ this.sensor.unit.GetHashCode();
hc = hc << 7 ^ hc >> 25;
hc = hc ^ (int)(this.sensor.offset & 0xffff);
hc = hc << 7 ^ hc >> 25;
hc = hc ^ (int)this.pid;
if (!string.IsNullOrEmpty(this.sensor.startbit))
{
hc = hc << 7 ^ hc >> 25;
hc = hc ^ (int)this.sensor.startbit.GetHashCode();
}
return hc;
}
///
/// Compare with another object.
///
/// Object to compare with.
/// 'true' if match.
public override bool Equals(object obj)
{
SensorMapItem sm1 = (SensorMapItem)obj;
return sm1.dataSet == this.dataSet
&& sm1.mode == this.mode
&& sm1.pid == this.pid
&& sm1.sensor.unit == this.sensor.unit
&& sm1.sensor.offset == this.sensor.offset;
}
}
}
}