Here are a few comments:
The Default Buttton ForeColor is Active Caption Text instead of ControlText.
By Default the label on a new button is almost impossible to read. I am not sure how the default gets set in AHMI, But it may be a good idea to have the default more readable.
On Your machine Active Caption Text may be set to a different color. On mine (windows default) it is white.
I have been struggling with PC CPU usage issues. My goal is to have a background thread read some arrays a couple of times a second. I can do this but so far all my attempts have resulted in high CPU usage. Perhaps
you have some insight on this. Consider the following code:
Dim PLC_Run As Boolean
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Read_PLC()
End Sub
Private Sub Read_PLC()
On Error GoTo Err_Sub
Dim sw As New Stopwatch
sw.Start()
Dim dB() As String = Me.EthernetIPforCLXCom1.Read("HMI.BOOL[0]", 2048)
Dim dR() As String = Me.EthernetIPforCLXCom1.Read("HMI.REAL[0]", 1560)
sw.Stop()
Me.lblTime.Text = sw.ElapsedMilliseconds
Exit_Sub:
Exit Sub
Err_Sub:
Debug.Print("Read_PLC " & Err.Description)
Resume Next
Resume
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
PLC_Run = True
Do While PLC_Run
Read_PLC()
Application.DoEvents()
Loop
End Sub
Private Sub btnStop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStop.Click
PLC_Run = False
End Sub
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
PLC_Run = True
Do While PLC_Run
Read_PLC()
Threading.Thread.Sleep(100)
Application.DoEvents()
Loop
End Sub
I have found that a single click of Button1 will cause the PC CPU usuage to jump to 50-60% on a dual core cpu and stay that way for 20 seconds.
A button2 click puts it in a loop where the reads happen continuously. In that case the PC cpu stays at about 10% until I click Stop at which time the CPU goes up to 50% for 10 seconds. During this continuous poll
the PLC CPU goes to 40%.
A button3_click also cause high CPU. If I comment out the Read_PLC the CPU drops to < 10%.
The important point is that a thread.sleep by itself causes the cpu to drop as you would expect, but if Read_PLC is involved with a thread.sleep the cpu jumps up.
This has the same behavior if I run this in a different thread.
I am surprised you can't replicate the problems with:
For i = 0 To 10
Dim dT() As String = frmLoad.EthernetIPforCLXCom1.Read("HMI.BOOL[" & i & "]", 1)
Next
Maybe it is because it is part of a UDT
Try this in a .L5X:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<RSLogix5000Content SchemaRevision="1.0" SoftwareRevision="19.01" TargetName="HMI" TargetType="DataType" ContainsContext="true" Owner="., ." ExportDate="Tue Aug 20 09:05:56 2013" ExportOptions="References
DecoratedData Context Dependencies ForceProtectedEncoding AllProjDocTrans">
<Controller Use="Context" Name="HMI">
<DataTypes Use="Context">
<DataType Use="Target" Name="HMI" Family="NoFamily" Class="User">
<Members>
<Member Name="REAL" DataType="REAL" Dimension="1560" Radix="Float" Hidden="false" ExternalAccess="Read/Write"/>
<Member Name="BOOL" DataType="BOOL" Dimension="2048" Radix="Decimal" Hidden="false" ExternalAccess="Read/Write"/>
<Member Name="SINT" DataType="SINT" Dimension="1" Radix="Decimal" Hidden="false" ExternalAccess="Read/Write"/>
<Member Name="INT" DataType="INT" Dimension="1" Radix="Decimal" Hidden="false" ExternalAccess="Read/Write"/>
<Member Name="DINT" DataType="DINT" Dimension="1" Radix="Decimal" Hidden="false" ExternalAccess="Read/Write"/>
<Member Name="LINT" DataType="LINT" Dimension="1" Radix="Decimal" Hidden="false" ExternalAccess="Read/Write"/>
<Member Name="STRG" DataType="STRING" Dimension="1" Radix="NullType" Hidden="false" ExternalAccess="Read/Write"/>
<Member Name="N_REAL" DataType="DINT" Dimension="0" Radix="Decimal" Hidden="false" ExternalAccess="Read/Write"/>
<Member Name="N_BOOL" DataType="DINT" Dimension="0" Radix="Decimal" Hidden="false" ExternalAccess="Read/Write"/>
<Member Name="N_SINT" DataType="DINT" Dimension="0" Radix="Decimal" Hidden="false" ExternalAccess="Read/Write"/>
<Member Name="N_INT" DataType="DINT" Dimension="0" Radix="Decimal" Hidden="false" ExternalAccess="Read/Write"/>
<Member Name="N_DINT" DataType="DINT" Dimension="0" Radix="Decimal" Hidden="false" ExternalAccess="Read/Write"/>
<Member Name="N_LINT" DataType="DINT" Dimension="0" Radix="Decimal" Hidden="false" ExternalAccess="Read/Write"/>
<Member Name="N_STRG" DataType="DINT" Dimension="0" Radix="Decimal" Hidden="false" ExternalAccess="Read/Write"/>
<Member Name="Z_TIMER" DataType="TIMER" Dimension="0" Radix="NullType" Hidden="false" ExternalAccess="Read/Write"/>
<Member Name="ZZZZZZZZZZHMI15" DataType="SINT" Dimension="0" Radix="Decimal" Hidden="true" ExternalAccess="Read/Write"/>
<Member Name="Z_TRIGGER" DataType="BIT" Dimension="0" Radix="Decimal" Hidden="false" Target="ZZZZZZZZZZHMI15" BitNumber="0" ExternalAccess="Read/Write"/>
</Members>
</DataType>
</DataTypes>
</Controller>
</RSLogix5000Content>
Import as UDT.
Then create a tag called HMI of that UDT type and try the code again. it always breaks for me.
As I said earlier I also have a lot of concerns with the unhandled exceptions I have experienced. As you know an unhandled exception is death to production code.
It appears that
Private Sub DataLinkLayer_DataReceived(ByVal sender As Object, ByVal e As MfgControl.AdvancedHMI.Drivers.Common.PlcComEventArgs)
could benefit from an error handler. You may know of other code entry points (especially from other threads) that need error handlers.
Any error in a driver should kick an error back to the driver entry point.
Personally I put an errorhandler on every code entry point. I still use the old unstructured code because Try Catch doesn't have a Resume Next.
Practically every block of code I write starts with:
Friend Sub Example()
On Error GoTo Err_Sub
Exit_Sub:
Exit Sub
Err_Sub:
ErrorHandler("Example " & Err.Description)
Resume Next
Resume
End Sub
I change Example in the name and ErrorHandler to the new name of the Sub or Function.
I have a global error handler:
Friend Sub ErrorHandler(ByVal S As String, Optional ByVal FlashRed As Boolean = False)
fHMI.ErrorHandler(S, FlashRed)
End Sub
the fHMI.ErrorHandler puts the error in a listbox and logs it to a text file.
If I start to get a lot of errors I can attach to the process and put a breakpoint on the ErrorHandler.
I can step over the real errorHandler and then once I am back in the erring routine move the execution point to resume to take me back to the offending line of code.
This approach has saved me a lot of time.
on the fHMI form (which is a container that can't close) I have a listbox docked to the botton of the form and a Clear and ClearAll button on the Same line just left of the listbox scrollbar.
The buttons are anchored to bottom right. Note that fHMI cannot be the default instance of the form, otherwise it can't display errors from multiple threads.
Here is the code.
Friend HMIErrors As Error_Struct
Friend Structure Error_Struct
Friend NumErrors As Integer
Friend FlashRed As Boolean
Friend LastFlashChange As Integer
End Structure
Friend Sub HMIUpdate()
On Error GoTo Err_Sub
Dim S As String
S = "Sec Change: " & Timing.Sec_Change & vbCrLf _
& "Sec TaskTime: " & Timing.Sec_TaskTime & vbCrLf _
& "PLC Trigger Wait: " & Timing.PLC_Trigger_Wait & vbCrLf _
& "PLC Read Wait: " & Timing.PLC_Read_Wait
Me.lblms.Text = S
If HMIErrors.FlashRed Then
If Now.Second <> HMIErrors.LastFlashChange Then
HMIErrors.LastFlashChange = Now.Second
If Me.ListError.BackColor = System.Drawing.Color.Red Then
Me.ListError.BackColor = Color.FromKnownColor(KnownColor.Window)
Else
Me.ListError.BackColor = System.Drawing.Color.Red
End If
End If
End If
Exit_Sub:
Exit Sub
Err_Sub:
ErrorHandler("frmHMI_HMIUpdate " & Err.Description)
Resume Next
Resume
End Sub
Private Sub btnClearAll_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnClearAll.Click
On Error GoTo Err_Sub
Me.ListError.Items.Clear()
HMIErrors.NumErrors = 0
HMIErrors.FlashRed = False
Me.ListError.BackColor = Color.FromKnownColor(KnownColor.Window)
Exit_Sub:
Exit Sub
Err_Sub:
ErrorHandler("btnClearAll_Click " & Err.Description)
Resume Next
Resume
End Sub
Private Sub btnClear_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnClear.Click
On Error GoTo Err_Sub
If Me.ListError.Items.Count = 0 Then
btnClearAll.PerformClick()
Exit Sub
End If
Dim LastItem As Integer = Me.ListError.TopIndex
Me.ListError.Items.RemoveAt(LastItem)
HMIErrors.NumErrors = Me.ListError.Items.Count
If HMIErrors.NumErrors = 0 Then
btnClearAll.PerformClick()
Else
LastItem = LastItem - 1
If LastItem < 0 Then LastItem = 0
Me.ListError.TopIndex = LastItem
End If
Exit_Sub:
Exit Sub
Err_Sub:
ErrorHandler("btnClear_Click " & Err.Description)
Resume Next
Resume
End Sub
Private Delegate Sub ErrorHandlerCallback(ByVal S As String, ByVal FlashRed As Boolean)
Friend Sub ErrorHandler(ByVal S As String, Optional ByVal FlashRed As Boolean = False)
On Error Resume Next
If Me.ListError.InvokeRequired Then
'The Callback is necessary to display errors from other threads
Dim d As New ErrorHandlerCallback(AddressOf ErrorHandler)
Me.Invoke(d, New Object() {S, FlashRed})
Else
Dim DT As Date
Dim DTFile As String
On Error Resume Next
HMIErrors.NumErrors += 1
DT = Now
S = DT.ToString & ": Alarm " & HMIErrors.NumErrors.ToString & ": " & S
DTFile = PathDLog & "\Log\ERR" & Format(DT, "yy") & Format(DT, "MM") & Format(DT, "dd")
If FlashRed Then
HMIErrors.FlashRed = True
fHMI.ListError.BackColor = System.Drawing.Color.Red
End If
If HMIErrors.NumErrors < 1000 Then
My.Computer.FileSystem.WriteAllText(DTFile & ".txt", S & vbCrLf, True)
Me.ListError.Items.Add(S)
ElseIf HMIErrors.NumErrors = 1000 Then
Me.ListError.Items.Add(S)
S = DT.ToString & ": Alarm " & HMIErrors.NumErrors.ToString & ": " & "Alarm Limit exceeded, Clear All to continue Error recording"
Me.ListError.Items.Add(S)
HMIErrors.FlashRed = True
fHMI.ListError.BackColor = System.Drawing.Color.Red
End If
Me.ListError.TopIndex = Me.ListError.Items.Count - 1
End If
End Sub
It is best to have a UnhandledException handler in MyApplication Events:
Private Sub MyApplication_UnhandledException(ByVal sender As Object, ByVal e As Microsoft.VisualBasic.ApplicationServices.UnhandledExceptionEventArgs) Handles Me.UnhandledException
ErrorHandler(e.Exception.ToString)
Application.DoEvents()
e.ExitApplication = False
End Sub
Also need a declaration:
Public Event UnhandledException(ByVal sender As Object, ByVal e As UnhandledExceptionEventArgs)
This will let your program continue to run in the event of an unhandled exception.
Note that it doesn't work with the debugger. It only works if started from an .exe
Test with:
Dim v(1) As Integer
v(10) = v(10)