/// /// Copyright (c) 2020-2023 Sensus Slovensko a.s. /// using System; using System.Collections.Generic; using System.Drawing; using System.IO.Ports; using System.Windows.Forms; using log4net; using SharedDatabase.Entities; namespace Workplace.CheckItems { public partial class S640Stream : UserControl, IDevice, ICheckItem { const Int64 MaxDropoutTime = 500; /// ms const Int64 MinHoldTime = 2500; /// ms const Int64 MinBreakTime = 10000; /// ms private static readonly ILog log = LogManager.GetLogger(typeof(S640Stream)); public override string ToString() { return string.Format("S640Stream: {0} {1}", IxPolozky, Part.Name); } /// /// Check item related variables /// WorkplaceDlg parent; public Part Part { get { return part; } } Part part; public int IxPolozky { get; set; } public IList BlackListedCodes { get; set; } public string ScannedCode { get { return PcbNumber.ToString(); } set { } } /// /// Opto serial and data stream related variables /// SerialPort serialPort; /// Used in DebugMode.Normal string partOfTelegram; /// Data unprocessed by ReadDatastream(...), contains incomplete frame Int64 currentRawVolumeUnwrapped; Int64 currentRawTimeUnwrapped; S640.DatastreamFrame frame = new S640.DatastreamFrame(); char[] pcbNrStr = new char[12]; char[] radioAddrStr = new char[10]; /// Int64 validPcbNumber; Int64 validRadioAddress; Int64 firstValidTime; Int64 lastValidTime; /// Int64 currentPcbNr; Int64 currentRadioAddr; /// /// PCB Number and Radio address from data stream /// public Int64 PcbNumber; public Int64 RadioAddress; /// Int64 lastSentNonzeroPcbNumber; S640Stream() { InitializeComponent(); BlackListedCodes = new List(); } /// Empty implementation, might be overriden in derived classes public virtual void StartChangeHandler() { } /// Empty implementation, might be overriden in derived classes public virtual void StopChangeHandler() { } public S640Stream(WorkplaceDlg parent, SharedDatabase.Entities.Part part) : this() { this.parent = parent; this.part = part; kodMaterialuLabel.Text = part.Name; descriptionMaterialuLabel.Text = part.Description; } void Localize() { } public void Initialize() { partOfTelegram = string.Empty; currentRawVolumeUnwrapped = 0; currentRawTimeUnwrapped = 0; validPcbNumber = (long)-1; validRadioAddress = (long)-1; firstValidTime = 0; lastValidTime = 0; sendPcbNr = false; lastSentNonzeroPcbNumber = (long)-1; /// Prepare serial port string comPortName = string.Format("COM{0}", Program.LocalSettings.ScaleSerialPort); serialPort = new SerialPort(comPortName, 9600, Parity.None, 8, StopBits.One); serialPort.Handshake = Handshake.None; serialPort.Open(); log.FatalFormat("{0} initialized: {1}", Name, this); } public void ClearCode() { } public void SubmitCode(string code) { } bool hasFocus; bool sendPcbNr; private void setFocusButton_Click(object sender, EventArgs e) { setFocusButton.Text = ">"; if (!hasFocus && !sendPcbNr && PcbNumber != 0 && PcbNumber != lastSentNonzeroPcbNumber) { sendPcbNr = true; } hasFocus = true; parent.OnFocusPressed(this, new FocusPressedEventArgs(IxPolozky, Part.CodeLocation)); } public void ResetFocus() { setFocusButton.Text = ""; hasFocus = false; } public void SetFocus() { setFocusButton.Text = ">"; if (!hasFocus && !sendPcbNr && PcbNumber != 0 && PcbNumber != lastSentNonzeroPcbNumber) { sendPcbNr = true; } hasFocus = true; } public void RunDeviceBefore() { /// Send if required if (sendPcbNr) { lastSentNonzeroPcbNumber = PcbNumber; sendPcbNr = false; if (PcbNumber > 0) { parent.OnBarcodeReceived(this, new BarcodeReceivedEventArgs(PcbNumber.ToString(), IxPolozky, Part.CodeLocation, PartError.None)); } } ReadDatastream(); if (hasFocus && PcbNumber != 0 && PcbNumber != lastSentNonzeroPcbNumber) { /// This control owned focus and the PcbNumber became valid sendPcbNr = true; } /// Send if required if (sendPcbNr) { lastSentNonzeroPcbNumber = PcbNumber; sendPcbNr = false; if (PcbNumber > 0) { parent.OnBarcodeReceived(this, new BarcodeReceivedEventArgs(PcbNumber.ToString(), IxPolozky, Part.CodeLocation, PartError.None)); } } /// Update UI pcbNumberTextBox.Text = (currentPcbNr > 0) ? currentPcbNr.ToString() : string.Empty; /// Always displayed /// int denominator = (currentPcbNr == (Int64)(-1)) ? (int)MinBreakTime : (int)MinHoldTime; int numerator = Math.Min(Math.Max(0, Convert.ToInt32((currentPcbNr == (Int64)(-1)) ? MinBreakTime - lastValidTime + firstValidTime : lastValidTime - firstValidTime)), denominator); pcbNumberProgressBar.Value = (currentPcbNr != 0) ? (100 * numerator / denominator) : 0; pcbNumberProgressBar.ColorId = (currentPcbNr > 0) ? 0 : 1; } public void RunDeviceAfter() { } public void StopDevice() { try { if (serialPort != null && serialPort.IsOpen) { serialPort.Close(); } } catch (Exception exc) { log.ErrorFormat("Closing serial port failed: {0}", exc.Message); } } /// /// 9600 Bd, 8 data bits, 1 stop bit, no parity /// /// Telegram description: /// /// AAAAAAAAAAAA[tab]BBBBBBBBBB[tab]CCCCCCCCC[tab]DDDDDDDDDD[tab]EE[cr][lf] (49 bytes) /// /// Data Comment Type Calculate to decimal /// ---------------------------------------------------------------- /// AAAAAAAAAAAA PCB number Decimal /// BBBBBBBBBB Radio address Decimal /// CCCCCCCCC Meter reading Decimal Volume in ml /// DDDDDDDDDD Timestamp Decimal Time in ms /// EE Checksum Hexadecimal /// ---------------------------------------------------------------- /// /// Example: /// 540210730770 4291524429 000250215 0433411876 56 /// 540210730770 4291524429 000250223 0433412126 4A /// ... /// /// OptoState.Read or OptoState.Flush void ReadDatastream() { if (serialPort == null || !serialPort.IsOpen) { BackColor = Color.Red; return; } BackColor = SystemColors.Control; try { int receivedBytes = serialPort.BytesToRead; if (receivedBytes > 0) { var buffer = new char[receivedBytes]; serialPort.Read(buffer, 0, receivedBytes); string allRcvd = partOfTelegram + new string(buffer); while (true) { int pos = allRcvd.IndexOf("\r\n"); if (pos < 0) { /// No CR+LF found =>stop processing => wait for more characters in the buffer in the next invocation partOfTelegram = allRcvd; break; } else if (pos < S640.DatastreamFrame.FrameLength - 2) { /// CR+LF found too early, truncate the beginning incl. CR+LF and keep processing datastream in this loop allRcvd = allRcvd.Substring(pos + 2); } else { bool frameOk = frame.UpdateFromString(allRcvd.Substring(pos - S640.DatastreamFrame.FrameLength + 2, S640.DatastreamFrame.FrameLength), ref currentRawVolumeUnwrapped, ref currentRawTimeUnwrapped); if (frameOk) { Array.Copy(frame.Data, 0, pcbNrStr, 0, 12); Array.Copy(frame.Data, 12, radioAddrStr, 0, 10); if (Int64.TryParse(new string(pcbNrStr), out currentPcbNr) && Int64.TryParse(new string(radioAddrStr), out currentRadioAddr) && currentPcbNr != 0 && currentRadioAddr != 0) { if (currentRawTimeUnwrapped - lastValidTime <= MaxDropoutTime && currentPcbNr == validPcbNumber && currentRadioAddr == validRadioAddress && currentPcbNr != lastSentNonzeroPcbNumber) { /// No datastream dropout and data have not changed: extend the validity time lastValidTime = currentRawTimeUnwrapped; if ((lastValidTime - firstValidTime) >= MinHoldTime) { PcbNumber = validPcbNumber; RadioAddress = validRadioAddress; } } else if (currentPcbNr != lastSentNonzeroPcbNumber) { /// Either a datastream dropout or data changed: consider this frame to be the first one for evaluation validPcbNumber = currentPcbNr; validRadioAddress = currentRadioAddr; firstValidTime = currentRawTimeUnwrapped; lastValidTime = currentRawTimeUnwrapped; PcbNumber = 0; RadioAddress = 0; } } } allRcvd = allRcvd.Substring(pos + 2); } } } if (receivedBytes == 0 || currentPcbNr == lastSentNonzeroPcbNumber || currentPcbNr == (long)-1) { /// No data received (= dropout, but no new data) currentPcbNr = (long)-1; currentRadioAddr = (long)-1; if (currentPcbNr == validPcbNumber && currentRadioAddr == validRadioAddress) { /// Next time lastValidTime = lastValidTime + (long)Const.Interval; } else { /// First time firstValidTime = lastValidTime + (long)Const.Interval; lastValidTime = firstValidTime; validPcbNumber = currentPcbNr; validRadioAddress = currentRadioAddr; PcbNumber = 0; RadioAddress = 0; } if ((lastValidTime - firstValidTime) >= MinBreakTime) { PcbNumber = validPcbNumber; RadioAddress = validRadioAddress; } } } catch (Exception e) { log.FatalFormat("Opto-data serial port failure : {0}", e.Message); if (e.InnerException != null) { log.FatalFormat("InnerMessage : {0}", e.InnerException.Message); } } } } }