-- Plugin for NovaStar Video Wall controllers -- Built in Lua --Layout inspired by VideoHub plugin @ https://github.com/locimation/qsys-plugins PluginInfo = { Name = "NovaStar~VX4S/VX6S/ProHD/ProHDJr/MCTRL4K", -- The tilde here indicates folder structure in the Shematic Elements pane Version = "0.0.0-master", Id = "novastar.plugin.0.0.0-master", -- show this is just a unique id. Show some commented out 'fun' unique ids Description = "Plugin for controlling NovaStar video wall controllers", ShowDebug = true, Author = "Joel Wetzell" } function GetPrettyName() return "NovaStar Controller" end -- Once you've drawn your plugin in Designer, you can determine what colors you use a lot. Save yourself some time by putting them in a table, and then simply calling them later. local Colors = { White = {255, 255, 255}, Black = {0, 0, 0}, Red = {255, 0, 0}, Green = {0, 255, 0}, Blue = {0, 0, 255}, DarkGrey = {0x56,0x56,0x56}, LCD = { 0x02, 0x33, 0xb2 } } -- We can let users determine some of the plugin properties by exposing them here -- While this function can be very useful, it is completely optional and not always needed. -- If no Properties are set here, only the position and fill properties of your plugin will show in the Properties pane function GetProperties() props = { { Name = "IP Address", Type = "string" } } return props end -- The below function is optional (like GetProperties() is), but it can allow further customization of what users can and can't do with your plugin. -- In this example, when Model 1 is selected in the properties pane, the ability to modify some of the properties will be hidden, only allowing customization with Model 2 -- Another application of this is if you have different input/output types for different models, and want those properties to be dynamic in the Properties pane function RectifyProperties(props) return props end -- The below function is where you will populate the controls for your plugin. function GetControls(props) local controls = { -- Source Inputs { Name = "IN1", ControlType = "Button", ButtonType = "Trigger" }, { Name = "IN2", ControlType = "Button", ButtonType = "Trigger" }, { Name = "IN3", ControlType = "Button", ButtonType = "Trigger" }, { Name = "IN4", ControlType = "Button", ButtonType = "Trigger" }, { Name = "IN5", ControlType = "Button", ButtonType = "Trigger" }, { Name = "IN6", ControlType = "Button", ButtonType = "Trigger" }, { Name = "IN7", ControlType = "Button", ButtonType = "Trigger" }, { Name = "IN8", ControlType = "Button", ButtonType = "Trigger" }, { Name = "IN9", ControlType = "Button", ButtonType = "Trigger" }, { Name = "IN0", ControlType = "Button", ButtonType = "Trigger" }, --Brightness Knob { Name = "Brightness", ControlType = "Knob", ControlUnit = "Integer", Min = 0, Max = 255, PinStyle = "Both", UserPin = true }, { Name = "Status", ControlType = "Indicator", IndicatorType = "Status", PinStyle = "Output", UserPin = true } } return controls end -- Variable holding Page Names for ease local pagenames = {"System"} function GetPages(props) -- This function allows you to populate pages in your plugin. local pages = {} table.insert(pages, {name = pagenames[1]}) return pages end function GetControlLayout(props) local controls = {} local rackSize = {800, 120} local inputPosition = {rackSize[1]*1/2,5} local graphics = { { -- Outer box Type = "GroupBox", StrokeWidth = 1, CornerRadius = 8, Fill = Colors.Black, StrokeColor = {0,0,0}, Size = rackSize, Position = {0,0} }, -- Inputs box { Type = "GroupBox", Text = "INPUTS", Color = Colors.White, HTextAlign = "CENTER", CornerRadius = 3, Fill = Colors.Black, StrokeColor = Colors.White, StrokeWidth = 1, Size = {250, rackSize[2] - 10}, Position = inputPosition }, -- NovaStar Text { Type = "Label", Text = "NovaStar", TextSize = 16, HTextAlign = "Center", IsBold = true, Size = {150,16}, Color = Colors.White, Position = {60, rackSize[2]*1/8} } } controls['Brightness'] = { Style="Knob", Color = Colors.DarkGrey, Fill = Colors.DarkGrey, Size={rackSize[2]/2,rackSize[2]/2}, Position= {250, rackSize[2]*1/4} } local SourceButton = { yStart = inputPosition[2] + 20, Padding = 5 } SourceButton.Size = {(rackSize[2] - 30 - (SourceButton.Padding * 2))/2, (rackSize[2] - 30 - (SourceButton.Padding * 2))/2} SourceButton.xStart = inputPosition[1] + ((250 - (((SourceButton.Size[1] * 5) + (SourceButton.Padding * 4))))/2) --First row of input buttons controls['IN1'] = { Style = "Button", ButtonType = "Trigger", Legend = "1", UnlinkOffColor = true, OffColor = Colors.White, Color = Colors.Red, Position = {SourceButton.xStart, SourceButton.yStart}, Size = SourceButton.Size } controls['IN2'] = { Style = "Button", ButtonType = "Trigger", Legend = "2", UnlinkOffColor = true, OffColor = Colors.White, Color = Colors.Red, Position = {SourceButton.xStart + ((SourceButton.Size[1]+SourceButton.Padding)*1), SourceButton.yStart}, Size = SourceButton.Size } controls['IN3'] = { Style = "Button", ButtonType = "Trigger", Legend = "3", UnlinkOffColor = true, OffColor = Colors.White, Color = Colors.Red, Position = {SourceButton.xStart + ((SourceButton.Size[1]+SourceButton.Padding)*2),SourceButton.yStart}, Size = SourceButton.Size } controls['IN4'] = { Style = "Button", ButtonType = "Trigger", Legend = "4", UnlinkOffColor = true, OffColor = Colors.White, Color = Colors.Red, Position = {SourceButton.xStart + ((SourceButton.Size[1]+SourceButton.Padding)*3),SourceButton.yStart}, Size = SourceButton.Size } controls['IN5'] = { Style = "Button", ButtonType = "Trigger", Legend = "5", UnlinkOffColor = true, OffColor = Colors.White, Color = Colors.Red, Position = {SourceButton.xStart + ((SourceButton.Size[1]+SourceButton.Padding)*4),SourceButton.yStart}, Size = SourceButton.Size } --bump down second row of buttons SourceButton.yStart = SourceButton.yStart + SourceButton.Size[1] + (SourceButton.Padding/2) --Second row of input buttons controls['IN6'] = { Style = "Button", ButtonType = "Trigger", Legend = "6", UnlinkOffColor = true, OffColor = Colors.White, Color = Colors.Red, Position = {SourceButton.xStart,SourceButton.yStart}, Size = SourceButton.Size } controls['IN7'] = { Style = "Button", ButtonType = "Trigger", Legend = "7", UnlinkOffColor = true, OffColor = Colors.White, Color = Colors.Red, Position = {SourceButton.xStart + ((SourceButton.Size[1]+SourceButton.Padding)*1), SourceButton.yStart}, Size = SourceButton.Size } controls['IN8'] = { Style = "Button", ButtonType = "Trigger", Legend = "8", UnlinkOffColor = true, OffColor = Colors.White, Color = Colors.Red, Position = {SourceButton.xStart + ((SourceButton.Size[1]+SourceButton.Padding)*2),SourceButton.yStart}, Size = SourceButton.Size } controls['IN9'] = { Style = "Button", ButtonType = "Trigger", Legend = "9", UnlinkOffColor = true, OffColor = Colors.White, Color = Colors.Red, Position = {SourceButton.xStart + ((SourceButton.Size[1]+SourceButton.Padding)*3),SourceButton.yStart}, Size = SourceButton.Size } controls['IN0'] = { Style = "Button", ButtonType = "Trigger", Legend = "0", UnlinkOffColor = true, OffColor = Colors.White, Color = Colors.Red, Position = {SourceButton.xStart + ((SourceButton.Size[1]+SourceButton.Padding)*4),SourceButton.yStart}, Size = SourceButton.Size } controls['Status'] = { Style = "Text", Color = Colors.LCD, TextSize = 10, UserPin = true, PinStyle = "Output", Size = {150,rackSize[2]/2}, Position = {60, rackSize[2]*1/4} } return controls, graphics end if (Controls) then local NovaStar = { socket = TcpSocket.New(), setStatus = function (value, msg) -- 0 = OK -- 1 = Compromised -- 2 = Fault -- 3 = Not Present -- 4 = Missing -- 5 = Initializing -- >5 = Fault Controls['Status'].Value = value; Controls['Status'].String = msg; end, Commands = { Preamble = {0x55, 0xAA}, Connect = {0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x02,0x00,0x57,0x56}, Brightness = {0x00,0x00,0xFE,0xFF,0x01,0xFF,0xFF,0xFF,0x01,0x00,0x01,0x00,0x00,0x02,0x01,0x00}, VX4S = { {0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x2D,0x00,0x20,0x02,0x01,0x00,0x10,0xB4,0x56}, {0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x2D,0x00,0x20,0x02,0x01,0x00,0xA0,0x44,0x57}, {0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x2D,0x00,0x20,0x02,0x01,0x00,0x01,0xA5,0x56}, {0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x2D,0x00,0x20,0x02,0x01,0x00,0x02,0xA6,0x56}, {0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x2D,0x00,0x20,0x02,0x01,0x00,0x71,0x15,0x57}, {0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x2D,0x00,0x20,0x02,0x01,0x00,0x72,0x16,0x57}, {0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x2D,0x00,0x20,0x02,0x01,0x00,0x40,0xE4,0x56}, {0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x2D,0x00,0x20,0x02,0x01,0x00,0x90,0x34,0x57}, } }, Response = { Connected = {0xAA,0x55,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x02,0x00,0x06,0x61,0xBE,0x56} } } -- NovaStar.socket.ReadTimeout = 0.5 function computeChecksum(bytes) local sum = 0 for k,v in pairs(bytes) do sum = sum + v end sum = sum + 0x5555 local sum_l = sum >> 8 local sum_h = sum & 0xFF local checksum = {} table.insert(checksum, sum_h) table.insert(checksum, sum_l) return checksum end function getCmdString(bytes) local cmd = "" for k,v in pairs(bytes) do cmd = cmd .. string.pack("B", v) end return cmd end function sendCommand(cmd) if NovaStar.socket.IsConnected then NovaStar.socket:Write(getCmdString(NovaStar.Commands.Preamble) .. getCmdString(cmd)) end end function sendBrightness(value) local cmd = NovaStar.Commands.Brightness cmd[17] = value if NovaStar.socket.IsConnected then NovaStar.socket:Write(getCmdString(NovaStar.Commands.Preamble) .. getCmdString(cmd) .. getCmdString(computeChecksum(cmd))) end end function hex_dump (str) local len = string.len( str ) local dump = "" local hex = "" local asc = "" for i = 1, len do if 1 == i % 8 then dump = dump .. hex .. asc .. "\n" hex = string.format( "%04x: ", i - 1 ) asc = "" end local ord = string.byte( str, i ) hex = hex .. string.format( "%02x ", ord ) if ord >= 32 and ord <= 126 then asc = asc .. string.char( ord ) else asc = asc .. "." end end return dump .. hex .. string.rep( " ", 8 - len % 8 ) .. asc end function processResponse(data) if data == getCmdString(NovaStar.Response.Connected) then print("Successful Connection Response Received") NovaStar.setStatus(0,"Connected - " .. Properties['IP Address'].Value) end end NovaStar.socket.Connected = function() print("TCP Connection Established to NovaStar @ " .. Properties['IP Address'].Value) sendCommand(NovaStar.Commands.Connect) end NovaStar.socket.Reconnect = function() NovaStar.setStatus(5,"Attempting to reconnect") end NovaStar.socket.Data = function () print("Reading " .. NovaStar.socket.BufferLength .. " Bytes") local data = NovaStar.socket:ReadLine(TcpSocket.EOL.Lf); while (data ~= nil) do processResponse(data) data = NovaStar.socket:ReadLine(TcpSocket.EOL.Lf); end end NovaStar.socket.Closed = function() NovaStar.setStatus(2,"Connection closed by NovaStar") end NovaStar.socket.Error = function(sock, err) print("TCP Socket Error: " .. err) NovaStar.setStatus(2,"Communication error with NovaStar") end NovaStar.socket.Timeout = function(sock, err) NovaStar.setStatus(2,"Timeout in connection to NovaStar") end for k,v in pairs(NovaStar.Commands['VX4S']) do print('Setting up IN' .. k) Controls['IN'..k].EventHandler = function() sendCommand(v) end end Controls['Brightness'].EventHandler = function() sendBrightness(Controls['Brightness'].Value) end if(Properties['IP Address'].Value ~= '') then NovaStar.setStatus(5, 'Connecting to NovaStar'); NovaStar.socket:Connect(Properties['IP Address'].Value, 5200); else NovaStar.setStatus(3, '\nPlease set IP address.'); Controls['IN1'].IsDisabled = true; Controls['IN2'].IsDisabled = true; Controls['IN3'].IsDisabled = true; Controls['IN4'].IsDisabled = true; Controls['IN5'].IsDisabled = true; Controls['IN6'].IsDisabled = true; Controls['IN7'].IsDisabled = true; Controls['IN8'].IsDisabled = true; Controls['IN9'].IsDisabled = true; Controls['IN0'].IsDisabled = true; Controls['Brightness'].IsDisabled = true; end; end