This is a demo showing how VB6 interfaces can be implemented and consumed to offer an "internal plugin" approach to programming operational phases within the lifetime of a VB6 program.
The basic idea here is to share a Form's MSComm control among several separate classes, each class implementing some phase of program operation that uses the MSComm control's connection to an external device.
In order to be robust these communicating "phase" classes need to implement timeouts, so they are written as InvisibleAtRuntime UserControl modules which can easily host their own Timer controls. Otherwise they could be Class modules instead.
The program's "operational phases" are:
Reset the external device.
Upload the script read from a file to the external device.
Run an activity that interacts with the running script on the external device.
On form unload, first reset the external device once more to leave it in a known state without a running script.
The form's user interface has several sections:
At the bottom we have a logging area where activity is reported. This could be omitted entirely, logged to disk, occur on a secondary modeless form, etc.
In the center we have a display only "terminal" area displaying the serial port interaction with the external device. In most phases it works in text mode, but since the script interaction phase of the program uses binary datagrams it shifts into displaying hex with direction indicators at the right. Like the logging area this could be omitted or moved elsewhere. It is just another program feature used to help monitor and understand the process.
At the top is the part of the form where you interact with the script running on the remote device. This is a trivial application where you can provide a 16-bit value and request it be incremented and returned, or you can request a reading from the 10-bit analog/digital converter on the remote device.
Requirements
Windows PC that can run VB6 programs and support the MSComm control, with a USB port and the driver for the USB-UART Bridge chip on your ModeMCU DevKit board.
VB6 Pro or Enterprise.
A NodeMCU DevKit board flashed with the NodeMCU Lua firmware and a USB-microUSB cable.
As written the program makes some assumptions defined at the top of Form1:
Code:
Private Const COMM_PORT As Integer = 3
Private Const COMM_SETTINGS As String = "115200,n,8,1"
Private Const LUA_PROGRAM_FILE As String = "sample.lua"
This presumes that your target NodeMCU DevKit board is on COM3: and is a recent firmware build that defaults to 115,200 bps instead of the older 9,600 bps default.
NodeMCU DevKit
I won't go into the details. You can search the web for a ton of information.
This target device was chosen because it is self-contained and needs very little to serve as an external platform for demonstrating this "internal plugin" approach. Just a $6 NodeMCU DevKit board and a $2 USB cable you may already have for phone charging and you are set.
If your DevKit has header pins soldered in you may want to press it into a prototyping breadboard to weight it down to your desk and protect the pins. But nothing needs to be connected aside from the USB cable.
These contain an ESP8266 with its RAM and Flash and WiFi Access Point/Station radio and antenna and GPIO pins as well as a USB-UART Bridge chip, a couple of pushbuttons, LEDs, and 3.3v regulator.
Normally you either flash these with custom compiled firmware or else store an "ini.lua" script in the flash filesystem to be executed upon device reset.
Instead we will reset the device, have it not find any initialization script, and then "upload" our script by programmatically "typing it in" to the Lua command prompt.
The last thing we "type" registers a callback into our script on receipt of data via the UART. Here is the file we "type into" Lua:
sample.lua
Code:
-- Turn off the WiFi radio we are not using, but don't save the
-- setting to flash:
wifi.nullmodesleep(true)
wifi.setmode(wifi.NULLMODE, false)
-- Define serial input callback function:
function handleInput(data)
local reqType = string.byte(data, 1)
local outValue = 0
local status = 0
if reqType == 0x01 then
-- Add 1 to unsigned 16-bit big-endian input and return it:
outValue = (string.byte(data, 2) * 256 + string.byte(data, 3) + 1) % 65536
status = 0x01
elseif reqType == 0x02 then
-- Ignore the next 2 bytes of input, read and return ADC value
-- from the NodeMCU DevKit's A0 pin:
outValue = adc.read(0)
status = 0x01
else
status = 0xff -- Respond with "bad request."
end
-- Note: math.floor() call not needed with an integer build of the NodeMCU
-- firmware.
uart.write(0, status, reqType, math.floor(outValue / 256), outValue % 256)
end
-- Register serial input callback. When 3 characters are received
-- execute it:
uart.on("data", 3, handleInput, 0)
(continued)
Last edited by dilettante; Apr 24th, 2018 at 09:25 PM.
Re: [VB6] Interfaces and Internal Plugin Architecture
(continuing)
RunningApp UserControl code
Code:
Option Explicit
'==========
'RunningApp
'==========
Implements ICommPlugin
Private CommHost As ICommHost
Private Request() As Byte
Private Response As ByteString 'Binary StringBuilder object.
Public Event Op1Response(ByVal Op1NewValue As Long)
Public Event Op2Response(ByVal Op2Result As Long)
Public Event ResponseError()
Public Property Get Object() As Object
Set Object = Me
End Property
Public Sub RequestOp1(ByVal Op1Value As Long)
Request(0) = &H1
Request(1) = Op1Value \ 256
Request(2) = Op1Value Mod 256
'Start interval timer:
Timer1.Enabled = True
With CommHost
.SendData Request
.EndOfDatagram SentDatagram:=True
End With
End Sub
Public Sub RequestOp2()
Request(0) = &H2
Request(1) = &H0
Request(2) = &H0
'Start interval timer:
Timer1.Enabled = True
With CommHost
.SendData Request
.EndOfDatagram SentDatagram:=True
End With
End Sub
Private Sub ICommPlugin_CommEvent(ByVal CommEvent As MSCommLib.OnCommConstants)
'We ignore these here.
End Sub
Private Sub ICommPlugin_Connect(ByVal ICommHost As ICommHost, Optional ByVal Task As Variant)
Set CommHost = ICommHost
CommHost.Message "Starting interaction with the script"
CommHost.Running State:=True
End Sub
Private Sub ICommPlugin_Disconnect()
CommHost.Running State:=False
Set CommHost = Nothing
End Sub
Private Sub ICommPlugin_ErrorEvent(ByVal CommEvent As MSCommLib.CommEventConstants)
Timer1.Enabled = False
With CommHost
.Running State:=False
.Message "Communication error " _
& CStr(CommEvent) _
& " while interacting with the script"
.Finished Success:=False
End With
End Sub
Private Sub ICommPlugin_ReceivedData(ByRef Data() As Byte)
'Process received data.
Dim I As Long
Dim ResponseBytes() As Byte
Timer1.Enabled = False
With Response
.Cat Data
If .Length >= 4 Then
CommHost.EndOfDatagram SentDatagram:=False
ResponseBytes = .Left(4)
.Value = .Mid(4) 'Extract the 4 processed bytes, though since we are using
'a chatty protocol there shouldn't be anything else left.
If ResponseBytes(0) = &H1 Then
Select Case ResponseBytes(1)
Case &H1
RaiseEvent Op1Response(ResponseBytes(2) * 256& + ResponseBytes(3))
Case &H2
RaiseEvent Op2Response(ResponseBytes(2) * 256& + ResponseBytes(3))
Case Else
RaiseEvent ResponseError
End Select
Else
RaiseEvent ResponseError
End If
Else
'Restart interval timer:
Timer1.Enabled = True
End If
End With
End Sub
Private Sub Timer1_Timer()
Timer1.Enabled = False
With CommHost
.Running State:=False
.Message "Timed out while interacting with the script"
.Finished Success:=False
End With
End Sub
Private Sub UserControl_Initialize()
Set Response = New ByteString
With Response
.Chunk = 4
.Clear
End With
ReDim Request(0 To 2)
Timer1.Interval = 64
End Sub
Private Sub UserControl_Resize()
Size 480, 480
End Sub
Private Sub UserControl_Terminate()
Set CommHost = Nothing
End Sub
This code interacts with Form1 to accept requests, format and send them on to the NodeMCU Lua script, and get results back and unformat those to be forwarded to and displayed on Form1.
Summary
You could rip out the interfaces and the UserControls and code everything inline within Form1. The notion here is that if your activities in each "operational phase" are complex you could end up with a really complicated and difficult to maintain giant blob of conditional logic to handle the serial communication. Add timeout handling and things just get uglier.
Instead Form1 is reduced to its application activity and managing the MSComm control at a simple level while plugging in objects using secondary interfaces to move from phase to phase. Instead of phased operation it might even route traffic to an array of secondary interface objects more dynamically.
You can even do the same sort of thing using a Winsock control and a TCP connection instead of the MSComm control and serial connection.
The NodeMCU board is a common, cheap, and easy to use item for this demo. It has far more capability than we made use of here but that's more of an IoT topic than a VB6 programming topic. Just adding a photodiode or light-dependent resistor to pin A0 of the DevKit would make this demo more interesting with no software changes.
Last edited by dilettante; Apr 24th, 2018 at 08:12 PM.
Re: [VB6] Interfaces and Internal Plugin Architecture
While this is a weird way to use an ESP8266 NodeMCU DevKit board, it does show one possibility.
Using the either the USB-UART serial connection (or remotely using TCP or HTTP over WiFi) you can use the DevKit to add GPIO pin capabilities to a Windows VB6 program. You could also do the same with a simpler/cheaper ESP8266 module from the ESP-01 on up. These can cost as little as $1 a piece on eBay.