tbf/Workplace/CheckItems/S640Stream.cs

365 lines
13 KiB
C#

///
/// 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<string> 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<string>();
}
/// <summary> Empty implementation, might be overriden in derived classes </summary>
public virtual void StartChangeHandler() { }
/// <summary> Empty implementation, might be overriden in derived classes </summary>
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);
}
}
/// <summary>
/// 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
/// ...
/// </summary>
/// <param name="streamState">OptoState.Read or OptoState.Flush</param>
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);
}
}
}
}
}