B4J Library AN SSH LIBRARY COURTESY OF MS COPILOT - B4JSSH

PREAMBLE

For some time now I have had a B4J app that manages a number of Netonix (any) and Ubiquiti (USW-FLEX) WISP switches that turn on/off a fleet of high end Hikvision cameras in a remote location.

This app has used the SSHJ library which has worked flawlessly.

I have decided to replace the Netonix switches with Planet (WGS-5225-8UP2SV) switches.

Unfortunately I discovered that SSHJ can't communicate with Planet switches, Copilot's explanation follows:

===========================================
Planet Switch SSH Compatibility — Technical Limitation Summary
Overview

Planet‑brand Ethernet switches require an interactive, PTY‑allocated SSH shell for authentication and CLI access. Their SSH server does not support non‑interactive exec channels for login or command execution.
Root Cause
The stock B4J SSHJ library (as distributed for B4J) only exposes non‑interactive exec‑style channels and does not provide a mechanism to open a PTY‑based interactive shell.
Planet switches rely on PTY allocation to present their username/password prompts and to maintain an authenticated session.
Failure Mode
When connecting without a PTY:
  • Planet still emits a Username: prompt
  • The switch rejects all submitted credentials
  • The login state machine resets
  • The session loops indefinitely with repeated Username: prompts
  • No CLI prompt (> or #) is ever reached
This behaviour is consistent across all Planet models that use the same SSH daemon.
Conclusion
Planet switches cannot be automated using the stock B4J SSHJ library because the library lacks PTY‑shell support, which Planet requires for interactive authentication and command execution.

===========================================

After a fruitless search for an existing solution I decided to have a crack at developing an SSH library with the assistance of Copilot.

This was a two-fold exercise - see what I could really do with Copilot and (if lucky) find a solution.

I FOUND IT!!!!!
 
Last edited:

JackKirk

Well-Known Member
Licensed User
Longtime User
CODE

I won't go thru the somewhat torturous conversations I had with Copilot but the net result was a folder in

N:\B4A\SimpleLibraryCompiler\B4JSSH with sub folders:

src
....B4JSSH.java
libs
....jsch-0.1.55.jar​

B4JSSH.java was entirely supplied by Copilot:
B4X:
package b4j.ssh;

import anywheresoftware.b4a.BA.ShortName;
import anywheresoftware.b4a.BA.Version;

import com.jcraft.jsch.*;

import java.io.InputStream;
import java.io.OutputStream;

@ShortName("B4JSSH")
@Version(1.00f)
public class B4JSSH {

    private JSch jsch;
    private Session session;
    private ChannelShell shell;
    private InputStream in;
    private OutputStream out;

    public void Initialize() {
        jsch = new JSch();
    }

    public void Connect(String host, int port, String user, String pass, int timeout) throws Exception {
        session = jsch.getSession(user, host, port);
        session.setPassword(pass);
        session.setConfig("StrictHostKeyChecking", "no");
        session.connect(timeout);
    }

    public void OpenShell() throws Exception {
        shell = (ChannelShell) session.openChannel("shell");
        shell.setPty(true);
        in = shell.getInputStream();
        out = shell.getOutputStream();
        shell.connect();
    }

    public void Write(String cmd) throws Exception {
        out.write((cmd + "\n").getBytes());
        out.flush();
    }

    public String Read() throws Exception {
        StringBuilder sb = new StringBuilder();
        while (in.available() > 0) {
            sb.append((char) in.read());
            Thread.sleep(10);
        }
        return sb.toString();
    }

    public void Disconnect() {
        try { if (shell != null) shell.disconnect(); } catch (Exception ignored) {}
        try { if (session != null) session.disconnect(); } catch (Exception ignored) {}
    }
}
The only real hassles with this was that initially the statement

@ShortName("B4JSSH")

was left out - eventually sorted by referring to an unrelated bit of java for another library that I had,

jsch-0.1.55.jar was sourced by Copilot at: https://mvnrepository.com/artifact/com.jcraft/jsch/0.1.55

and then the compilation steps (after a bit of confusion) were:

N:\B4A\SimpleLibraryCompiler > B4J_LibraryCompiler.exe
Java 8 Compiler: C:\Program Files\Eclipse Adoptium\jdk-8.0.472.8-hotspot\bin\javac.exe
Project Fo;der: N:\B4A\SimpleLibraryCompiler\B4JSSH
Library Name: B4JSSH
Compile

Builds: N:\B4J\Additional Libraries\B4JSSH.xml
N:\B4J\Additional Libraries\B4JSSH.jar

Then all that needed to be done was copy jsch-0.1.55.jar to N:\B4J\Additional Libraries and add this to my B4J project's Main module:

#Region Project Attributes
...
'For B4JSSH library:
#AdditionalJar: jsch-0.1.55.jar
...
#End Region

Sounds easy but took me a weekend...
 
Last edited:

JackKirk

Well-Known Member
Licensed User
Longtime User
INSTALLATION

Load the attached files into your B4J Additional Libraries folder.

Download jsch-0.1.55.jar from: https://mvnrepository.com/artifact/com.jcraft/jsch/0.1.55 and place it into your B4J Additional Libraries folder.

In your B4J project select the B4JSSH library and add this to the Main module:

#Region Project Attributes
...
'For B4JSSH library:
#AdditionalJar: jsch-0.1.55.jar
...
#End Region
 

Attachments

  • B4JSSH.jar
    1.4 KB · Views: 3
  • B4JSSH.xml
    1.9 KB · Views: 4
Last edited:

JackKirk

Well-Known Member
Licensed User
Longtime User
EXAMPLE

Here is a bit of the code from my new B4J wisp switch manager that shows how you can interact with Netonix, Planet and Ubiquiti switches:
B4X:
                    Try

                        'Always start with a fresh engine
                        Obj_ssh.Initialize

                        'Connect
                        Obj_ssh.Connect(wrk_switch_ip, wrk_switch_ssh_port, wrk_camera.switch_user_name, wrk_camera.switch_password, Gen_timeout * DateTime.TicksPerSecond)

                        'Open interactive PTY shell
                        Obj_ssh.OpenShell

                        'Flush camera's switch connect count
                        Gen_switch_connect_count.Remove(wrk_camera.switch_type & ":" & wrk_camera.switch_name & ":" & wrk_camera.switch_ip & ":" & wrk_camera.switch_ssh_port)

                    Catch
                   
                        'If camera's switch timeout count exists...
                        If Gen_switch_connect_count.ContainsKey(wrk_camera.switch_type & ":" & wrk_camera.switch_name & ":" & wrk_camera.switch_ip & ":" & wrk_camera.switch_ssh_port) Then
                       
                            'Increment it
                            Gen_switch_connect_count.Put(wrk_camera.switch_type & ":" & wrk_camera.switch_name & ":" & wrk_camera.switch_ip & ":" & wrk_camera.switch_ssh_port, NumberFormat(Gen_switch_connect_count.Get(wrk_camera.switch_type & ":" & wrk_camera.switch_name & ":" & wrk_camera.switch_ip & ":" & wrk_camera.switch_ssh_port), 1, 0) + 1)
                   
                        'Otherwise...
                        Else
                       
                            'Set it to 1
                            Gen_switch_connect_count.Put(wrk_camera.switch_type & ":" & wrk_camera.switch_name & ":" & wrk_camera.switch_ip & ":" & wrk_camera.switch_ssh_port, 1)
                   
                        End If
                   
                        Error_msg.Text = "Switch connect failure 1 " & Gen_switch_connect_count.Get(wrk_camera.switch_type & ":" & wrk_camera.switch_name & ":" & wrk_camera.switch_ip & ":" & wrk_camera.switch_ssh_port) & " of " & Gen_max_retries & " - " & wrk_camera.switch_type & ":" & wrk_switch_name & ":"  & wrk_switch_ip & ":" &  wrk_switch_ssh_port & "(" & NumberFormat(Gen_switch_connect_count.Get(wrk_camera.switch_type & ":" & wrk_camera.switch_name & ":" & wrk_camera.switch_ip & ":" & wrk_camera.switch_ssh_port), 1, 0) & ")"
                   
                        'Close failed session
                        Obj_ssh.Disconnect

                        'If camera's switch connection has failed Gen_max_retries times...
                        If Gen_max_retries = NumberFormat(Gen_switch_connect_count.Get(wrk_camera.switch_type & ":" & wrk_camera.switch_name & ":" & wrk_camera.switch_ip & ":" & wrk_camera.switch_ssh_port), 1, 0) Then
                   
                            'Save camera's switch error state
                            Gen_switch_error_state.Put(wrk_camera.switch_type & ":" & wrk_camera.switch_name & ":" & wrk_camera.switch_ip & ":" & wrk_camera.switch_ssh_port, Error_msg.Text)
                       
                            'Whatever reporting here
                       
                        End If
                   
                    End Try
               
                    'If switch connected...
                    If Not(Gen_switch_connect_count.ContainsKey(wrk_camera.switch_type & ":" & wrk_camera.switch_name & ":" & wrk_camera.switch_ip & ":" & wrk_camera.switch_ssh_port)) Then

                        'If Netonix switch...
                        If wrk_camera.switch_type = "Netonix" Then

                            'Ask switch for current configuration details
                            wrk_commands_sent = "terminal length 0" & CRLF & "show config" & CRLF & "exit"
                            Obj_ssh.Write(wrk_commands_sent)

                            'Read until we see Netonix prompt or timeout
                            wrk_result = SSH_Read_Until(Obj_ssh, wrk_switch_name & "# exit")

                            'If got Netonix prompt...
                            If wrk_result.Status = "Marker" Then
                       
                                'Extract JSON which is configuration details
                                wrk_json_start = wrk_result.Output.IndexOf("{")
                                wrk_json_end = wrk_result.Output.IndexOf(wrk_switch_name & "# exit")
                                wrk_json_str = wrk_result.Output.SubString2(wrk_json_start, wrk_json_end)

                                'If switch has not had its Revert Timer setting set to 0...
                                If wrk_json_str.IndexOf("""Switch_Revert_Timer"": ""0"",") = -1 Then
                   
                                    'Update Config log on disk
                                    Logger("Config", Gen_crlf & "Netonix switch non-zero revert timer - " & wrk_switch_type & ":"  & wrk_switch_name & ":" & wrk_switch_ip & ":" &  wrk_switch_ssh_port)
                       
                                    Error_msg.Text = "Netonix switch non-zero revert timer - " & wrk_switch_type & ":"  & wrk_switch_name & ":" & wrk_switch_ip & ":" &  wrk_switch_ssh_port
                           
                                    'As no timer is enabled this will cause app to hang
                                    Return
                       
                                End If
                   
                                'Extract PoE Ports configuration details
                                wrk_ports_start = wrk_json_str.IndexOf("""Ports"": [")
                                wrk_ports_end = wrk_json_str.IndexOf2("],", wrk_ports_start)
                                wrk_ports_str = wrk_json_str.SubString2(wrk_ports_start, wrk_ports_end)
                                wrk_port_number_end = 0
                   
                                wrk_port_poe.Clear

                                'Loop forever...
                                Do While True
               
                                    'Locate start of next PoE Port number field
                                    wrk_port_number_start = wrk_ports_str.IndexOf2("""Number"":", wrk_port_number_end) + 10
               
                                    'Quit loop if no more
                                    If wrk_port_number_start = 9 Then Exit
               
                                    'Locate end of PoE Port number field
                                    wrk_port_number_end = wrk_ports_str.IndexOf2(",", wrk_port_number_start)
               
                                    'Extract PoE Port number
                                    wrk_port_number = wrk_ports_str.SubString2(wrk_port_number_start, wrk_port_number_end)
               
                                    'Locate start of next PoE Port state field
                                    wrk_port_poe_start = wrk_ports_str.IndexOf2("""PoE"":", wrk_port_number_end) + 8
               
                                    'Locate end of PoE Port state field
                                    wrk_port_poe_end = wrk_ports_str.IndexOf2(""",", wrk_port_poe_start)
               
                                    'Extract PoE Port current state
                                    wrk_port_poe.Put(wrk_port_number, wrk_ports_str.SubString2(wrk_port_poe_start, wrk_port_poe_end))
                   
                                Loop
                   
                            'Otherwise, must have timed out...
                            Else
                   
                                Error_msg.Text = "Netonix switch show timeout - " & wrk_switch_type & ":" & wrk_switch_name & ":" & wrk_switch_ip & ":" & wrk_switch_ssh_port
               
                                'Save error state of switch
                                Gen_switch_error_state.Put(wrk_switch_type & ":" & wrk_switch_name & ":" & wrk_switch_ip & ":" & wrk_switch_ssh_port, Error_msg.Text)
                   
                                'Whatever reporting here

                            End If

                        'Otherwise, if Planet switch...
                        Else If wrk_camera.switch_type = "Planet" Then

                            'Read until we see Planet Username: prompt or timeout
                            wrk_result = SSH_Read_Until(Obj_ssh, "Username:")
                       
                            'If got Planet Username: prompt...
                            If wrk_result.Status = "Marker" Then
                       
                                'Send Planet switch username
                                wrk_commands_sent = wrk_camera.switch_user_name
                                Obj_ssh.Write(wrk_commands_sent)

                                'Read until we see Planet Password: prompt or timeout
                                wrk_result = SSH_Read_Until(Obj_ssh, "Password:")
                               
                                'If got Planet Password: prompt...
                                If wrk_result.Status = "Marker" Then
                                   
                                    'Send Planet switch password
                                    wrk_commands_sent = wrk_camera.switch_password
                                    Obj_ssh.Write(wrk_commands_sent)
                                   
                                    'Read until we see Planet # prompt or timeout
                                    wrk_result = SSH_Read_Until(Obj_ssh, "#")
                                   
                                    'If got Planet # prompt...
                                    If wrk_result.Status = "Marker" Then
                                   
                                        'Send Planet command to show PoE status of all PoE ports
                                        wrk_commands_sent = "show poe"
                                        Obj_ssh.Write(wrk_commands_sent)

                                        'Read until we see Planet prompt or timeout
                                        wrk_result = SSH_Read_Until(Obj_ssh, "#")

                                        'If got Planet # prompt...
                                        If wrk_result.Status = "Marker" Then

                                            'If got to here wrk_result.Output should look like this:
                                            '
                                            'PoE                     PD     Port               Power     Current
                                            'Interface               Class  Status             Used [W]  Used [mA]
                                            '----------------------  -----  -----------------  --------  ---------
                                            'GigabitEthernet 1/1     6      PoE ON             2 .9      62
                                            'GigabitEthernet 1/2     4      PoE ON             6 .1      129
                                            'GigabitEthernet 1/3     ---    PoE disabled       0 .0      0
                                            'GigabitEthernet 1/4     ---    PoE Search         0 .0      0
                                            'GigabitEthernet 1/5     ---    PoE Search         0 .0      0
                                            'GigabitEthernet 1/6     ---    PoE Search         0 .0      0
                                            'GigabitEthernet 1/7     ---    PoE Search         0 .0      0
                                            'GigabitEthernet 1/8     ---    PoE Search         0 .0      0
                                            '----------------------  -----  -----------------  --------  ---------
                                            '
                                            'Current Power Consumption    9. 0[W] (2%)
                                            'PoE Voltage                 47. 5[V]
                                            'PoE Version==> 3.55(  0)
                                            '
                                            'WGS-5225-8UP2SV#
                                            '
                                            'Notes 1. all Planet Wisp switches denote port numbers as 1/whatever
                                            '         with 1/ being common across all models e.g. "1/3" means port 3
                                            '      2. PD Class 6 = 802.3bt device (a.k.a. PoE++ e.g. Ubiquiti port 1)
                                            '      3. PD CLass 4 = 802.3at device (a.k.a. POE+ e.g. Hikvision camera)
                                            '      4. Normally only a Ubiquiti port 1 would be connected to any
                                            '         WGS-5225-8UP2SV port
                                            '      5. PoE ON = power on, automatically negotiated as per PD class
                                            '      6. PoE disabled = power off
                                            '      7. PoE Search = no device attached

                                            wrk_port_lines = Regex.Split(Gen_crlf, wrk_result.Output)
                           
                                            wrk_port_poe.Clear

                                            For Each wrk_port_line As String In wrk_port_lines

                                                'If a port line...
                                                If wrk_port_line.StartsWith("GigabitEthernet 1/") Then
                                   
                                                    'Strip out "GigabitEthernet 1/"
                                                    wrk_port_line = wrk_port_line.Replace("GigabitEthernet 1/", "")
                                   
                                                    'Extract PoE Port number
                                                    wrk_port_number = wrk_port_line.SubString2(0, 1)

                                                    'Extract PoE Port current state
                                                    If wrk_port_line.Contains("PoE ON") Then
                                                        wrk_port_poe.Put(wrk_port_number, "48V/H")
                                                    Else
                                                        wrk_port_poe.Put(wrk_port_number, "Off")
                                                    End If

                                                End If

                                            Next
                                       
                                        End If

                                    End If
                       
                                End If

                            End If

                            'If timed out somewhere above...
                            If wrk_result.Status = "Timeout" Then
               
                                Error_msg.Text = "Planet switch show timeout - " & wrk_switch_type & ":" & wrk_switch_name & ":" & wrk_switch_ip & ":" & wrk_switch_ssh_port
           
                                'Save error state of switch
                                Gen_switch_error_state.Put(wrk_switch_type & ":" & wrk_switch_name & ":" & wrk_switch_ip & ":" & wrk_switch_ssh_port, Error_msg.Text)
               
                                'Whatever reporting here

                            End If

                        'Otherwise, must be Ubiquiti switch...
                        Else

                            'Send Ubiquiti command to show PoE status of all ports
                            wrk_commands_sent = "ubntbox swctrl poe show" & CRLF & "exit"
                            Obj_ssh.Write(wrk_commands_sent)

                            'Read until we see marker or timeout
                            wrk_result = SSH_Read_Until(Obj_ssh, "# exit")

                            'If got marker...
                            If wrk_result.Status = "Marker" Then

                                'If got to here wrk_result.Output should look like this:
                                '
                                '~l04262279:
                                '~l04262279:
                                'BusyBox v1.25.1 () built-in shell (ash)
                                '~l04262279:
                                '~l04262279:
                                '  ___ ___      .__________.__
                                ' |   |   |____ |__\_  ____/__|
                                ' |   |   /    \|  ||  __) |  |   (c) 2010-2023
                                ' |   |  |   |  \  ||  \   |  |   Ubiquiti Inc.
                                ' |______|___|  /__||__/   |__|
                                '            |_/                  https://www.ui.com
                                '~l04262279:
                                '      Welcome to UniFi USW-Flex!
                                '~l04262279:
                                '********************************* NOTICE **********************************
                                '* By logging in to, accessing, or using any Ubiquiti product, you are     *
                                '* signifying that you have read our Terms of Service (ToS) and End User   *
                                '* License Agreement (EULA), understand their terms, and agree to be       *
                                '* fully bound to them. The use of SSH (Secure Shell) can potentially      *
                                '* harm Ubiquiti devices and result in lost access to them and their data. *
                                '* By proceeding, you acknowledge that the use of SSH to modify device(s)  *
                                '* outside of their normal operational scope, or in any manner             *
                                '* inconsistent with the ToS or EULA, will permanently and irrevocably     *
                                '* void any applicable warranty.                                           *
                                '***************************************************************************
                                '~l04262279:
                                'USW-Flex-US.6.5.32# Total Power Limit(mW): 46000
                                '~l04262279:
                                'Port  OpMode      HpMode    PwrLimit   Class   PoEPwr  PwrGood  Power(W)  Voltage(V)  Current(mA)
                                '                              (mW)
                                '----  ------  ------------  --------  -------  ------  -------  --------  ----------  -----------
                                '   2    Auto        Dot3at     31507  Unknown     Off      Bad      0.00        0.00         0.00
                                '   3    Auto        Dot3at     31507  Class 4      On     Good      2.01       48.60         0.00
                                '   4    Auto        Dot3at     31507  Unknown     Off      Bad      0.00        0.00         0.00
                                '   5    Auto        Dot3at     31507  Class 4      On     Good      2.15       48.81        44.00
                                'USW-Flex-US.6.5.32# exit
                               
                                'Extract block of configuration details
                                wrk_block_start = wrk_result.Output.IndexOf("----  ------  ------------")
                                wrk_block_start = wrk_result.Output.IndexOf2("   2", wrk_block_start)
                                wrk_block_end = wrk_result.Output.IndexOf("# exit")
                                wrk_block_end = wrk_result.Output.LastIndexOf2(Gen_crlf, wrk_block_end)
                                wrk_block_str = wrk_result.Output.SubString2(wrk_block_start, wrk_block_end + 2)

                                'wrk_block_str should look like this:
                                '
                                '   2    Auto        Dot3at     31507  Unknown     Off      Bad      0.00        0.00         0.00
                                '   3    Auto        Dot3at     31507  Class 4      On     Good      2.01       48.60         0.00
                                '   4    Auto        Dot3at     31507  Unknown     Off      Bad      0.00        0.00         0.00
                                '   5    Auto        Dot3at     31507  Class 4      On     Good      2.15       48.81        44.00

                                wrk_port_lines = Regex.Split(Gen_crlf, wrk_block_str)

                                wrk_port_poe.Clear

                                For Each wrk_port_line As String In wrk_port_lines

                                    'wrk_port_line should look like this:
                                    '
                                    '   2    Auto        Dot3at     31507  Unknown     Off      Bad      0.00        0.00         0.00
                       
                                    'Extract PoE Port number
                                    wrk_port_number = wrk_port_line.SubString2(0, 4)
                           
                                    'Extract PoE Port current state
                                    If wrk_port_line.IndexOf(" Off ") = -1 Then
                                        wrk_port_poe.Put(wrk_port_number, "48V")
                                    Else
                                        wrk_port_poe.Put(wrk_port_number, "Off")
                                    End If

                                Next

                            'Otherwise, must have timed out...
                            Else
                                                           
                                Error_msg.Text = "Ubiquiti switch show timeout - " & wrk_switch_type & ":" & wrk_switch_name & ":" & wrk_switch_ip & ":" & wrk_switch_ssh_port
                               
                                'Save error state of switch
                                Gen_switch_error_state.Put(wrk_switch_type & ":" & wrk_switch_name & ":" & wrk_switch_ip & ":" & wrk_switch_ssh_port, Error_msg.Text)
                                   
                                'Whatever reporting here
                           
                            End If

                        End If
                               
                        Obj_ssh.Disconnect
               
                    End If

                End If

and the SSH_Read_Until sub looks like this:
B4X:
Private Sub SSH_Read_Until(SSH_object As B4JSSH, Marker As String) As SSH_Read_Result_Type

    Private wrk_result As SSH_Read_Result_Type
    Private wrk_chunk, wrk_str As String
    Private wrk_end As Long = DateTime.Now + Gen_timeout * DateTime.tickspersecond

    Private wrk_jo As JavaObject
    wrk_jo.InitializeStatic("java.lang.Thread")

    Do While DateTime.Now  < wrk_end
        wrk_chunk = SSH_object.Read
        If wrk_chunk.Length > 0 Then
            wrk_str = wrk_str & wrk_chunk

            'Marker detection
            If wrk_str.Contains(Marker) Then
                wrk_result.Status = "Marker"
                wrk_result.Output = wrk_str
                Return wrk_result
            End If

        End If

        '5 ms pause, stays inside loop       
        Private wrk_5 As Long = 5
        wrk_jo.RunMethodJO("sleep", Array As Object(wrk_5))
       
    Loop

    'Iimeout detection
    wrk_result.Status = "Timeout"
    wrk_result.Output = wrk_str
    Return wrk_result
   
End Sub

For what it is worth here is some documentation I got Copilot to do:
# 📘 **B4JSSH Library — Method Summary**

The **B4JSSH** library provides a minimal, deterministic SSH client wrapper for B4J, designed specifically for devices that require **interactive PTY‑based shell sessions** (e.g., Planet, Netonix, Ubiquiti, Cisco).
It exposes a small, predictable API surface suitable for automation, scripting, and prompt‑driven CLI interaction.


## 🔧 **Initialize**

### **`Initialize()`**
Creates the internal JSch instance and prepares the library for use.

**Purpose:**
Sets up the SSH engine. Must be called before any other method.

---

## 🔌 **Connect**

### **`Connect(host As String, port As Int, user As String, pass As String, timeout As Int)`**
Establishes an SSH session with the target device.

**Parameters:**
- `host` — IP address or hostname
- `port` — SSH port (typically 22)
- `user` — username
- `pass` — password
- `timeout` — connection timeout in milliseconds

**Behaviour:**
- Creates a new SSH session
- Disables strict host key checking
- Authenticates using username/password
- Applies the specified timeout

**Notes:**
This method does **not** open a shell. Use `OpenShell` after connecting.

---

## 🖥️ **OpenShell**

### **`OpenShell()`**
Opens an interactive **PTY‑enabled shell channel**.

**Purpose:**
Provides a persistent, interactive CLI session required by Planet and similar switches.

**Behaviour:**
- Allocates a PTY
- Opens a shell channel
- Exposes raw input/output streams
- Enables real‑time command execution

**Notes:**
This is essential for devices that do not support exec channels.

---

## ✏️ **Write**

### **`Write(cmd As String)`**
Sends a command or raw text to the open shell session.

**Behaviour:**
- Writes the string followed by a newline
- Flushes immediately
- Does not wait for output

**Usage:**
Typically paired with `Read` or a custom `ReadUntil` loop in B4J.

---

## 📥 **Read**

### **`Read() As String`**
Returns all currently available output from the shell buffer.

**Behaviour:**
- Non‑blocking
- Reads whatever bytes are available at the moment
- Returns partial or complete CLI output
- Does not wait for prompts

**Notes:**
This method is intentionally low‑level to allow deterministic prompt handling in B4J.

---

## 🔌 **Disconnect**

### **`Disconnect()`**
Closes the shell channel and SSH session.

**Behaviour:**
- Safely disconnects the shell
- Safely disconnects the session
- Ignores exceptions during cleanup

**Notes:**
Always call this when finished to release resources.

---

# 🧩 **Design Philosophy**

The B4JSSH library intentionally exposes only the **minimal primitives** required for reliable automation:

- Connect
- OpenShell
- Write
- Read
- Disconnect

All higher‑level logic (prompt detection, command wrappers, parsing, retries, paging control) is implemented in B4J, not inside the library.
This keeps the library deterministic, predictable, and compatible with a wide range of network devices.
 
Last edited:

JackKirk

Well-Known Member
Licensed User
Longtime User
POSSIBLE ENHANCEMENTS
 

JackKirk

Well-Known Member
Licensed User
Longtime User
ADDITIONAL NOTES

I don't do java, xml or build libraries but Copilot can - with a lot of guidance mainly in the form of progressively less dumb questions from me....
 
Last edited:

MicroDrie

Well-Known Member
Licensed User
Longtime User
Thank you for sharing this information.
 
Top