Startup procedure for initializing Luup plugins

I just made some improvements to the Somfy plugin so that it more gracefully handles startup issues and reporting failures to a user. Previously the startup sequence always showed up as ‘ok’, giving the user a false sense that everything was ok even when he hadn’t yet specified basic parameters.

Now, the code first checks for basic startup parameters, which are stored in UPNP variables, and sets them to a default value if they’re not already specified. You should at least set them an empty string because by setting them to something then the user will see them in the web user interface and be able to easily change them, as opposed to creating them again from scratch:

     local lul_ID = luup.variable_get("urn:micasaverde-com:serviceId:SomfyBlinds1","BlindIds",lul_device)
     if( lul_ID==nill ) then
      lul_ID = "01,02"
      luup.variable_set("urn:micasaverde-com:serviceId:SomfyBlinds1","BlindIds",lul_ID,lul_device)
     end

Next, since this device uses an IO Port (ie a serial port, or an ethernet port, or a usb connection), we should check that the connection is specified and is active before doing the startup:

     if( luup.io.is_connected(lul_device)==false ) then
      luup.log('No port for Somfy',1)
      luup.task('Choose the Serial Port for the URTSI',2,'Somfy Blind Interface',-1)
      return false
     end

The is_connected function is just added in firmware 912. If there’s a failure of any kind, we should call luup.task and put in some sort of description with the status code 2 (Error), and then ‘return false’. This way the user sees in the web panel that the module ‘Somfy Blind Interface’ is in an error condition and what the problem is (he didn’t choose the serial port). If we didn’t do the ‘return false’, the user would think the Somfy module was ok since, when the startup function doesn’t return false, the architecture assumes the module is running fine. If we called return false, didn’t call luup.task, then the user see that the module was failing, but would have only a generic “Startup sequence failed” without knowing the explicit reason.

Have you considered using Lua’s tuple return like:

return false, "Choose the Serial Port for the URTSI"

this appears to be a common convention in Lua, and would save the extra lines for calling [tt]luup.task[/tt] with the random parameters.

It would also make for a less code solution, which will likely help folks get going. Folks that use the legacy “[tt]return false[/tt]” (single value) will get the same behavior they get today, since your calling framework will simply get “nil” when it makes the call.

Our plugin code would then look like:

return false, "Choose the Serial Port for the EiEiO"

and your framework code would look like:

local state, message = lul_startup(lul_device) if (~state) then luup.log(message, 1) luup.task(message, 2, '', -1) end

call me lazy, but I look to put as much into the framework components as possible to avoid people writing complex script :slight_smile:

Thoughts?

good point. I’ll add that ability right now, in addition to the task method.

Done, the next release has this. Now the startup for the gc100 looks like this with return [false=fail, true=ok], ‘comments’, ‘module name’

function lug_startup(lul_device)
–[[
Need to fetch all the gc100 devices. The call to luup.io.write can take a while,
so it should not be called in a normal job or upnp action since, if it blocks for more than a few seconds,
it will create a deadlock condition and abort. Only the startup code can block for extended periods
because the device hasn’t finished starting up yet.

  Alternative: Rather than doing it here, since this could take a while,
  there's an action "Initialize" and we'll call it in a job so we're not moduleing for a long time
  waiting for the gc100 to enumerate the devices.  luup.call_action("Initialize")

  We can put a settings/ioPort at the top of this file so we don't need to open the port manually.
  Without it add this to the top of this function: 
  luup.io.open(lul_device,luup.devices[lul_device].ip,4998)

  need to add a call to lu_ready(false)
  ]]

  luup.log("starting device: " .. tostring(lul_device))

  lug_numdevices=1
  lug_modules = {}

  if luup.io.intercept()==false or luup.io.write("getdevices")==false then
    luup.log("cannot send getdevices",1)
    return false,'Cannot send devices','gc100'
  end

  luup.log("got devices")
  child_devices = luup.chdev.start(lul_device);
  
  local lul_IsError=false

  while true do
    incoming_data = luup.io.read();

    if( incoming_data == nil or string.find(incoming_data,"endlistdevices") ) then
      if( incoming_data == nil ) then
        luup.log("didn't get endlistdevices")
        lul_IsError=true
      end
        break
    end

    local lul_tokens = string.gmatch(incoming_data, "[%w]+")
    local lol_type=lul_tokens()
    local lul_iModule=lul_tokens()
    local lul_iCount=lul_tokens()
    local lul_sType=lul_tokens()

    if( lol_type~="device" or lul_sType==null ) then
        luup.log("got unknown " .. tostring(incoming_data),1)
      lul_IsError=true
    else
        luup.log("module" .. tostring(lul_iModule) .. " port " .. tostring(lul_iCount) .. " type " .. tostring(lul_sType) .. " device: " .. tostring(lul_device))
        lug_parsedevice(lul_device,tonumber(lul_iModule),tonumber(lul_iCount),lul_sType)
    end
  end
  
  if( lul_IsError ) then
    luup.log("failed to get devices",1)
    return false,'Cannot get devices','gc100'
  end
  
  luup.log("num modules " .. tostring(#lug_modules))
  local lul_iValidModules = 0
  local lul_iPortNum = 4999

  for iModule,tData in pairs(lug_modules) do
    if( tData.type=="IR" ) then
        if luup.io.write("getstate," .. tostring(tData.module) .. ":" .. tostring(tData.unit) )==false then
	        luup.log("cannot send getstate",1)
        return false,'Cannot get state','gc100'
        end

        incoming_data = luup.io.read();
        if( string.find(incoming_data,"state," .. tostring(tData.module) .. ":" .. tostring(tData.unit)) ) then
	        tData.type = "TTL"
        end
    end
    
    local lul_sType = tData.type

    if( lul_sType=="SERIAL" ) then
        luup.chdev.append(lul_device,child_devices,iModule,"","urn:micasaverde-org:device:SerialPort:1","","","urn:micasaverde-com:serviceId:HaDevice1,Protocol=RS232\nurn:micasaverde-com:serviceId:HaDevice1,IOPort=" .. tostring(lul_iPortNum),true,true)
      lul_iPortNum = lul_iPortNum+1
      lul_iValidModules=lul_iValidModules+1
    elseif( lul_sType=="RELAY" ) then
        luup.chdev.append(lul_device,child_devices,iModule,"","urn:schemas-upnp-org:device:BinaryLight:1","D_BinaryLight1.xml","","",true)
      lul_iValidModules=lul_iValidModules+1
    elseif( lul_sType=="IR" ) then
        luup.chdev.append(lul_device,child_devices,iModule,"","urn:schemas-micasaverde-com:device:IrTransmitter:1","D_IrTransmitter1.xml","","",true,true)
      lul_iValidModules=lul_iValidModules+1
    elseif( lul_sType=="TTL" ) then
        luup.chdev.append(lul_device,child_devices,iModule,"","urn:schemas-micasaverde-com:device:MotionSensor:1","D_MotionSensor1.xml","","",true)
      lul_iValidModules=lul_iValidModules+1
    else
      luup.log("Skipping unknown type: " .. tostring(lul_sType))
    end
  end

  luup.log("ValidModules: " .. tostring(lul_iValidModules))
  if( lul_iValidModules>0 ) then luup.set_failure(false) else luup.set_failure(true) end
  lug_last_sendir_jobid = 1 -- Give each ir job a unique id
  luup.chdev.sync(lul_device,child_devices)
  return true,'ok','gc100'
end

Great, thanks!!

It would be nice to have something similar for regular jobs - to report job failure and the cause.