365 lines
13 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|