A VNC server framework for Lua on NodeMCU. It implements a basic subset of the RFB protocol and enables Lua scripts to send graphics via TCP connection to a VNC client on PC or smartphone. It was inspired by pylotron which demonstrates the simplicity of graphical interaction between an embedded system and a smart client via VNC.
Modules required to be compiled into the firmware:
net
bit
struct
u8g2
The vnc server operates on a NodeMCU socket instance and has to be created by the user script.which has to be prepared upfront by the user script:
require("vncserver").createServer(5900, width, height,
function( srv )
end
)
The createServer()
function expects 4 mandatory parameters:
port
to listen atwidth
of the announced displayheight
of the announced displayuser_init
user init function
The user init function is called when a client connected to the server. It should be used to prepare the new session and set up callback hooks.
All protocol sequences are subsequently handled by the vncserver module.
Once the server exchanged all required info with the client, it stands by and waits for messages from the client. It will delegate these to the user script via callbacks. Callback functions for the following events can be registered within the user init function:
srv.cb_fbupdate
client sent a FramebufferUpdateRequestsrv.cb_disconnection
client disconnectedsrv.cb_key
client sent a KeyEventsrv.cb_pointer
client sent a PointerEventsrv.cb_datasent
all queued data was sent to the client
srv.cb_disconnection = function( srv )
print( "client disconnected" )
end
srv.cb_fbupdate = function( srv )
print( "framebuffer update requested" )
end
srv.cb_key = function( srv, key, down )
-- 'key' contains the keysym
-- 'down' is true for key pressed and false for key released
print( "key event received" )
end
srv.cb_pointer = function( srv, button_mask, x, y )
-- 'button_mask' contains the state of 7 pointer buttons
-- 'x' and 'y' are the current pointer coordinates
print( "pointer event received" )
end
Whenever the client requests a framebuffer update, the server needs to send a FramebufferUpdate message containing a list of rectangles. Note that vncserver only supports RRE encoding at the moment.
A rectangle tells the client which area of the display is affected and what is the background color of this region:
srv:rre_rectangle( base_x, base_y, width, height, num_subrectangles, background )
The following sub-rectangles (if any) define the regions with different colors. They are specified relative to the surrounding rectangle:
srv:rre_subrectangle( rel_x, rel_y, width, height, color )
Example from rectangles.lua
:
function draw_rectangles()
-- FramebufferUpdate message indicating that 1 rectangle description follows:
srv:update_fb( 1 )
-- Next is the rectangle in RRE encoding, 4 sub-rectangles will follow:
srv:rre_rectangle( 0, 0, 128, 128, 4, 0 )
-- The sub-rectangles:
srv:rre_subrectangle( 10, 10, 30, 10, red )
srv:rre_subrectangle( 50, 20, 20, 40, green )
srv:rre_subrectangle( 80, 80, 40, 40, blue )
srv:rre_subrectangle( 60, 50, 30, 50, yellow )
end
For more advanced graphics there's an integration with the u8g2 library, see u8g2_vnc.lua
.
Clients can request a variety of pixel encoding formats. They boil down to the definition of how many values for the red, green, and blue components are available and where they are located inside the transmitted pixel info.
The server stores the client's definitions during the initial handshaking process and provides them as variables to the user script:
srv.red_max
maximum red valuesrv.green_max
maximum green valuesrv.blue_max
maximum blue valuesrv.red_shift
shift for red to final slotsrv.green_shift
shift for green to final slotsrv.blue_shift
shift for blue to final slotsrv.red_len
red value bit lengthsrv.green_len
green value bit lengthsrv.blue_len
blue value bit length
User code needs to adapt its color encoding to these parameters or the client will not be able interpret the colors correctly. See rectangles.lua
for an example.