Changeset 41


Ignore:
Timestamp:
2012-12-02 23:19:32 (12 years ago)
Author:
nlrb
Message:
  • Creates devices based on panel settings (AutoCreate?=1)
  • Prepared for multi-language support
  • Message sending more robust
  • Note: not backwards compatible (variables changed)
Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • TabularUnified trunk/D_Powermax.json

    r39 r41  
    6161                    "Display": { 
    6262                        "Service": "urn:micasaverde-com:serviceId:PowermaxAlarmPanel1", 
    63                         "Variable": "PowermaxVersion", 
     63                        "Variable": "PluginVersion", 
    6464                        "Top": 25, 
    6565                        "Left": 150, 
  • TabularUnified trunk/L_Powermax.lua

    r40 r41  
    88local bitw = require("bit") 
    99 
    10 local POWERMAX_VERSION = "1.0 alpha" 
    11 local LOG_DEBUG = true 
     10local PLUGIN_VERSION = "1.0 alpha" 
    1211local MotionOffDelay = 5 
    1312 
     
    3635end 
    3736 
     37-- DEVICES -- 
     38local pmPanelDev = 0 
     39local pmPartitionDev_t  = { 0, 0, 0 } 
     40local pmPanelTypeNr = 3 -- assume Powerlink Pro (no partitions) by default 
    3841-- MESSAGE HANDLING -- 
    3942local pmIncomingPdu = "" 
     
    4750local TIMEOUT = 20 -- wait max. 20 sec for a response 
    4851local MSG_RETRIES = 10 
    49 -- DEVICES -- 
    50 local panel_device = 0 
    51 local partition_device  = 0 
    5252-- SETTINGS -- 
    53 local pmSettings_t = { [0x00] = "", [0x01] = "", [0x02] = "", [0x04] = "", [0x09] = "", [0x0A] = "", [0x0B] = "" } 
     53local pmSettings_t = {} 
    5454local pmForcedDisarmCode = string.char(0x00, 0x00) 
    5555local pmPincode_t = { string.char(0x00, 0x00) } 
    56 local pmPanelSerial = "" 
     56local pmLang = "EN" 
    5757-- EVENT LOG -- 
    5858local pmEventLog = "" 
     
    6464local pmPowerlinkRetry = 0 
    6565local PL_RETRIES = 3 
     66--- Debugging --- 
     67local pmLogDebug = true 
     68local pmFilenameLog = "/var/log/cmh/powermax_pdu.txt" 
     69local pmFilenameSettings = "/var/log/cmh/powermax_settings.txt" 
    6670 
    6771local PANEL_SID        = "urn:micasaverde-com:serviceId:PowermaxAlarmPanel1" 
     
    6973local SECURITY_SID     = "urn:micasaverde-com:serviceId:SecuritySensor1" 
    7074local SENSOR_SID       = "urn:micasaverde-com:serviceId:HaDevice1" 
     75local KEYPAD_SID       = "urn:micasaverde-com:serviceId:Keypad1" 
    7176      
    7277local X10DEVICE_SID    = "urn:upnp-org:serviceId:SwitchPower1" 
     
    7883local CON_SENSOR_INIT = SECURITY_SID .. ",Tripped=0" 
    7984local CON_BATSENSOR_INIT = CON_SENSOR_INIT .. "\n" .. SENSOR_SID .. ",BatteryLevel=100" 
     85local CON_SIREN       = X10DEVICE_SID .. ",Target=0\n" .. X10DEVICE_SID .. ",Status=0\n" 
     86local CON_KEYPAD      = KEYPAD_SID .. ",Status=0" 
    8087 
    8188local pmDevices_t = { 
    82    VAR_ALARM  = { "urn:schemas-micasaverde-com:device:AlarmPartition:2", "D_PowermaxPartition2.xml", "", "", true }, 
    83    VAR_SWITCH = { "urn:schemas-micasaverde-com:device:BinaryLight:1", "D_BinaryLight1.xml", "", CON_X10SW_INIT, false }, 
    84    VAR_DIM    = { "urn:schemas-micasaverde-com:device:DimmableLight:1", "D_DimmableLight1.xml", "",CON_X10DIM_INIT, false }, 
    85    VAR_DOOR   = { "urn:schemas-micasaverde-com:device:DoorSensor:1", "D_DoorSensor1.xml", "", CON_BATSENSOR_INIT, false }, 
    86    VAR_MOTION = { "urn:schemas-micasaverde-com:device:MotionSensor:1", "D_MotionSensor1.xml", "", CON_BATSENSOR_INIT, false }, 
    87    VAR_SMOKE  = { "urn:schemas-micasaverde-com:device:SmokeSensor:1", "D_SmokeSensor1.xml", "", CON_BATSENSOR_INIT, false } 
     89   VAR_PARTITION = { "urn:schemas-micasaverde-com:device:AlarmPartition:2", "D_PowermaxPartition2.xml", "", "", true }, 
     90   VAR_SWITCH    = { "urn:schemas-micasaverde-com:device:BinaryLight:1", "D_BinaryLight1.xml", "", CON_X10SW_INIT, false }, 
     91   VAR_DIM       = { "urn:schemas-micasaverde-com:device:DimmableLight:1", "D_DimmableLight1.xml", "",CON_X10DIM_INIT, false }, 
     92   VAR_MAGNET    = { "urn:schemas-micasaverde-com:device:DoorSensor:1", "D_DoorSensor1.xml", "", CON_BATSENSOR_INIT, false }, 
     93   VAR_MOTION    = { "urn:schemas-micasaverde-com:device:MotionSensor:1", "D_MotionSensor1.xml", "", CON_BATSENSOR_INIT, false }, 
     94   VAR_SMOKE     = { "urn:schemas-micasaverde-com:device:SmokeSensor:1", "D_SmokeSensor1.xml", "", CON_BATSENSOR_INIT, false }, 
     95   VAR_SIREN     = { "urn:schemas-micasaverde-com:device:Siren:1", "D_Siren1.xml", "", CON_SIREN, false }, 
     96   VAR_KEYPAD    = { "urn:schemas-micasaverde-com:service:Keypad:1", "D_Keypad1.xml", "", CON_KEYPAD, false } 
    8897} 
    8998 
     
    93102local pmSendMsg_t = { 
    94103   MSG_INIT      = { string.char(0xAB, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43), nil }, 
    95    MSG_RESTORE   = { string.char(0xAB, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43), nil }, 
     104   MSG_RESTORE   = { string.char(0xAB, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43), 0xA5 }, 
    96105   MSG_ENROLL    = { string.char(0xAB, 0x0A, 0x00, 0x00) .. pmDownloadCode .. string.char(0x00, 0x00, 0x00, 0x00, 0x00, 0x43), nil }, 
    97106   MSG_EVENTLOG  = { string.char(0xA0, 0x00, 0x00, 0x00) .. "pin" .. string.char(0x00, 0x00, 0x00, 0x00, 0x00, 0x43), 0xA0 }, 
     
    102111   MSG_BYPASSDIS = { string.char(0xAA) .. "pin" .. string.char(0x00, 0x00, 0x00, 0x00) .. "bypass" .. string.char(0x43), nil }, 
    103112   MSG_DOWNLOAD  = { string.char(0x24, 0x00, 0x00) .. pmDownloadCode.. string.char(0x00, 0x00, 0x00, 0x00, 0x00, 0x00), 0x3C }, 
    104    MSG_SETTIME   = { string.char(0x46, 0xF8, 0x00, 0x00) .. "time" .. string.char(0xFF, 0xFF), nil }, 
     113   MSG_SETTIME   = { string.char(0x46, 0xF8, 0x00) .. "time" .. string.char(0xFF, 0xFF), nil }, 
    105114   MSG_DL        = { string.char(0x3E) .. "item" .. string.char(0xB0, 0x00, 0x00, 0x00, 0x00, 0x00), 0x3F}, 
    106115   MSG_SER_TYPE  = { string.char(0x5A, 0x30, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), 0x33}, 
     
    110119 
    111120pmDownloadItem_t = { 
    112    MSG_DL_FORCECODE = string.char(0x11, 0x01, 0x02, 0x00), 
    113    MSG_DL_PHONENRS  = string.char(0x36, 0x01, 0x20, 0x00), 
    114    MSG_DL_PINCODES  = string.char(0xFA, 0x01, 0x10, 0x00), 
    115    MSG_DL_PANELFW   = string.char(0x00, 0x04, 0x20, 0x00), 
    116    MSG_DL_SERIAL    = string.char(0x30, 0x04, 0x08, 0x00), 
    117    MSG_DL_ZONES     = string.char(0x00, 0x09, 0x78, 0x00), 
    118    MSG_DL_KEYFOBS   = string.char(0x78, 0x09, 0x40, 0x00), 
    119    MSG_DL_X10NAMES  = string.char(0x30, 0x0B, 0x10, 0x00), 
    120    MSG_DL_ZONENAMES = string.char(0x40, 0x0B, 0x1E, 0x00), 
    121    MSG_DL_EVENTLOG  = string.char(0xDF, 0x04, 0x28, 0x03) 
     121   MSG_DL_FORCECODE  = string.char(0x11, 0x01, 0x02, 0x00), 
     122   MSG_DL_PHONENRS   = string.char(0x36, 0x01, 0x20, 0x00), 
     123   MSG_DL_PINCODES   = string.char(0xFA, 0x01, 0x10, 0x00), 
     124   MSG_DL_PGMX10     = string.char(0x14, 0x02, 0xD5, 0x00), 
     125   MSG_DL_PANELFW    = string.char(0x00, 0x04, 0x20, 0x00), 
     126   MSG_DL_SERIAL     = string.char(0x30, 0x04, 0x08, 0x00), 
     127   MSG_DL_ZONES      = string.char(0x00, 0x09, 0x78, 0x00), 
     128   MSG_DL_KEYFOBS    = string.char(0x78, 0x09, 0x40, 0x00), 
     129   MSG_DL_SIRENS     = string.char(0x60, 0x0A, 0x08, 0x00), 
     130   MSG_DL_X10NAMES   = string.char(0x30, 0x0B, 0x10, 0x00), 
     131   MSG_DL_ZONENAMES  = string.char(0x40, 0x0B, 0x1E, 0x00), 
     132   MSG_DL_EVENTLOG   = string.char(0xDF, 0x04, 0x28, 0x03), 
     133   MSG_DL_ZONESTR    = string.char(0x00, 0x19, 0x00, 0x02), 
     134   MSL_DL_ZONECUSTOM = string.char(0xA0, 0x1A, 0x50, 0x00), 
     135   MSG_DL_ALL        = string.char(0x00, 0x00, 0x00, 0xFF) 
    122136} 
    123137 
     
    137151} 
    138152 
    139 local pmLogEvent_t = { 
     153local pmLogEvent_t = {  
     154   EN = { 
    140155   "None", "Interior Alarm", "Perimeter Alarm", "Delay Alarm", "24h Silent Alarm", "24h Audible Alarm", 
    141156   "Tamper", "Control Panel Tamper", "Tamper Alarm", "Tamper Alarm", "Communication Loss", "Panic From Keyfob", 
     
    159174   "Freeze Alert Restore", "Human Cold Alert", "Human Cold Alert Restore", "Human Hot Alert", 
    160175   "Human Hot Alert Restore", "Temperature Sensor Trouble", "Temperature Sensor Trouble Restore" 
    161 } 
     176}, NL = { 
     177   "Geen", "In alarm", "In alarm", "In alarm", "In alarm", "In alarm",  
     178   "Sabotage alarm", "Systeem sabotage", "Sabotage alarm", "Add user", "Communicate fout", "Paniekalarm", 
     179   "Code bedieningspaneel paniek", "Dwang", "Bevestig alarm", "Successful U/L", "Probleem herstel", 
     180   "Herstel", "Herstel", "Herstel", "Herstel", "Herstel",  
     181   "Sabotage herstel", "Systeem sabotage herstel", "Sabotage herstel", "Sabotage herstel", "Communicatie herstel", 
     182   "Stop alarm", "Algemeen herstel", "Brand probleem herstel", "Systeem inactief", "Recent close", "Brand", "Brand herstel", 
     183   "Niet actief", "Noodoproep", "Remove user", "Controleer code", "Bevestig alarm", "Supervisie",  
     184   "Supervisie herstel", "Batterij laag", "Batterij OK", "230VAC uitval", "230VAC herstel", 
     185   "Controlepaneel batterij laag", "Controlepaneel batterij OK", "Radio jamming", "Radio herstel", 
     186   "Communicatie mislukt", "Communicatie hersteld", "Telefoonlijn fout", "Telefoonlijn herstel",  
     187   "Automatische test", "Zekeringsfout", "Zekering herstel", "Batterij laag", "Batterij OK", "Monteur reset", 
     188   "Accu vermist", "Batterij laag", "Batterij OK", "Supervisie",  
     189   "Supervisie herstel", "Lage batterij bevestiging", "Reinigen", "Probleem", "Batterij laag", "Batterij OK", 
     190   "230VAC uitval", "230VAC herstel", "Supervisie", "Supervisie herstel", "Gas alarm", "Gas herstel", 
     191   "Gas probleem", "Gas probleem herstel", "Lekkage alarm", "Lekkage herstel", "Probleem", "Probleem herstel",  
     192   "Deelschakeling", "Ingeschakeld", "Snel deelschakeling", "Snel ingeschakeld", "Uitgezet", "Inschakelfout (auto)", "Test gestart", 
     193   "Test gestopt", "Force aan", "Geheel in (auto)", "Onmiddelijk", "Overbruggen", "Inschakelfout", 
     194   "Log verzenden", "Systeem reset", "Installateur programmeert", "Foutieve code", "Overbruggen" 
     195}} 
    162196 
    163197local pmLogUser_t = { 
     
    173207} 
    174208 
    175 local pmSysStatus_t = { 
     209local pmSysStatus_t = { EN = { 
    176210   "Disarmed", "Home Exit Delay", "Away Exit Delay", "Entry Delay", "Armed Home", "Armed Away", "User Test", 
    177211   "Downloading", "Programming", "Installer", "Home Bypass", "Away Bypass", "Ready", "Not Ready", "??", "??",  
    178212   "Disarmed Instant", "Home Exit Delay Instant", "Away Exit Delay Instant", "Entry Delay Instant", "Armed Home Instant",  
    179213   "Armed Away Instant" 
    180 } 
    181  
    182 local pmSysStatusFlags_t = { 
     214}} 
     215 
     216local pmSysStatusFlags_t = {  
     217   EN = { 
    183218   "Ready", "Alert in memory", "Trouble", "Bypass on", "Last 10 seconds", "Zone event", "Status changed", "Alarm event" 
    184 } 
     219}, NL = { 
     220   "Klaar", "Alarm in geheugen", "Probleem", "Overbruggen aan", "Laatste 10 seconden", "Zone verstoord", "Status gewijzigd", 
     221   "Alarm actief" 
     222}} 
    185223 
    186224local pmArmed_t = { 
     
    199237} -- Not used: Night, NightInstant, Vacation 
    200238 
    201 local pmEventType_t = { 
    202    "None", "Tamper Alarm", "Tamper Restore", "Open", "Closed", "Violated (Motion)", "Panic Alarm", "RF Jamming", "Tamper Open", 
    203    "Communication Failure", "Line Failure", "Fuse", "Not Active", "Low Battery", "AC Failure", "Fire Alarm", "Emergency", 
    204    "Siren Tamper", "Siren Tamper Restore", "Siren Low Battery", "Siren AC Fail" 
    205 } 
     239local pmEventType_t = {  
     240   EN = { 
     241   "None", "Tamper Alarm", "Tamper Restore", "Open", "Closed", "Violated (Motion)", "Panic Alarm", "RF Jamming", 
     242   "Tamper Open", "Communication Failure", "Line Failure", "Fuse", "Not Active", "Low Battery", "AC Failure",  
     243   "Fire Alarm", "Emergency", "Siren Tamper", "Siren Tamper Restore", "Siren Low Battery", "Siren AC Fail" 
     244}, NL = { 
     245   "Geen", "Sabotage alarm", "Sabotage herstel", "Open", "Gesloten", "Verstoord (beweging)", "Paniek alarm", "RF verstoring", 
     246   "Sabotage open", "Communicatie probleem", "Lijnfout", "Zekering", "Niet actief", "Lage batterij", "AC probleem", 
     247   "Brandalarm", "Noodoproep", "Sirene sabotage", "Sirene sabotage herstel", "Sirene lage batterij", "Sirene AC probleem" 
     248}} 
    206249 
    207250local pmPanelAlarmType_t = { 
     
    218261 
    219262local pmPanelType_t = { 
    220    [0] = "PowerMax", [1] = "PowerMax+", [2] = "PowerMax Pro", [3] = "PowerMax Complete", [4] = "PowerMax Pro Part", 
    221    [5] = "PowerMax Complete Part", [6] = "PowerMax Express", [7] = "PowerMaster10", [8] = "PowerMaster30" 
     263   [1] = "PowerMax", [2] = "PowerMax+", [3] = "PowerMax Pro", [4] = "PowerMax Complete", [5] = "PowerMax Pro Part", 
     264   [6] = "PowerMax Complete Part", [7] = "PowerMax Express", [8] = "PowerMaster10", [9] = "PowerMaster30" 
    222265} 
    223     
     266 
     267-- Config for each panel type (1-9) 
     268local pmPanelConfig_t = { 
     269   CFG_PARTITIONS = {   0,   0,   0,   0,   3,   3,   0,   3,   3 }, 
     270   CFG_EVENTS     = { 250, 250, 250, 250, 250, 250, 250, 250,1000 }, 
     271   CFG_KEYFOBS    = {   8,   8,   8,   8,   8,   8,   8,   8,  32 }, 
     272   CFG_1WKEYPADS  = {   8,   8,   8,   8,   8,   8,   8,   0,   0 }, 
     273   CFG_2WKEYPADS  = {   2,   2,   2,   2,   2,   2,   2,   8,  32 }, 
     274   CFG_SIRENS     = {   2,   2,   2,   2,   2,   2,   2,   4,   8 }, 
     275   CFG_USERCODES  = {   8,   8,   8,   8,   8,   8,   8,   8,  48 }, 
     276   CFG_PROXTAGS   = {   0,   0,   8,   0,   8,   8,   0,   8,  32 }, 
     277   CFG_WIRELESS   = {  28,  28,  28,  28,  28,  28,  28,  30,  64 }, 
     278   CFG_WIRED      = {   2,   2,   2,   2,   2,   2,   1,   1,   2 }, 
     279   CFG_ZONECUSTOM = {   0,   5,   5,   5,   5,   5,   5,   5,   5 } 
     280} 
     281 
    224282local pmPanelName_t = { 
    225283   ["0000"] = "PowerMax", ["0001"] = "PowerMax LT", ["0004"] = "PowerMax A", ["0005"] = "PowerMax 108", ["0006"] = "PowerMax LT 108", 
     
    275333 
    276334local pmZoneType_t = {  
    277    "Non-Alarm", "Emergency", "Flood", "Gas", "Delay 1", "Delay 2", "Interior-Follow", "Perimeter", "Perimeter-Follow",  
    278    "24 Hours Silent", "24 Hours Audible", "Fire", "Interior" 
    279 } 
    280  
     335   EN = { 
     336   "Non-Alarm", "Emergency", "Flood", "Gas", "Delay 1", "Delay 2", "Interior-Follow", "Perimeter", 
     337   "Perimeter-Follow", "24 Hours Silent", "24 Hours Audible", "Fire", "Interior" 
     338}, NL = { 
     339   "Geen alarm", "Noodtoestand", "Water", "Gas", "Vertraagd 1", "Vertraagd 2", "Interieur volg", "Omtrek", 
     340   "Omtrek volg", "24 uurs stil", "24 uurs luid", "Brand", "Interieur" 
     341}} 
     342 
     343-- Zone names are taken from the panel, so no langauage support needed 
    281344local pmZoneName_t = { 
    282345   "Attic", "Back door", "Basement", "Bathroom", "Bedroom", "Child room", "Closet", "Den", "Dining room", "Downstairs",  
    283346   "Emergency", "Fire", "Front door", "Garage", "Garage door", "Guest room", "Hall", "Kitchen", "Laundry room", "Living room",  
    284    "Master bathroom", "Master bedroom", "Office", "Upstairs", "Utility room", "Yard", "Custom1", "Custom2", "Custom3", "Custom4",  
    285    "Custom5", "Not Installed" 
     347   "Master bathroom", "Master bedroom", "Office", "Upstairs", "Utility room", "Yard", "Custom 1", "Custom 2", "Custom 3", 
     348   "Custom4", "Custom 5", "Not Installed" 
    286349} 
    287350 
     
    290353} 
    291354 
    292 local pmZoneSensor_t = {  
    293    [0x3] = "PIR", [0x4] = "PIR", [0x5] = "Magnet", [0x6] = "Magnet", [0xA] = "Smoke" 
     355-- Note: names need to match to VAR_xxx 
     356local pmZoneSensor_t = { 
     357   [0x3] = "Motion", [0x4] = "Motion", [0x5] = "Magnet", [0x6] = "Magnet", [0xA] = "Smoke", [0xF] = "Siren" 
    294358} -- unknown to date: Control Panel, Push Button, Gas, Flood, Universal 
    295359 
    296 -- Control debug to /var/log/cmh/LuaUPnP.log (taken from GE Caddx Panel) 
    297 function debug(s) 
    298     if (LOG_DEBUG) then 
    299        luup.log("POWERMAX: " .. s) 
    300     end 
     360-- Create a system variable if it does not exist 
     361function createIfNeeded(sid, var, newVal, id) 
     362    local curVal = luup.variable_get(sid, var, id) 
     363    if (curVal == nil) then 
     364        luup.variable_set(sid, var, newVal, id) 
     365        return true 
     366    end 
     367    return false 
    301368end 
    302369 
     
    318385       end 
    319386    end 
     387end 
     388 
     389-- Control debug to /var/log/cmh/LuaUPnP.log (taken from GE Caddx Panel) 
     390function debug(s) 
     391    if (pmLogDebug) then 
     392       luup.log("POWERMAX: " .. s) 
     393    end 
     394end 
     395 
     396-- pmDumpSettings 
     397function pmDumpSettings() 
     398   if (pmLogDebug) then 
     399      local dumpfile = pmFilenameSettings 
     400      local outf = io.open(dumpfile , 'w') 
     401      debug("Dumping PowerMax settings to file") 
     402      outf:write(string.format("PowerMax settings on %s\n", os.date('%Y-%m-%d %H:%M:%S'))) 
     403      for i = 0, 0xFF do 
     404         if (pmSettings_t[i] ~= nil) then 
     405            for j = 0, 0x0F do 
     406               local s = "" 
     407               outf:write(string.format("%08X: ", i * 0x100 + j * 0x10)) 
     408               for k = 1, 0x10 do 
     409                  local byte = string.byte(pmSettings_t[i], j * 0x10 + k) 
     410                  outf:write(string.format(" %02X", byte)) 
     411                  s = (byte < 0x20 or (byte >= 0x80 and byte < 0xA0)) and (s .. ".") or (s .. string.char(byte)) 
     412               end 
     413               outf:write("  " .. s .. "\n") 
     414            end 
     415         end 
     416      end 
     417      outf:close() 
     418   end 
     419end 
     420 
     421-- PDUs can be logged in a file. We use this to discover new never before seen 
     422-- PowerMax messages we need to decode and make sense of in long evenings... 
     423function pmLogPdu(PDU, direction) 
     424   if (pmLogDebug) then 
     425      local logfile = pmFilenameLog 
     426      -- empty file if it reaches 250kb 
     427      local outf = io.open(logfile , 'a') 
     428      local filesize = outf:seek("end") 
     429      outf:close() 
     430      if (filesize > 250000) then 
     431         local outf = io.open(logfile , 'w') 
     432         outf:write('') 
     433         outf:close() 
     434      end 
     435 
     436      local outf = io.open(logfile, 'a') 
     437      outf:write(string.format("%s %s  %s %s\n", os.date('%Y-%m-%d %H:%M:%S'), os.time(), direction, PDU)) 
     438      outf:close() 
     439   end 
    320440end 
    321441 
     
    380500   pmPowerlinkMode = true 
    381501   luup.call_timer("pmCheckKeepAlive", 1, "1m", "", "") 
    382    pmSendMessage(pmSendMsg_t.MSG_DL, { item = pmDownloadItem_t.MSG_DL_FORCECODE })   -- Request the forced disarm code 
    383    pmSendMessage(pmSendMsg_t.MSG_DL, { item = pmDownloadItem_t.MSG_DL_PANELFW })     -- Request the panel FW 
    384    pmSendMessage(pmSendMsg_t.MSG_DL, { item = pmDownloadItem_t.MSG_DL_SERIAL })      -- Request panel serial nr. 
    385    pmSendMessage(pmSendMsg_t.MSG_DL, { item = pmDownloadItem_t.MSG_DL_PINCODES })    -- Request pin codes 
    386    pmSendMessage(pmSendMsg_t.MSG_DL, { item = pmDownloadItem_t.MSG_DL_ZONES })       -- Request zone info 
    387    pmSendMessage(pmSendMsg_t.MSG_DL, { item = pmDownloadItem_t.MSG_DL_ZONENAMES })   -- Request zone names 
    388    pmSendMessage(pmSendMsg_t.MSG_EXIT)       -- Exit download mode 
    389    luup.variable_set(PANEL_SID, "PowerlinkMode", "Powerlink", panel_device) 
    390 end 
    391  
    392 -- pmReadPanelSettings 
     502   pmSendMessage("MSG_DL", { item = pmDownloadItem_t.MSG_DL_PANELFW })     -- Request the panel FW 
     503   pmSendMessage("MSG_DL", { item = pmDownloadItem_t.MSG_DL_ZONESTR })     -- Read the names of the zones 
     504   pmSendMessage("MSG_START")      -- Start sending all relevant settings please 
     505   pmSendMessage("MSG_EXIT")       -- Exit download mode 
     506   luup.variable_set(PANEL_SID, "PowerlinkMode", "Powerlink", pmPanelDev) 
     507end 
     508 
     509-- pmReadPanelSettings: update settings after coming out of programming mode 
    393510function pmReadPanelSettings() 
    394    pmSendMessage(pmSendMsg_t.MSG_SER_TYPE) -- Request panel settings 
    395    pmSendMessage(pmSendMsg_t.MSG_DOWNLOAD) -- Send our download code to the Powermax 
    396    pmSendMessage(pmSendMsg_t.MSG_START)    -- Start sending please 
    397    pmSendMessage(pmSendMsg_t.MSG_EXIT)     -- Exit download mode 
     511   if (pmPowerlinkMode == true) then 
     512      pmSendMessage("MSG_DOWNLOAD") -- Send our download code to the Powermax 
     513      pmSendMessage("MSG_DL", { item = pmDownloadItem_t.MSG_DL_ZONESTR }) 
     514      pmSendMessage("MSG_START")    -- Start sending please 
     515      pmSendMessage("MSG_EXIT")     -- Exit download mode 
     516   end 
    398517end 
    399518 
     
    405524        -- Let Powermax know we are alive (and reset Powerlink communication error) 
    406525        debug("Clear Powerlink communication error") 
    407       pmSendMessage(pmSendMsg_t.MSG_RESTORE) 
     526      pmSendMessage("MSG_RESTORE") 
     527      pmPowerlinkRetry = pmPowerlinkRetry + 1 
    408528      if (pmPowerlinkRetry == PL_RETRIES) then 
    409529         pmPowerlinkMode = false 
    410          luup.variable_set(PANEL_SID, "PowerlinkMode", "Standard", panel_device) 
     530         luup.variable_set(PANEL_SID, "PowerlinkMode", "Standard", pmPanelDev) 
    411531         return 
    412       else 
    413          pmPowerlinkRetry = pmPowerlinkRetry + 1 
    414532      end 
    415533    end 
     
    423541     for i = 1, 30 do 
    424542        local s = string.format("Z%02d", i) 
    425         local child = findChild(panel_device, s)     
     543        local child = findChild(pmPanelDev, s)     
    426544        if (child == nil) then 
    427545            -- debug("no zone " .. s) 
     
    439557end 
    440558 
    441 -- Initialises the panel and creates necessary child devices 
     559-- pmCreateDevice 
     560function pmCreateDevice(deviceList, altid, deviceName, zoneName, zoneType) 
     561   local devVar = "VAR_" .. string.upper(deviceName) 
     562   local devInit = pmDevices_t[devVar] 
     563 
     564   assert(devInit ~= nil) 
     565   local setVars = devInit[4] 
     566   if (type(altid) == "number") then 
     567      altid = string.format("%s-%d", deviceName, altid) 
     568   elseif (string.sub(altid, 1, 1) == "Z") then 
     569      local wirelessCnt = pmPanelConfig_t.CFG_WIRELESS[pmPanelTypeNr] 
     570      local nr = tonumber(string.sub(altid, 2)) 
     571      if (nr > wirelessCnt) then 
     572         setVars = CON_SENSOR_INIT -- wired zone 
     573      end 
     574   end 
     575   if (zoneName == nil) or (zoneName == "") then 
     576      zoneName = altid 
     577   end 
     578   if (zoneType ~= nil) and (zoneType ~= "") then 
     579      zoneName = zoneName .. " (" .. zoneType .. ")" 
     580   else 
     581      zoneName = zoneName .. " (PowerMax)" 
     582   end 
     583   pmMessage("Adding device " .. altid .. " (" .. zoneName .. ")", 4) 
     584   luup.chdev.append(pmPanelDev, deviceList, altid, zoneName, devInit[1], devInit[2], devInit[3], setVars, devInit[5]) 
     585end 
     586 
     587-- Initialises the panel, handlers etc. 
    442588function pmStartup(lul_device) 
    443     panel_device = lul_device 
    444  
    445     luup.log("POWERMAX: starting ... device #"..tostring(panel_device), 10) 
    446  
    447     updateIfNeeded(PANEL_SID, "PowermaxVersion", POWERMAX_VERSION, panel_device) 
    448    updateIfNeeded(PANEL_SID, "PowerlinkMode", "Unknown", panel_device) 
    449  
    450     if (luup.io.is_connected(panel_device) == false) then 
     589    pmPanelDev = lul_device 
     590 
     591    luup.log("POWERMAX: starting ... device #" .. tostring(pmPanelDev), 10) 
     592 
     593    updateIfNeeded(PANEL_SID, "PluginVersion", PLUGIN_VERSION, pmPanelDev) 
     594    createIfNeeded(PANEL_SID, "PluginDebug", "0", pmPanelDev) 
     595   createIfNeeded(PANEL_SID, "AutoCreate", "1", pmPanelDev) 
     596   createIfNeeded(PANEL_SID, "DoorZones", "", pmPanelDev) 
     597   createIfNeeded(PANEL_SID, "MotionZones", "", pmPanelDev) 
     598   createIfNeeded(PANEL_SID, "SmokeZones", "", pmPanelDev) 
     599   createIfNeeded(PANEL_SID, "Devices", "", pmPanelDev) 
     600   updateIfNeeded(PANEL_SID, "PowerlinkMode", "Unknown", pmPanelDev) 
     601   createIfNeeded(PANEL_SID, "PanelStatusCode", "", pmPanelDev) 
     602   createIfNeeded(PANEL_SID, "PanelStatusData", "", pmPanelDev) 
     603   createIfNeeded(PANEL_SID, "PanelAlarmType", "", pmPanelDev) 
     604   createIfNeeded(PANEL_SID, "PanelTroubleType", "", pmPanelDev) 
     605 
     606   pmLogDebug = (luup.variable_get(PANEL_SID, "PluginDebug", pmPanelDev) ~= "0") 
     607    
     608    if (luup.io.is_connected(pmPanelDev) == false) then 
    451609        pmMessage("Please select the Serial Port for the PowerMax", 2) 
    452610        return false 
     
    466624   pmReceiveMsg_t[0xAB][2] = pmHandlePowerlink 
    467625    
    468     child_devices = luup.chdev.start(panel_device) 
    469  
    470     pmMessage("Adding partition device", 4) 
    471    local devInit = pmDevices_t.VAR_ALARM 
    472     luup.chdev.append(panel_device, child_devices, "Partition-1", "Partition-1 (Powermax)", devInit[1], devInit[2], devInit[3], devInit[4], devInit[5]) 
    473     partition_device = findChild(panel_device, "Partition-1") 
    474  
    475     pmMessage("Adding zone devices", 4) 
    476  
    477     local doorZones = luup.variable_get(PANEL_SID, "DoorZones", panel_device)  
    478     if (doorZones == nil) then 
    479        luup.variable_set(PANEL_SID, "DoorZones", "", panel_device) 
    480        doorZones = "" 
    481     end 
    482  
    483     local motionZones = luup.variable_get(PANEL_SID, "MotionZones", panel_device)  
    484     if (motionZones == nil) then 
    485        luup.variable_set(PANEL_SID, "MotionZones", "", panel_device) 
    486        motionZones = "" 
    487     end 
    488  
    489     local smokeZones = luup.variable_get(PANEL_SID, "SmokeZones", panel_device)  
    490     if (smokeZones == nil) then 
    491        luup.variable_set(PANEL_SID, "SmokeZones", "", panel_device) 
    492        smokeZones = "" 
    493     end 
    494  
    495     local x10Devices = luup.variable_get(PANEL_SID, "X10Devices", panel_device)  
    496     if (x10Devices == nil) then 
    497         luup.variable_set(PANEL_SID, "X10Devices", "", panel_device) 
    498         x10Devices = "" 
    499     end 
    500  
    501     local PGM = luup.variable_get(PANEL_SID, "PGM", panel_device) 
    502     if (PGM == "0" or PGM == "1") then 
    503         -- Do nothing, value is correct 
    504     elseif ((PGM == "true") or (PGM == "yes")) then 
    505         luup.variable_set(PANEL_SID, "PGM", "1", panel_device) 
    506         PGM = "1" 
    507     else 
    508         luup.variable_set(PANEL_SID, "PGM", "0", panel_device) 
    509         PGM = "0" 
    510     end 
    511  
    512     for i = 1, 30 do 
    513         s = string.format("Z%02d", i) 
    514       local setVars = devInit[4] 
    515       if (i > 28) then 
    516          setVars = CON_SENSOR_INIT -- zones 29 & 30 are wired 
    517       end 
    518         if (string.find(doorZones, s) ~= nil) then 
    519            debug("adding door zone " .. s) 
    520          local devInit = pmDevices_t.VAR_DOOR 
    521          luup.chdev.append(panel_device, child_devices, s, s.." (Powermax)", devInit[1], devInit[2], devInit[3], setVars, devInit[5]) 
    522         elseif (string.find (motionZones, s) ~= nil) then 
    523            debug("adding motion zone " .. s) 
    524          local devInit = pmDevices_t.VAR_MOTION 
    525          luup.chdev.append(panel_device, child_devices, s, s.." (Powermax)", devInit[1], devInit[2], devInit[3], setVars, devInit[5]) 
    526         elseif (string.find (smokeZones, s) ~= nil) then 
    527            debug("adding smoke zone " .. s) 
    528          local devInit = pmDevices_t.VAR_SMOKE 
    529          luup.chdev.append(panel_device, child_devices, s, s.." (Powermax)", devInit[1], devInit[2], devInit[3], setVars, devInit[5]) 
    530         end 
    531     end 
    532      
    533     for i = 0, 15 do 
    534         if (i == 0) then 
    535             s = "PGM" 
    536             if (PGM == "1") then 
    537                 debug("adding PGM output") 
    538             local devInit = pmDevices_t.VAR_SWITCH 
    539             luup.chdev.append(panel_device, child_devices, s, s.." (Powermax)", devInit[1], devInit[2], devInit[3], devInit[4], devInit[5]) 
    540             end 
    541         else 
    542             s = string.format("X%02d", i) 
    543          local pos = string.find(x10Devices, s) 
    544             if (pos ~= nil) then 
    545             debug("adding x10 device " .. s) 
    546             local devInit 
    547             if (string.sub(x10Devices, pos + 3, pos + 3) == "d") then 
    548                devInit = pmDevices_t.VAR_DIM 
    549             else 
    550                devInit = pmDevices_t.VAR_SWITCH 
    551             end 
    552             luup.chdev.append(panel_device, child_devices, s, s.." (Powermax)", devInit[1], devInit[2], devInit[3], devInit[4], devInit[5]) 
    553             end 
    554         end 
    555         local x10_device = findChild(panel_device, s) 
    556         if (x10_device ~= nil) then 
    557             local x10_dev_nr = luup.variable_get(PANEL_SID, "X10DeviceNr", x10_device) 
    558             if (x10_dev_nr == nil) then 
    559                 x10_dev_nr = string.format("%04x", 2 ^ i) 
    560                 luup.variable_set(PANEL_SID, "X10DeviceNr", x10_dev_nr, x10_device) 
    561             end 
    562         end 
    563     end 
    564  
    565     luup.chdev.sync(panel_device,child_devices) 
    566626    luup.call_timer("pmMotionOff", 1, "1m", "", "") 
    567     pmSendMessage(pmSendMsg_t.MSG_INIT) 
    568    pmSendMessage(pmSendMsg_t.MSG_STATUS) 
    569    --pmReadPanelSettings() 
    570     pmSendMessage(pmSendMsg_t.MSG_DOWNLOAD) -- Send our download code to the Powermax 
     627    pmSendMessage("MSG_INIT") 
     628    pmSendMessage("MSG_INIT")     -- Two times, in case in wrong state 
     629   pmSendMessage("MSG_RESTORE")  -- Also sends status 
     630   pmSendMessage("MSG_SER_TYPE") -- Request panel type & serial (without being in download mode) 
     631    pmSendMessage("MSG_DOWNLOAD") -- Send our download code to the Powermax 
    571632    -- If we get a NACK, then the download code is not valid (not enrolled) 
    572633end 
    573634 
    574 -- Convert a PIN given as string in the PIN PDU format as used in messages to powermax 
     635-- pmGetPin: Convert a PIN given as string in the PIN PDU format as used in messages to powermax 
    575636function pmGetPin(pin)  
    576637    local pinPDU 
     
    589650    return pinPDU 
    590651end 
    591    
    592  -- PDUs are logged in a file. We use this to discover new never before seen 
    593  -- PowerMax messages we need to decode and make sense of in long evenings 
    594  function pmLogPdu(PDU, direction) 
    595     local logfile = '/var/log/cmh/powermax_pdu.txt' 
    596     -- empty file if it reaches 250kb 
    597     local outf = io.open(logfile , 'a') 
    598     local filesize = outf:seek("end") 
    599     outf:close() 
    600     if (filesize > 250000) then 
    601        local outf = io.open(logfile , 'w') 
    602        outf:write('') 
    603        outf:close() 
    604     end 
    605  
    606     local outf = io.open(logfile, 'a') 
    607     outf:write(string.format("%s %s  %s %s\n", os.date('%Y-%m-%d %H:%M:%S'), os.time(), direction, PDU)) 
    608     outf:close() 
     652 
     653-- pmWriteSettings: add a certain setting to the settings table 
     654function pmWriteSettings(page, index, setting) 
     655   local len = string.len(setting) + 1 
     656   local wrap = (index + len - 0x100) 
     657   local sett = { "", "" } 
     658   local i 
     659   assert(len <= 0xB1) 
     660   if (wrap > 0) then 
     661      sett[1] = string.sub(setting, 1, len - wrap) 
     662      sett[2] = string.sub(setting, len - wrap + 1, len) 
     663      wrap = 1 
     664   else 
     665      sett[1] = setting 
     666      wrap = 0 
     667   end 
     668   for i = 0, wrap do 
     669      if (pmSettings_t[page + i] == nil) then 
     670         pmSettings_t[page + i] = string.rep(string.char(0xFF), 0x100) 
     671      end 
     672      len = string.len(sett[i + 1]) + 1 
     673      if (i == 1) then  
     674         index = 0  
     675      end 
     676      pmSettings_t[page + i] = string.sub(pmSettings_t[page + i], 1, index) .. sett[i + 1] .. string.sub(pmSettings_t[page + i], index + len, 0x100) 
     677   end 
     678end 
     679 
     680-- pmReadSettings 
     681function pmReadSettings(item) 
     682   local index = string.byte(item, 1) + 1 
     683   local page = string.byte(item, 2) 
     684   local len = string.byte(item, 3) + 0x100 * string.byte(item, 4) 
     685   local s = "" 
     686   if (pmSettings_t[page] ~= nil) then 
     687      while (len > 0) do 
     688         local str = string.sub(pmSettings_t[page], index, index + len) 
     689         s = s .. str 
     690         page = page + 1 
     691         index = 1 
     692         len = len - string.len(str) 
     693      end 
     694   end 
     695   return s 
     696end 
     697 
     698-- pmProcessSetting 
     699function pmProcessSettings() 
     700   local i, setting 
     701   local zone_t = {} 
     702   local x10_t = {} 
     703   local doorZoneStr = "" 
     704   local motionZoneStr = "" 
     705   local smokeZoneStr = "" 
     706   local deviceStr = "" 
     707   local childDevices = luup.chdev.start(pmPanelDev) 
     708   local autoCreate = (luup.variable_get(PANEL_SID, "AutoCreate", pmPanelDev) == "1") 
     709 
     710   local model = pmPanelType_t[pmPanelTypeNr] 
     711   updateIfNeeded(PANEL_SID, "PanelType", model, pmPanelDev) 
     712    
     713   local zoneCnt = pmPanelConfig_t.CFG_WIRELESS[pmPanelTypeNr] + pmPanelConfig_t.CFG_WIRED[pmPanelTypeNr] 
     714   local customCnt = pmPanelConfig_t.CFG_ZONECUSTOM[pmPanelTypeNr] 
     715   local userCnt = pmPanelConfig_t.CFG_USERCODES[pmPanelTypeNr] 
     716   local partitionCnt = pmPanelConfig_t.CFG_PARTITIONS[pmPanelTypeNr] 
     717   local sirenCnt = pmPanelConfig_t.CFG_SIRENS[pmPanelTypeNr] 
     718   local keypad1wCnt = pmPanelConfig_t.CFG_1WKEYPADS[pmPanelTypeNr] 
     719   local keypad2wCnt = pmPanelConfig_t.CFG_2WKEYPADS[pmPanelTypeNr] 
     720 
     721   pmDumpSettings() 
     722    
     723   for i = 1, 1 do -- TODO: '1 + partitionCnt' for multiple partition support 
     724      pmCreateDevice(childDevices, i, "Partition") 
     725      local s = string.format("Partition-%d", i) 
     726      pmPartitionDev_t[i] = findChild(pmPanelDev, s) 
     727   end 
     728    
     729    pmMessage("Adding zone devices", 4) 
     730   if (pmPowerlinkMode == true) then 
     731      -- Process zone names of this panel 
     732      setting = pmReadSettings(pmDownloadItem_t.MSG_DL_ZONESTR) 
     733      for i = 1, 26 + customCnt do 
     734         local s = string.sub(setting, (i - 1) * 0x10 + 1, i * 0x10):gsub("%s*$", "") 
     735         if (string.byte(s, 1) ~= 0xFF) then 
     736            pmZoneName_t[i] = string.sub(s, 1, 1) .. string.lower(string.sub(s, 2, 0x0F)) 
     737         end 
     738      end 
     739    
     740      -- PowerLink mode 
     741      debug("Processing forced disarm code") 
     742      pmForcedDisarmCode = pmReadSettings(pmDownloadItem_t.MSG_DL_FORCECODE) 
     743 
     744      debug("Processing pin codes") 
     745      setting = pmReadSettings(pmDownloadItem_t.MSG_DL_PINCODES) 
     746      for i = 1, userCnt do 
     747         pmPincode_t[i] = string.sub(setting, 2 * i - 1, 2 * i) 
     748      end 
     749 
     750      debug("Processing software version") 
     751      setting = pmReadSettings(pmDownloadItem_t.MSG_DL_PANELFW) 
     752      local panelEprom = string.sub(setting, 0x01, 0x10) 
     753      local panelSoftware = string.sub(setting, 0x11, 0x20) 
     754      debug(string.format("EPROM: %s; SW: %s", panelEprom, panelSoftware)) 
     755      updateIfNeeded(PANEL_SID, "PanelEprom", panelEprom, pmPanelDev) 
     756      updateIfNeeded(PANEL_SID, "PanelSoftware", panelSoftware, pmPanelDev) 
     757 
     758      debug("Processing serial and panel type") 
     759      setting = pmReadSettings(pmDownloadItem_t.MSG_DL_SERIAL) 
     760      local idx = string.format("%02x%02x", string.byte(setting, 8), string.byte(setting, 7)) 
     761      local pmPanelName = pmPanelName_t[idx] or "UNKNOWN" 
     762      local pmPanelSerial = "" 
     763      for i = 0, 5 do 
     764         local nr = string.byte(setting, i + 1) 
     765         local s = (nr == 0xFF) and "." or string.format("%02X", nr) 
     766         pmPanelSerial = pmPanelSerial .. s 
     767      end 
     768      debug(string.format("Panel %s with serial %s", pmPanelName, pmPanelSerial)) 
     769      updateIfNeeded(PANEL_SID, "PanelName", pmPanelName, pmPanelDev) 
     770      updateIfNeeded(PANEL_SID, "PanelSerial", pmPanelSerial, pmPanelDev) 
     771 
     772      debug("Processing zone information") 
     773      setting = pmReadSettings(pmDownloadItem_t.MSG_DL_ZONES) 
     774      local zoneNames = pmReadSettings(pmDownloadItem_t.MSG_DL_ZONENAMES) 
     775      for i = 1, zoneCnt do 
     776         local zoneEnrolled = (string.sub(setting, i * 4 - 3, i * 4 - 1) ~= string.char(0, 0, 0)) 
     777         local zoneName = pmZoneName_t[string.byte(zoneNames, i) + 1] 
     778         if (zoneEnrolled == true) then 
     779            local zoneInfo = string.byte(setting, i * 4) 
     780            local zoneType = pmZoneType_t[pmLang][bitw.band(zoneInfo, 0x0F) + 1] 
     781            local zoneChime = pmZoneChime_t[bitw.rshift(zoneInfo, 4) + 1] 
     782            local sensorType = string.byte(setting, i * 4 - 1) 
     783            local sensorName = pmZoneSensor_t[bitw.band(sensorType, 0xF)] or "UNKNOWN" 
     784            debug(string.format("Zone %02d: %s (%s; sensor type = %02X [%s])" , i, zoneType, zoneChime, sensorType, sensorName)) 
     785            zone_t[i] = { zoneName, zoneType } 
     786            if (sensorName == "Magnet") then 
     787               doorZoneStr = string.format("%s,Z%02d", doorZoneStr, i) 
     788            elseif (sensorName == "Motion") then 
     789               motionZoneStr = string.format("%s,Z%02d", motionZoneStr, i) 
     790            elseif (sensorName == "Smoke") then 
     791               smokeZoneStr = string.format("%s,Z%02d", smokeZoneStr, i) 
     792            end 
     793         end 
     794      end 
     795      debug("Processing PGM/X10 information") 
     796      setting = pmReadSettings(pmDownloadItem_t.MSG_DL_PGMX10) 
     797      local x10Names = pmReadSettings(pmDownloadItem_t.MSG_DL_X10NAMES) 
     798      for i = 0, 15 do 
     799         local enabled = false 
     800         local x10Name = 0x1F 
     801         for j = 0, 8 do 
     802            enabled = enabled or (string.byte(setting, 6 + i + j * 0x10) ~= 0) 
     803         end 
     804         if (i > 0) then 
     805            x10Name = string.byte(x10Names, i) 
     806            x10_t[i] = pmZoneName_t[x10Name + 1] 
     807         end 
     808         if (enabled == true) or (x10Name ~= 0x1F) then 
     809            if (i == 0) then 
     810               deviceStr = deviceStr .. ",PGM" 
     811            else 
     812               deviceStr = string.format("%s,X%02d", deviceStr, i) 
     813            end 
     814         end 
     815      end 
     816      -- TODO: automatic keypad detection 
     817      debug("Processing siren information") 
     818      setting = pmReadSettings(pmDownloadItem_t.MSG_DL_SIRENS) 
     819      for i = 1, sirenCnt do 
     820         local sirenEnrolled = (string.sub(setting, i * 4 - 3, i * 4 - 1) ~= string.char(0, 0, 0)) 
     821         if (sirenEnrolled == true) then 
     822            deviceStr = string.format("%s,S%02d", deviceStr, i) 
     823         end 
     824      end 
     825   end 
     826 
     827   -- Use variables if AutoCreate is false or not in Powerlink mode, otherwise use what we read in the settings 
     828   local doorZones = doorZoneStr 
     829   local motionZones = motionZoneStr 
     830   local smokeZones = smokeZoneStr 
     831   local devices = deviceStr 
     832   if (autoCreate == false) or (pmPowerlinkMode == false) then 
     833      doorZones = luup.variable_get(PANEL_SID, "DoorZones", pmPanelDev) 
     834      motionZones = luup.variable_get(PANEL_SID, "MotionZones", pmPanelDev) 
     835      smokeZones = luup.variable_get(PANEL_SID, "SmokeZones", pmPanelDev) 
     836      devices = luup.variable_get(PANEL_SID, "Devices", pmPanelDev) 
     837   elseif (pmPowerlinkMode == true) then 
     838      updateIfNeeded(PANEL_SID, "DoorZones", string.sub(doorZoneStr, 2), pmPanelDev) 
     839      updateIfNeeded(PANEL_SID, "MotionZones", string.sub(motionZoneStr, 2), pmPanelDev) 
     840      updateIfNeeded(PANEL_SID, "SmokeZones", string.sub(smokeZoneStr, 2), pmPanelDev) 
     841      updateIfNeeded(PANEL_SID, "Devices", string.sub(deviceStr, 2), pmPanelDev) 
     842   end 
     843    
     844   -- Start adding zones 
     845   local zoneName, zoneType 
     846   for i = 1, zoneCnt do 
     847      local s = string.format("Z%02d", i) 
     848      if (zone_t[i] ~= nil) then 
     849         zoneName = zone_t[i][1] 
     850         zoneType = zone_t[i][2] 
     851      else 
     852         zoneName = nil 
     853         zoneType = nil 
     854      end 
     855      if (string.find(doorZones, s) ~= nil) then 
     856         pmCreateDevice(childDevices, s, "Magnet", zoneName, zoneType) 
     857      elseif (string.find (motionZones, s) ~= nil) then 
     858         pmCreateDevice(childDevices, s, "Motion", zoneName, zoneType) 
     859      elseif (string.find (smokeZones, s) ~= nil) then 
     860         pmCreateDevice(childDevices, s, "Smoke", zoneName, zoneType) 
     861      end 
     862   end 
     863     
     864   -- Add PGM and X10 devices 
     865    for i = 0, 15 do 
     866        if (i == 0) then 
     867            if (string.find(devices, "PGM")) then 
     868            pmCreateDevice(childDevices, "PGM", "Switch", "PGM") 
     869            end 
     870        else 
     871            s = string.format("X%02d", i) 
     872         local pos = string.find(devices, s) 
     873            if (pos ~= nil) then 
     874            local devInit 
     875            if (string.sub(devices, pos + 3, pos + 3) == "d") then 
     876               pmCreateDevice(childDevices, s, "Dim", x10_t[i], s) 
     877            else 
     878               pmCreateDevice(childDevices, s, "Switch", x10_t[i], s) 
     879            end 
     880            end 
     881        end 
     882        local x10_device = findChild(pmPanelDev, s) 
     883        if (x10_device ~= nil) then 
     884         x10_dev_nr = string.format("%04x", 2 ^ i) 
     885         createIfNeeded(PANEL_SID, "X10DeviceNr", x10_dev_nr, x10_device) 
     886        end 
     887    end 
     888    
     889   -- Add sirens 
     890   for i = 1, sirenCnt do 
     891      local sirenEnrolled = (string.find(devices, string.format("S%02d", i)) ~= nil) 
     892      if (sirenEnrolled == true) then 
     893         pmCreateDevice(childDevices, i, "Siren") 
     894      end 
     895   end 
     896 
     897   -- Add keypads 
     898   for i = 1, keypad1wCnt do 
     899      local keypadEnrolled = (string.find(devices, string.format("K1%d", i)) ~= nil) 
     900      if (keypadEnrolled == true) then 
     901         pmCreateDevice(childDevices, i, "Keypad", nil, "1-way") 
     902      end 
     903   end 
     904    
     905   for i = 1, keypad2wCnt do 
     906      local keypadEnrolled = (string.find(devices, string.format("K2%d", i)) ~= nil) 
     907      if (keypadEnrolled == true) then 
     908         pmCreateDevice(childDevices, i, "Keypad", nil, "2-way") 
     909      end 
     910   end 
     911   luup.chdev.sync(pmPanelDev, childDevices) 
     912   pmMessage("Ready for use", 2) 
    609913end 
    610914 
     
    654958      timeout = true 
    655959   else 
     960      msg = pmSendMsg_t[msg] 
     961      assert(msg ~= nil) 
    656962      outPdu = string.char(0x0D) .. msg[1] .. string.char(0x00, 0x0A) 
    657963      response = (msg[2] == nil) and "" or string.char(msg[2]) 
     
    661967            outPdu = string.gsub(outPdu, fnd, rpl) 
    662968         end 
     969      end 
     970       
     971      if (string.byte(outPdu, 2) == 0x3E) then 
     972         local cnt = (string.byte(outPdu, 5)) + 0x100 * string.byte(outPdu, 6) 
     973         response = string.rep(response, math.floor(cnt / 0xB0) + 1) 
    663974      end 
    664975   end 
     
    7261037               pmExpectedResponse = string.sub(pmExpectedResponse, 2) 
    7271038               if (firstExpectedResponse ~= msgType) then 
    728                   debug(string.format("*** Re-sending PDU (expected %02X got %02X) ***", firstExpectedResponse, msgType)) 
    729                   if (pmSendMsgRetries == MSG_RETRIES) then 
    730                      pmExpectedResponse = "" 
    731                      pmSendMessage(pmSendMsg_t.MSG_RESTORE) 
     1039                  if (pmSendMsgRetries == 0) then 
     1040                     debug(string.format("*** Waiting for next PDU (expected %02X got %02X) ***", firstExpectedResponse, msgType)) 
     1041                     pmSendMsgRetries = 1 -- don't resend immediately - first wait for the next response 
     1042                     pmExpectedResponse = string.char(firstExpectedResponse) .. pmExpectedResponse 
    7321043                  else 
    733                      pmSendMsgRetries = pmSendMsgRetries + 1 
    734                      pmSendMessage(-1) 
     1044                     debug(string.format("*** Re-sending PDU (expected %02X got %02X) ***", firstExpectedResponse, msgType)) 
     1045                     if (pmSendMsgRetries == MSG_RETRIES) then 
     1046                        pmExpectedResponse = "" 
     1047                        pmSendMessage("MSG_INIT") 
     1048                     else 
     1049                        pmSendMsgRetries = pmSendMsgRetries + 1 
     1050                        pmSendMessage(-1) 
     1051                     end 
    7351052                  end 
    7361053               else 
     
    7411058         if (pmExpectedResponse == "") then 
    7421059            debug("*** Sending next PDU in queue ***") 
     1060            luup.sleep(10) 
    7431061            pmSendMessage(nil) 
    7441062            end 
     
    7531071-- pmHandleAck (0x02) 
    7541072function pmHandleAck() 
     1073   local lastSendMsgType = string.byte(pmLastSendMsg, 2) 
     1074 
     1075   if (lastSendMsgType == 0x0F) then 
     1076      pmProcessSettings() 
     1077   end 
    7551078   return true 
    7561079end 
     
    7591082function pmHandleTimeout() 
    7601083   debug("Powermax sends timeout") 
    761    pmExpectedResponse = "" 
    762    pmSendMessage(pmSendMsg_t.MSG_EXIT) 
     1084   pmExpectedResponse = string.char(0x06) 
     1085   pmSendMessage("MSG_EXIT") 
    7631086   return true 
    7641087end 
     
    7731096   elseif (lastSendMsgType == 0x24) then 
    7741097      pmPowerlinkMode = false 
    775       luup.variable_set(PANEL_SID, "PowerlinkMode", "Standard", panel_device) 
     1098      luup.variable_set(PANEL_SID, "PowerlinkMode", "Standard", pmPanelDev) 
    7761099      pmMessage("Vera PowerLink not enrolled.", 2) 
    7771100   end 
     
    7891112-- pmHandleSettings (0x33) 
    7901113function pmHandleSettings() 
    791    local row = string.byte(pmIncomingPdu, 3) 
     1114   local index = string.byte(pmIncomingPdu, 3) 
    7921115   local page = string.byte(pmIncomingPdu, 4) 
    7931116   local lastSendMsgType = string.byte(pmLastSendMsg, 2) 
    794    debug(string.format("Received Powermax setting page %02X row %02X", page, row)) 
     1117   debug(string.format("Received Powermax setting page %02X index %02X", page, index)) 
    7951118   if (lastSendMsgType == 0x0A) then 
    7961119      pmExpectedResponse = pmExpectedResponse .. string.char(0x33) -- we're not done; keep them coming 
    7971120   end 
    798    pmSettings_t[page] = pmSettings_t[page] .. string.sub(pmIncomingPdu, 5, 12) 
     1121   pmWriteSettings(page, index, string.sub(pmIncomingPdu, 5, 12)) 
    7991122   return true 
    8001123end 
     
    8021125-- pmHandleInfo (0x3C) 
    8031126function pmHandleInfo() 
    804    local id2 = string.byte(pmIncomingPdu, 8) 
    805    local model = pmPanelType_t[id2] 
    806    updateIfNeeded(PANEL_SID, "PanelType", model, panel_device) 
     1127   pmPanelTypeNr = string.byte(pmIncomingPdu, 8) + 1 
     1128   local model = pmPanelType_t[pmPanelTypeNr] 
     1129   updateIfNeeded(PANEL_SID, "PanelType", model, pmPanelDev) 
     1130   -- TODO: update DL messages (length) based on panel type 
    8071131   pmPowerlinkEnrolled() 
    8081132   return true 
     
    8131137   local idx = string.byte(pmIncomingPdu, 3) 
    8141138   local page = string.byte(pmIncomingPdu, 4) 
    815    local idxPage = string.sub(pmIncomingPdu, 3, 4) 
    8161139   local len = string.byte(pmIncomingPdu, 5) 
    817    local i 
    8181140   debug(string.format("Received Powermax setting page %02X index %02X", page, idx)) 
    819    if (idxPage == string.sub(pmDownloadItem_t.MSG_DL_FORCECODE, 1, 2)) then 
    820       debug("Reading forced disarm code") 
    821       pmForcedDisarmCode = string.sub(pmIncomingPdu, 6, 7) 
    822    elseif (idxPage == string.sub(pmDownloadItem_t.MSG_DL_PINCODES, 1, 2)) then 
    823       debug("Reading pin codes") 
    824       for i = 1, 8 do 
    825          pmPincode_t[i] = string.sub(pmIncomingPdu, 4 + 2 * i, 5 + 2 * i) 
    826       end 
    827    elseif (idxPage == string.sub(pmDownloadItem_t.MSG_DL_PANELFW, 1, 2)) then 
    828       debug("Reading software version") 
    829       local panelEprom = string.sub(pmIncomingPdu, 0x06, 0x15) 
    830       local panelSoftware = string.sub(pmIncomingPdu, 0x16, 0x25) 
    831       debug(string.format("EPROM: %s; SW: %s", panelEprom, panelSoftware)) 
    832       updateIfNeeded(PANEL_SID, "PanelEprom", panelEprom, panel_device) 
    833       updateIfNeeded(PANEL_SID, "PanelSoftware", panelSoftware, panel_device) 
    834    elseif (idxPage == string.sub(pmDownloadItem_t.MSG_DL_SERIAL, 1, 2)) then 
    835       debug("Reading serial and panel type") 
    836       local idx = string.format("%02x%02x", string.byte(pmIncomingPdu, 0x0D), string.byte(pmIncomingPdu, 0x0C)) 
    837       pmPanelName = pmPanelName_t[idx] or "UNKNOWN" 
    838       pmPanelSerial = "" 
    839       for i = 0, 5 do 
    840          local nr = string.byte(pmIncomingPdu, 0x06 + i) 
    841          local s = (nr == 0xFF) and "." or string.format("%02X", nr) 
    842          pmPanelSerial = pmPanelSerial .. s 
    843       end 
    844       debug(string.format("Panel %s with serial %s", pmPanelName, pmPanelSerial)) 
    845       updateIfNeeded(PANEL_SID, "PanelName", pmPanelName, panel_device) 
    846       updateIfNeeded(PANEL_SID, "PanelSerial", pmPanelSerial, panel_device) 
    847    elseif (idxPage == string.sub(pmDownloadItem_t.MSG_DL_ZONES, 1, 2)) then 
    848       debug("Reading zone information") 
    849       for i = 1, 30 do 
    850          local zoneEnrolled = (string.sub(pmIncomingPdu, 2 + i * 4, 4 + i * 4) ~= string.char(0, 0, 0)) 
    851          if (zoneEnrolled == true) then 
    852             local zoneInfo = string.byte(pmIncomingPdu, 5 + i * 4) 
    853             local zoneType = pmZoneType_t[bitw.band(zoneInfo, 0x0f) + 1] 
    854             local zoneChime = pmZoneChime_t[bitw.rshift(zoneInfo, 4) + 1] 
    855             local sensorType = string.byte(pmIncomingPdu, 4 + i * 4) 
    856             local sensorName = pmZoneSensor_t[bitw.band(sensorType, 0xF)] or "UNKNOWN" 
    857             debug(string.format("Zone %02d: %s (%s; sensor type = %02X [%s])" , i, zoneType, zoneChime, sensorType, sensorName)) 
    858          end 
    859       end 
    860    elseif (idxPage == string.sub(pmDownloadItem_t.MSG_DL_ZONENAMES, 1, 2)) then 
    861       debug("Reading zone name information") 
    862       for i = 1, 30 do 
    863          local zoneName = pmZoneName_t[string.byte(pmIncomingPdu, 5 + i) + 1] 
    864          debug(string.format("Zone %02d: %s" , i, zoneName)) 
    865       end 
    866    end 
     1141   pmWriteSettings(page, idx, string.sub(pmIncomingPdu, 6, 5 + len)) 
    8671142end 
    8681143 
     
    8851160      local eventType = string.byte(pmIncomingPdu, 12)                 
    8861161      pmEventLog = pmEventLog .. string.format("\r\n%02d/%02d/%d %02d:%02d:%02d    ", dayNum, monNum, yerNum, horNum, minNum, secNum) 
    887       pmEventLog = pmEventLog .. (pmLogUser_t[eventZone+1] or "UNKNOWN") 
     1162      pmEventLog = pmEventLog .. (pmLogUser_t[eventZone + 1] or "UNKNOWN") 
    8881163      pmEventLog = pmEventLog .. "    "  
    889       pmEventLog = pmEventLog .. (pmLogEvent_t[eventType+1] or "UNKNOWN") 
     1164      pmEventLog = pmEventLog .. (pmLogEvent_t[pmLang][eventType + 1] or "UNKNOWN") 
    8901165      if (eventNum == pmEventCnt) then 
    891          luup.variable_set(PANEL_SID, "EventLog", pmEventLog, panel_device) 
     1166         luup.variable_set(PANEL_SID, "EventLog", pmEventLog, pmPanelDev) 
    8921167      else 
    8931168         pmExpectedResponse = pmExpectedResponse .. string.char(0xA0) 
     
    9031178   if (msgTot > 0) and (eventType ~= msgTot) then 
    9041179      pmExpectedResponse = pmExpectedResponse .. string.char(0xA5) -- we're not done; keep them coming 
     1180   else  
     1181      pmExpectedResponse = string.char(0xA5) 
    9051182   end 
    9061183   if (eventType == 0x02) then 
     
    9091186      local batteryStatus = string.byte(pmIncomingPdu, 9, 12) 
    9101187      for i = 1, 30 do 
    911          local child = findChild(panel_device, string.format("Z%02d", i)) 
     1188         local child = findChild(pmPanelDev, string.format("Z%02d", i)) 
    9121189         if (child ~= nil) then 
    9131190            local trip = bitw.band(zoneStatus, 2 ^ (i - 1)) > 0 and 1 or 0 
     
    9281205 
    9291206      if (zoneEType ~= 0x00) then 
    930          debug(string.format("Event %s in Zone %02d",pmEventType_t[zoneEType+1], eventZone)) 
    931          local child = findChild(panel_device, string.format("Z%02d", eventZone)) 
     1207         debug(string.format("Event %s in Zone %02d",pmEventType_t[pmLang][zoneEType+1], eventZone)) 
     1208         local child = findChild(pmPanelDev, string.format("Z%02d", eventZone)) 
    9321209         if (child == nil) then 
    9331210            debug(string.format("unable to locate zone device %02d", eventZone)) 
     
    9481225 
    9491226      -- update status of PGM 
    950       local child = findChild(panel_device, string.format("PGM")) 
     1227      local child = findChild(pmPanelDev, string.format("PGM")) 
    9511228      if (child ~= nil) then 
    9521229         if (bitw.band(x10Stat1, 1) == 1) then 
     
    9631240      for i = 1, 15 do 
    9641241         local s = string.format("X%02d", i) 
    965          child = findChild(panel_device, s) 
     1242         child = findChild(pmPanelDev, s) 
    9661243         if (child ~= nil) then 
    9671244            local status = bitw.band(x10Status, 2 ^ i) > 0 and 1 or 0 
     
    9781255      if (bitw.band(sysFlags, 2) == 2) then  
    9791256         sysFlagsStr = sysFlagsStr .. ",MEM" 
    980          updateIfNeeded(PARTITION_SID, "AlarmMemory", 1, partition_device) 
     1257         updateIfNeeded(PARTITION_SID, "AlarmMemory", 1, pmPartitionDev_t[1]) 
    9811258      else 
    982          updateIfNeeded(PARTITION_SID, "AlarmMemory", 0, partition_device) 
     1259         updateIfNeeded(PARTITION_SID, "AlarmMemory", 0, pmPartitionDev_t[1]) 
    9831260      end 
    9841261      if (bitw.band(sysFlags, 4) == 4) then  
     
    9881265      if (bitw.band(sysFlags, 128) == 128) then  
    9891266         sysFlagsStr = sysFlagsStr .. ",ALRM"  
    990          if(updateIfNeeded(PARTITION_SID, "Alarm", "Active", partition_device) == true) then 
    991             luup.variable_set(PARTITION_SID, "LastAlarmActive", os.time(), partition_device) 
     1267         if(updateIfNeeded(PARTITION_SID, "Alarm", "Active", pmPartitionDev_t[1]) == true) then 
     1268            luup.variable_set(PARTITION_SID, "LastAlarmActive", os.time(), pmPartitionDev_t[1]) 
    9921269         end 
    9931270      else 
    994          updateIfNeeded(PARTITION_SID, "Alarm", "None", partition_device) 
     1271         updateIfNeeded(PARTITION_SID, "Alarm", "None", pmPartitionDev_t[1]) 
    9951272      end 
    9961273      local armModeNum = (pmArmed_t[sysStatus + 1] ~= nil) and 1 or 0 
     
    10001277         if (bitw.band(sysFlags, 2 ^ i) ~= 0) then 
    10011278            if (i == 5) then 
    1002                s = s .. string.format("Zone %02d %s, ", eventZone, pmEventType_t[zoneEType + 1]) 
     1279               if (eventZone == 0xFF) then 
     1280                  s = s .. pmEventType_t[pmLang][zoneEType + 1] .. " from Panel, " 
     1281               else 
     1282                  s = s .. string.format("%s in Zone %02d, ", pmEventType_t[pmLang][zoneEType + 1], eventZone) 
     1283               end 
    10031284            else 
    1004                s = s .. pmSysStatusFlags_t[i + 1] .. ", " 
     1285               s = s .. pmSysStatusFlags_t[pmLang][i + 1] .. ", " 
    10051286            end 
    10061287         end 
    10071288      end 
    10081289      s = (s == "") and "Not ready" or string.sub(s, 1, string.len(s) - 2) 
    1009       debug(string.format("System state is %s %s",(pmSysStatus_t[sysStatus + 1] or "UNKNOWN"), sysFlagsStr)) 
    1010       updateIfNeeded(PARTITION_SID, "VendorStatus", (pmSysStatus_t[sysStatus + 1]  or "UNKNOWN") .. sysFlagsStr, partition_device) 
    1011       updateIfNeeded(PARTITION_SID, "VendorStatusCode", string.format("%02X%02X", sysStatus, sysFlags), partition_device) 
    1012       updateIfNeeded(PARTITION_SID, "VendorStatusData", s, partition_device) 
    1013         updateIfNeeded(PARTITION_SID, "ArmMode", armMode, partition_device) 
    1014         updateIfNeeded(PARTITION_SID, "ArmModeNum", armModeNum, partition_device) 
    1015       updateIfNeeded(PARTITION_SID, "DetailedArmMode", (pmDetailedArmMode_t[sysStatus + 1]  or "UNKNOWN"), partition_device) 
     1290      debug(string.format("System state is %s %s",(pmSysStatus_t[pmLang][sysStatus + 1] or "UNKNOWN"), sysFlagsStr)) 
     1291      updateIfNeeded(PARTITION_SID, "VendorStatus", (pmSysStatus_t[pmLang][sysStatus + 1]  or "UNKNOWN") .. sysFlagsStr, pmPartitionDev_t[1]) 
     1292      updateIfNeeded(PARTITION_SID, "VendorStatusCode", string.format("%02X%02X", sysStatus, sysFlags), pmPartitionDev_t[1]) 
     1293      updateIfNeeded(PARTITION_SID, "VendorStatusData", s, pmPartitionDev_t[1]) 
     1294        updateIfNeeded(PARTITION_SID, "ArmMode", armMode, pmPartitionDev_t[1]) 
     1295        updateIfNeeded(PARTITION_SID, "ArmModeNum", armModeNum, pmPartitionDev_t[1]) 
     1296      updateIfNeeded(PARTITION_SID, "DetailedArmMode", (pmDetailedArmMode_t[sysStatus + 1]  or "UNKNOWN"), pmPartitionDev_t[1]) 
    10161297 
    10171298   elseif (eventType == 0x06) then 
     
    10201301      local zoneBypass = string.byte(pmIncomingPdu, 9, 12) 
    10211302      for i = 1, 30 do 
    1022          local child = findChild(panel_device, string.format("Z%02d", i)) 
     1303         local child = findChild(pmPanelDev, string.format("Z%02d", i)) 
    10231304         if (child ~= nil) then 
    10241305            if (updateIfNeeded(SECURITY_SID, "Armed", bitw.band(zoneBypass, 2 ^ (i - 1)) > 0 and 0 or 1, child) == true) then 
     
    10341315function pmHandlePanel() 
    10351316   local i 
    1036    for i = 0, 3 do 
    1037       local eventZone = string.byte(pmIncomingPdu, 5 + 2 * i) 
    1038       local logEvent  = string.byte(pmIncomingPdu, 6 + 2 * i) 
    1039  
    1040       if (logEvent == 0) then 
    1041          return true 
    1042       end 
    1043       local s = pmLogEvent_t[logEvent + 1] .. " / " .. pmLogUser_t[eventZone + 1] 
     1317   local msgCnt = string.byte(pmIncomingPdu, 3) 
     1318   for i = 1, msgCnt do 
     1319      local eventZone = string.byte(pmIncomingPdu, 3 + 2 * i) 
     1320      local logEvent  = string.byte(pmIncomingPdu, 4 + 2 * i) 
     1321      local s = pmLogEvent_t[pmLang][logEvent + 1] .. " / " .. pmLogUser_t[eventZone + 1] 
    10441322      debug("System message: " .. s) 
    10451323      local alarmStatus = pmPanelAlarmType_t[logEvent] or "None" 
    10461324      local troubleStatus = pmPanelTroubleType_t[logEvent] or "None" 
    1047       luup.variable_set(PANEL_SID, "PanelStatusCode", string.format("%02X", logEvent), panel_device) 
    1048       luup.variable_set(PANEL_SID, "PanelStatusData", s, panel_device) 
    1049       luup.variable_set(PANEL_SID, "PanelAlarmType", alarmStatus, panel_device) 
    1050       luup.variable_set(PANEL_SID, "PanelTroubleType", troubleStatus, panel_device) 
    1051       --luup.variable_set(PARTITION_SID, "LastUser", pmLogUser_t[eventZone + 1], partition_device) 
     1325      luup.variable_set(PANEL_SID, "PanelStatusCode", string.format("%02X", logEvent), pmPanelDev) 
     1326      luup.variable_set(PANEL_SID, "PanelStatusData", s, pmPanelDev) 
     1327      luup.variable_set(PANEL_SID, "PanelAlarmType", alarmStatus, pmPanelDev) 
     1328      luup.variable_set(PANEL_SID, "PanelTroubleType", troubleStatus, pmPanelDev) 
     1329      --luup.variable_set(PARTITION_SID, "LastUser", pmLogUser_t[eventZone + 1], pmPartitionDev_t[1]) 
     1330      -- TODO: update siren status 
     1331      if (logEvent == 0x60) then -- System reset 
     1332         pmReadPanelSettings() 
     1333      end 
    10521334   end 
    10531335   return true 
     
    10711353   elseif (subType == 0x0A and string.byte(pmIncomingPdu, 5) == 0x01) then 
    10721354      debug("Enrolling PowerLink") 
    1073       pmSendMessage(pmSendMsg_t.MSG_ENROLL) 
    1074       pmSendMessage(pmSendMsg_t.MSG_EXIT) 
    1075       pmSendMessage(pmSendMsg_t.MSG_DOWNLOAD) 
     1355      pmSendMessage("MSG_ENROLL") 
     1356      pmSendMessage("MSG_EXIT") 
     1357      pmSendMessage("MSG_DOWNLOAD") 
    10761358      pmExpectedResponse = string.char(0xAB) .. pmExpectedResponse 
    10771359   else 
     
    10881370   debug("RequestArmMode " .. (state or "N/A")) 
    10891371   if (armPDU ~= nil) then 
    1090       pmSendMessage(pmSendMsg_t.MSG_ARM, { arm = string.char(armPDU), pin = pinPDU }) 
     1372      pmSendMessage("MSG_ARM", { arm = string.char(armPDU), pin = pinPDU }) 
    10911373   else 
    10921374      debug(string.format("RequestArmMode invalid state requested " .. (state or "N/A"))) 
     
    11001382   if (pmPowerlinkMode == true) then 
    11011383      RequestArmMode(DetailedArmMode, pmPincode_t[1]) 
     1384   elseif (DetailedArmMode == "Stay") or (DetailedArmMode == "Armed") then 
     1385      -- TODO: check system setting whether this mode is enabled 
     1386      RequestArmMode(DetailedArmMode, string.char(0, 0)) 
    11021387   else 
    11031388      pmMessage("RequestQuickArmMode only supported in Powerlink mode.", 2) 
     
    11071392-- SetArmed 
    11081393function SetArmed(device, newArmedValue) 
    1109     luup.variable_set(SECURITY_SID, "Armed", newArmedValue, device) 
     1394   if (pmPowerlinkMode == true) then 
     1395        local zone = tonumber(string.sub(luup.devices[device].id, 2)) 
     1396      local bypass = string.format("%08X", 2 ^ (zone - 1)) 
     1397      local bypassStr = "" 
     1398      for i = 0, 3 do 
     1399         bypassStr = bypassStr .. string.char("0x" .. string.sub(bypass, 7 - i * 2, 8 - i * 2)) 
     1400      end 
     1401      if (newArmedValue == "1") then 
     1402         pmSendMessage("MSG_BYPASSDIS", { pin = pmPincode_t[1], bypass = bypassStr }) 
     1403      elseif (newArmedValue == "0") then 
     1404         pmSendMessage("MSG_BYPASSEN", { pin = pmPincode_t[1], bypass = bypassStr }) 
     1405      end 
     1406      pmSendMessage("MSG_STATUS") -- request status to check success and update variable 
     1407   else 
     1408      luup.variable_set(SECURITY_SID, "Armed", newArmedValue, device) 
     1409   end 
    11101410end 
    11111411 
     
    11171417   if (DeviceID ~= nil) then 
    11181418        local s = (DeviceID == 0) and "PGM" or string.format("X%02d", DeviceID) 
    1119         local child = findChild(panel_device, s) 
     1419        local child = findChild(pmPanelDev, s) 
    11201420        if (child == nil) then 
    11211421            debug(string.format("Unable to locate device X%02d", DeviceID)) 
     
    11321432       debug("Turn X10 device on/off (" .. onoff .. "/" ..  device_nr ..")") 
    11331433       luup.variable_set(X10DEVICE_SID, "Target", onoff, device) 
    1134       pmSendMessage(pmSendMsg_t.MSG_X10PGM, { cmd = string.char(onoff), device = x10dev }) 
     1434      pmSendMessage("MSG_X10PGM", { cmd = string.char(onoff), device = x10dev }) 
    11351435        -- If it went OK, we will receive an A5 status from the PowerMax, which will update the X10 status 
    11361436    end 
     
    11761476         luup.variable_set(X10DEVICEDIM_SID, "LoadLevelStatus", current, device) -- TODO: only after ACK 
    11771477         local x10dev = string.char(tonumber(string.sub(device_nr, 3, 4),16)) .. string.char(tonumber(string.sub(device_nr, 1, 2),16)) 
    1178          pmSendMessage(pmSendMsg_t.MSG_X10PGM, { cmd = string.char(cmd), device = x10dev }) 
    1179          pmSendMessage(pmSendMsg_t.MSG_X10PGM, { cmd = string.char(cmd), device = x10dev }) -- send it twice (step by 10%) 
     1478         pmSendMessage("MSG_X10PGM", { cmd = string.char(cmd), device = x10dev }) 
     1479         pmSendMessage("MSG_X10PGM", { cmd = string.char(cmd), device = x10dev }) -- send it twice (step by 10%) 
    11801480      end 
    11811481   end 
     
    12041504    debug("GetEventLog") 
    12051505    luup.variable_set(PANEL_SID, "EventLog", "", device) 
    1206    pmSendMessage(pmSendMsg_t.MSG_EVENTLOG, { pin = pmGetPin(PINCode) }) 
     1506   pmSendMessage("MSG_EVENTLOG", { pin = pmGetPin(PINCode) }) 
    12071507end 
    12081508 
     
    12131513    t.year=t.year - 2000 
    12141514    -- Does not work on all Powermax models 
    1215    local timePdu = string.char(t.min, t.hour, t.day, t.month, t.year) 
    1216    pmSendMessage(pmSendMsg_t.MSG_SETTIME, { time = timePdu }) 
     1515   local timePdu = string.char(t.sec, t.min, t.hour, t.day, t.month, t.year) 
     1516   pmSendMessage("MSG_SETTIME", { time = timePdu }) 
    12171517end 
    12181518 
     
    12221522    local bypassOn = 0 
    12231523    for i = 1, 30 do 
    1224         local child = findChild(panel_device, string.format("Z%02d", i)) 
     1524        local child = findChild(pmPanelDev, string.format("Z%02d", i)) 
    12251525        if (child ~= nil) then 
    12261526            local armed = tonumber(luup.variable_get(SECURITY_SID, "Armed", child) or 0) 
     
    12371537        bypassStr = bypassStr .. string.char("0x" .. string.sub(str, 7 - i * 2, 8 - i * 2)) 
    12381538    end 
    1239    pmSendMessage(pmSendMsg_t.MSG_BYPASSEN, { pin = pmGetPin(PINCode), bypass = bypassStr }) 
     1539   pmSendMessage("MSG_BYPASSEN", { pin = pmGetPin(PINCode), bypass = bypassStr }) 
    12401540end 
    12411541 
     
    12451545    local bypassOff = 0 
    12461546    for i = 1, 30 do 
    1247         local child = findChild(panel_device, string.format("Z%02d", i)) 
     1547        local child = findChild(pmPanelDev, string.format("Z%02d", i)) 
    12481548        if (child ~= nil) then 
    12491549            local armed = tonumber(luup.variable_get(SECURITY_SID, "Armed", child) or 0) 
     
    12601560        bypassStr = bypassStr .. string.char("0x" .. string.sub(str, 7 - i * 2, 8 - i * 2)) 
    12611561    end 
    1262    pmSendMessage(pmSendMsg_t.MSG_BYPASSDIS, { pin = pmGetPin(PINCode), bypass = bypassStr }) 
    1263 end 
     1562   pmSendMessage("MSG_BYPASSDIS", { pin = pmGetPin(PINCode), bypass = bypassStr }) 
     1563end 
Note: See TracChangeset for help on using the changeset viewer.