//----------------------------------------------------------------------- // // 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; } } } }