B4J Question WebApp/WebSockets - Send to specific connection

Declan

Well-Known Member
Licensed User
Longtime User
I have a WebSocket Webapp that allows multiple users to login and access the device status of remote devices from a MySQL database..
The following code works well and accepts multiple connections.
The HTML code has a Dropdown Combobox which allows the user to select a specific device and the MySQL query filters this and displays in the HTML Tableview.
In order to send updated data to the user, I set a timer that then on trigger, send the updated data to ALL connected users and defaults to "ALL Devices".
If a connected user selects a specific Device from the Combobox, I must now Map/List the connection and the "SelectedText" from "cmbFilter_Change" so that when "PopulateDashboardTable" function runs, all connected users will receive the updated data to the selected specific Device selected in the Combobox.
For Example:
Connection 1: Send (PopulateDashboardTable) = [to device connection ID] ["1FE1233"] <-- User selected Combobox item 1FE1233
Connection 2: Send (PopulateDashboardTable) = [to device connection ID] ["ALL Devices"] <-- User selected Combobox item ALL Devices (Default)
Connection 3: Send (PopulateDashboardTable) = [to device connection ID] ["1FEA044"] <-- User selected Combobox item 1FEA044
On initial connection "WebSocket_Connected" the "SelectedText" would be the default "ALL Devices".

I have been able - about 7 years ago - to accomplish something similar, but it was the server communicating with multiple Android tablets.
I have looked at all the examples and tutorials on the forum (Chat - Websockets etc. etc.), and also Granny Google, but am unable to get my head around this.
Could someone please point me in the right direction?

Main Module:
B4X:
'Non-UI application (console / server application)
#Region Project Attributes
    #CommandLineArgs:
    #MergeLibraries: True
    
        'MySQL Connector/J Driver
    #AdditionalJar: mysql-connector-java-5.1.47-bin.jar
#End Region
Sub Process_Globals
    Public srvr As Server   
    Private LiveTimer As Timer
    Public ConnectedDevices As Map
    Dim myFilterText As String
End Sub

Sub AppStart (Args() As String)
    ConnectedDevices = srvr.CreateThreadSafeMap
    
    srvr.StaticFilesFolder = File.Combine(File.DirApp, "www")
    srvr.Initialize("srvr")
    srvr.Port = 5010
    
    ' Register the WebSocket handler for the dashboard page   
    srvr.AddWebSocket("/dashboardws", "DashboardWS")
    
    ' Start background thread to monitor MySQL changes
    StartLiveMonitor
    
    srvr.Start
    StartMessageLoop
End Sub

Public Sub NewConnection(GUID As String, ws As Object)   
    ConnectedDevices.Put(GUID, ws)
End Sub

Public Sub RemoveConnection(GUID As String)
    ConnectedDevices.Remove(GUID)
End Sub

Sub StartLiveMonitor
    ' Periodically queries MySQL and broadcasts updates to active browsers
    LiveTimer.Initialize("LiveTimer", 30000) ' Poll every 10 seconds
    LiveTimer.Enabled = True
End Sub

Sub LiveTimer_Tick   
'    Log("Active Connections: " & ConnectedDevices.Size)
'    For Each ws As Object In ConnectedDevices.Values
'        CallSubDelayed2 (ws, "PopulateDashboardTable", "ALL Devices")
'    Next
End Sub

public Sub GetGUID() As String
    Dim jo As JavaObject
    Return jo.InitializeStatic("java.util.UUID").RunMethod("randomUUID", Null)
End Sub

Websocket Handler:
B4X:
'WebSocket class
Sub Class_Globals
    Private ws As WebSocket
    Private GUID As String   
    Private sql As SQL
End Sub

Public Sub Initialize

End Sub

Private Sub WebSocket_Connected (WebSocket1 As WebSocket)
    GUID = Main.GetGUID
    Log("New Connection: " & GUID)
    ws = WebSocket1
    Main.myFilterText  = "ALL Devices"
    Main.NewConnection(GUID, Me)
    PopulateCombo
    PopulateDashboardTable(Main.myFilterText)
End Sub

Private Sub WebSocket_Disconnected
    Log("Disconnected: " & GUID)
    Main.RemoveConnection(GUID)
End Sub

' Call this method whenever the timer detects new data in MySQL
Public Sub PopulateDashboardTable(myFilter As String)
    If ws.Open = False Then Return
    Log("myFilter: " & myFilter)
    
    sql.Initialize2("com.mysql.jdbc.Driver", "jdbc:mysql://localhost/ideasolut?useSSL=false", "root", "Declan2014!")

    Try
        If (myFilter = "ALL Devices") Then
        Dim rs As ResultSet = sql.ExecQuery("SELECT * FROM ideasolut.trans ORDER BY id DESC")
        End If
        If (myFilter <> "ALL Devices") Then
            Dim rs As ResultSet = sql.ExecQuery("SELECT * FROM ideasolut.trans WHERE deviceid=" & "'" & myFilter & "' ORDER BY id DESC")
        End If
        
        Dim sb As StringBuilder
        sb.Initialize
        Do While rs.NextRow
            Dim myVolts As String = rs.GetString("volts")
            myVolts = myVolts & "V"
            Dim myMessage As String = rs.GetString("msgtype")
            If (myMessage = "00") Then myMessage = "Closed"
            If (myMessage = "01") Then myMessage = "Opened"
            If (myMessage = "03") Then myMessage = "Attack"
            If (myMessage = "04") Then myMessage = "Closed"
            If (myMessage = "05") Then myMessage = "Opened"
            If (myMessage = "06") Then myMessage = "Start"
            If (myMessage = "07") Then myMessage = "Stop"
            If (myMessage = "09") Then myMessage = "SOS"
            If (myMessage = "10") Then myMessage = "Disarmed"
            If (myMessage = "11") Then myMessage = "Armed"
            If (myMessage = "12") Then myMessage = "Motion Detected"
            If (myMessage = "20") Then myMessage = "Power ON"
            If (myMessage = "21") Then myMessage = "Power OFF"
            sb.Append("<tr>")
            sb.Append("<td>").Append(rs.GetString("deviceid")).Append("</td>")
            sb.Append("<td>").Append(rs.GetString("msgdate")).Append("</td>")
            sb.Append("<td>").Append(myMessage).Append("</td>")
            sb.Append("<td>").Append(myVolts).Append("</td>")
            sb.Append("</tr>")
        Loop
        rs.Close
        sql.Close ' Always return connection to the pool
        
        Log("Gotcha: :" & (sb.ToString))
        
        ' Push the pure HTML rows directly into the table body element
        ws.RunFunction("UpdateTableBody", Array As Object(sb.ToString))
        ws.Flush
    Catch
        Log(LastException.Message)
    End Try
    sql.Close
End Sub

Public Sub PopulateCombo
    If ws.Open = False Then Return
    ' Initialize your MySQL connection
    sql.Initialize2("com.mysql.jdbc.Driver", "jdbc:mysql://localhost/ideasolut?useSSL=false", "root", "Declan2014!")

    Try
    Dim rs As ResultSet = sql.ExecQuery("SELECT * FROM regdevices")
        Dim sb As StringBuilder
        sb.Initialize
        sb.Append("<option value=''>ALL Devices</option>")

    Do While rs.NextRow
            Dim id As Int = rs.GetInt("id")
            Dim deviceid As String = rs.GetString("deviceid")
            ' Append the formatted option tag
            sb.Append($"<option value="${id}">${deviceid}</option>"$)
        Loop
        rs.Close
        sql.Close
        
        'Inject the options into the dropdown element directly
        ws.RunFunction("updateDropdown", Array(sb.ToString))
        ws.Flush
    Catch
        Log(LastException.Message)
    End Try
    sql.Close
End Sub

' This sub automatically triggers when b4j_sendData("dropdown_changed", ...) is called
Sub cmbFilter_Change (Params As Map)
    ' Pull the visible text shown to the user in the UI
    Dim SelectedText As String = Params.Get("text")
    
    ' Log out your data natively without crashing
    Log("--- Dropdown Text Retrieved ---")
    Log("Text Received: " & SelectedText)
    Log("From: " & GUID)
'    Log("From: " & receivedFrom)

    PopulateDashboardTable(SelectedText)
End Sub
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Assuming that you know the GUID of the client that you want to communicate with:
B4X:
Dim WebSocketHandler As Object = ConnectedDevices.Get(GUID)
CallSubDelayed2(WebSocketHandler, "Send_Message", "message here") 'Send_Message is a sub in the handler code. Better to include underscore to make sure that it is not obfuscated
 
Upvote 0

Declan

Well-Known Member
Licensed User
Longtime User
Assuming that you know the GUID of the client that you want to communicate with:
B4X:
Dim WebSocketHandler As Object = ConnectedDevices.Get(GUID)
CallSubDelayed2(WebSocketHandler, "Send_Message", "message here") 'Send_Message is a sub in the handler code. Better to include underscore to make sure that it is not obfuscated
Where would I call this from?
Would Send_Message be my "Public Sub PopulateDashboardTable(myFilter As String)"
 
Upvote 0
Top