Changes in / [20:30]


Ignore:
Files:
6 added
4 edited

Legend:

Unmodified
Added
Removed
  • /trunk/S_EnOceanGateway1.xml

    r20 r30  
    1111        </stateVariable> 
    1212        <stateVariable> 
    13             <name>NumDevices</name> 
     13            <name>MaxID</name> 
     14            <dataType>ui4</dataType> 
     15        </stateVariable> 
     16        <stateVariable> 
     17            <name>TeachInStage</name> 
     18            <dataType>ui4</dataType> 
     19        </stateVariable> 
     20        <stateVariable> 
     21            <name>A_ARG_TYPE_UI4</name> 
    1422            <dataType>ui4</dataType> 
    1523        </stateVariable> 
     
    2634            </argumentList> 
    2735        </action> 
     36        <action> 
     37            <name>TeachIn</name> 
     38            <argumentList> 
     39                <argument> 
     40                    <name>pinCode</name> 
     41                    <direction>in</direction> 
     42                    <relatedStateVariable>A_ARG_TYPE_UI4</relatedStateVariable> 
     43                </argument> 
     44            </argumentList> 
     45        </action> 
    2846    </actionList> 
    2947</scpd> 
  • /trunk/I_EnOceanGateway1.xml

    r20 r30  
    1515            -- Callbacks 
    1616            ClearSysMessage = enoceanPlugin.ClearSysMessage 
     17            TeachIn = enoceanPlugin.TeachIn 
    1718 
    1819            return enoceanPlugin.Init( lul_device ) 
     
    4041            </run> 
    4142        </action> 
     43        <action> 
     44            <serviceId>urn:micasaverde-com:serviceId:EnOceanGateway1</serviceId> 
     45            <name>TeachIn</name> 
     46            <run> 
     47                enoceanPlugin.TeachIn( 1, lul_settings.pinCode ) 
     48            </run> 
     49        </action> 
    4250    </actionList> 
    4351</implementation> 
  • /trunk/L_EnOceanGateway1.lua

    r20 r30  
     1-- Steps to get Vera learned into a device (e.g. an actuator), to be able to control it: 
     2-- 0. User enters pin code into GUI and presses the “Teach” button. 
     3-- 1. Vera transmits 'Unlock device' telegram. (DONE) 
     4-- 2. Vera transmits 'Query ID' telegram. (DONE) 
     5-- 3. Vera receives 'Query ID' answer and creates the devices with the appropriate sender ID. (DONE) 
     6-- 4. Vera transmits 'Enter learn mode' telegram. (DONE) 
     7-- 5. Vera transmits RPS teach-in telegram. (DONE) 
     8-- 6. Vera transmits 'Stop learn' telegram. (DONE) 
     9-- 7. Vera transmits 'Lock device' telegram. (DONE) 
     10-- 
     11-- Requirements 
     12-- * Vera must have an unique sender ID. (DONE) 
     13-- 
     14-- FAQ 
     15-- Q: When Vera broadcasts the 'Unlock device' command, won't all the devices be unlocked and learn about Vera? 
     16-- A: We assume that only devices which accepted the pin code will be unlocked. 
     17--    So, if the user has 3 actuators but only 1 accepted the PIN code, 
     18--    that will be the only actuator that will learn about Vera and respond to commands from Vera. 
     19 
    120module("L_EnOceanGateway1", package.seeall) 
    221 
     
    928local F_DEBUG_MODE          = true 
    1029local F_LEARN_MODE          = false 
    11 local F_WAIT_FOR_RESPONSE   = false -- If this is 'true', no further messages will be sent 
    1230local F_SENDING_IN_PROGRESS = false 
    1331 
     
    4664-- Message types 
    4765local SYS_MESSAGE_TYPES = { 
    48     BUSY    =  1, 
    49     ERROR   =  2, 
    50     SUCCESS =  4 
     66    BUSY    = 1, 
     67    ERROR   = 2, 
     68    SUCCESS = 4 
    5169} 
    5270 
    5371-- Delays for luup.delay, in seconds 
    5472local DELAYS = { 
    55     SEND_NEXT_MESSAGE = 2 
     73    SEND_NEXT_MESSAGE = 2, 
     74    TEACHIN_STAGES    = 3 
    5675} 
    5776 
     
    7695 
    7796local TELEGRAM_TYPES = { 
    78     ["RPS"] = 0xF6, 
    79     ["1BS"] = 0xD5, 
    80     ["4BS"] = 0xA5 
     97    ["RPS"]    = 0xF6, 
     98    ["1BS"]    = 0xD5, 
     99    ["4BS"]    = 0xA5, 
     100    ["ADT"]    = 0xA6, 
     101    ["SYS_EX"] = 0xC5 
    81102} 
    82103 
     
    86107    ["RPS_EB"]           = {  3,  1 }, 
    87108    ["RPS_T21"]          = {  2,  1 }, 
     109    ["RPS_NU"]           = {  3,  1 }, 
    88110    ["1BS_LRN"]          = {  4,  1 }, 
    89111    ["1BS_CO"]           = {  7,  1 }, 
     
    101123    ["4BS_06_ILL1"]      = { 16,  8 }, 
    102124    ["4BS_06_ILL2"]      = {  8,  8 }, 
    103     ["4BS_07_PIRS"]      = { 16,  8 } 
     125    ["4BS_07_PIRS"]      = { 16,  8 }, 
     126    ["SYS_EX_FUNC_CODE"] = { 36, 12 }, 
     127    ["SYS_EX_MANUF_ID"]  = { 25, 11 } 
    104128} 
    105129 
     
    113137    [6] = "DI", 
    114138    [7] = "DO", 
     139} 
     140 
     141-- Base ID related variables. 
     142local MIN_BASE_ID = 0xFF800000 
     143local MAX_BASE_ID = 0xFFFFFF00 -- The last sender IDs interval has only 127 values instead of 128, 
     144                               -- so don't use it, to avoid handling it separately. 
     145local BASE_ID_GAP = 128 
     146---- 
     147 
     148local NUM_TEACHIN_STAGES  = 7 
     149 
     150local TEACHIN_STAGES = { 
     151    NONE                  = 0, 
     152    SEND_UNLOCK_RMCC      = 1, 
     153    SEND_QUERY_ID_RMCC    = 2, 
     154    RECV_QUERY_ID_ANSWER  = 3, 
     155    SEND_START_LEARN_RPC  = 4, 
     156    SEND_TEACHIN_TELEGRAM = 5, 
     157    SEND_STOP_LEARN_RPC   = 6, 
     158    SEND_LOCK_RMCC        = 7 
     159} 
     160 
     161local TEACHIN_STAGE_STRINGS = { 
     162    [ TEACHIN_STAGES.NONE ]                 = "NONE", 
     163    [ TEACHIN_STAGES.SEND_UNLOCK_RMCC ]     = "SEND UNLOCK RMCC", 
     164    [ TEACHIN_STAGES.SEND_QUERY_ID_RMCC ]   = "SEND QUERY ID RMCC", 
     165    [ TEACHIN_STAGES.RECV_QUERY_ID_ANSWER ] = "RECEIVE QUERY ID ANSWER", 
     166    [ TEACHIN_STAGES.SEND_START_LEARN_RPC ] = "SEND START LEARN RPC", 
     167    [ TEACHIN_STAGES.SEND_TEACHIN_TELEGRAM ]= "SEND TEACH-IN TELEGRAM", 
     168    [ TEACHIN_STAGES.SEND_STOP_LEARN_RPC ]  = "SEND STOP LEARN RPC", 
     169    [ TEACHIN_STAGES.SEND_LOCK_RMCC ]       = "SEND LOCK RMCC" 
     170} 
     171 
     172-- Remote management commands function numbers 
     173local RMCC = { 
     174    UNLOCK          = 0x001, 
     175    LOCK            = 0x002, 
     176    QUERY_ID        = 0x004, 
     177    QUERY_ID_ANSWER = 0x604 
     178} 
     179 
     180-- Remote procedure calls function numbers 
     181local RPC = { 
     182    REMOTE_LEARN = 200 
     183} 
     184 
     185local DATA_LENGTHS = { 
     186    [RMCC.UNLOCK]      = 4, 
     187    [RMCC.LOCK]        = 4, 
     188    [RMCC.QUERY_ID]    = 3, 
     189    [RPC.REMOTE_LEARN] = 4 
    115190} 
    116191 
     
    163238 
    164239local g_childDevices = {}   -- The list of all our child devices 
    165 local g_numDevices = 0      -- The number of learned EnOcean devices. This is different than the number of child devices, 
    166                             -- because there can be several child devices for one EnOcean device. 
     240local g_maxId = 0           -- A number that increments with every device learned. 
    167241 
    168242local g_knownSenderIds = {} -- The list of the newly learned EnOcean devices while in Learn Mode. 
     
    180254------------------------------------------------------------------------------------------------------------------------ 
    181255 
    182 local log      = luup.log 
    183 -- Get the 'getinfo' function from the 'debug' module before overwriting the 'debug' keyword. 
    184 local getinfo  = debug.getinfo 
     256local log = luup.log 
     257-- Save the 'getinfo' function from the 'debug' module before overwriting the 'debug' keyword. 
     258local getinfo = debug.getinfo 
    185259local function debug() end 
    186260 
    187261 
    188 local function ShowSysMessage (message, mode, critical) 
     262local function ShowSysMessage (message, mode, permanent) 
    189263    mode = mode or SYS_MESSAGE_TYPES.BUSY 
    190     critical = critical or false 
     264    permanent = permanent or false 
    191265    log( "(EnOceanPlugin::ShowSysMessage-"..(getinfo(2).name or "N/A")..") mode: ".. mode 
    192         ..", critical: ".. tostring( critical ) ..", message: ".. message ) 
     266        ..", permanent: ".. tostring( permanent ) ..", message: ".. message ) 
    193267 
    194268    luup.task( message, mode, "EnOcean Plugin", g_taskHandle ) 
    195269    g_lastSysMessage = tostring( os.time() ) 
    196270 
    197     if not critical then 
     271    if not permanent then 
    198272        -- Clear the previous system message, since it's transient. 
    199273        luup.call_delay( "ClearSysMessage", 30, g_lastSysMessage ) 
    200     else 
     274    elseif mode == SYS_MESSAGE_TYPES.ERROR then 
     275        -- Critical error. 
    201276        luup.set_failure( true, g_parentDevice ) 
    202277    end 
     
    258333-- Concatenates a table of numbers into a string separated by the given separator. 
    259334local function ConcatTableHex( t, sep, start, length ) 
    260     sep = sep or "-" 
     335    sep = sep or '-' 
    261336    start = start or 1 
    262337    if start < 0 then 
     
    291366 
    292367 
     368-- Appends the second array at the end of the first array. 
     369local function AppendArray( a1, a2 ) 
     370    local table_insert = table.insert 
     371    for _, v in ipairs(a2) do 
     372        table_insert( a1, v ) 
     373    end 
     374end 
     375 
     376 
     377-- Splits a string based on the given separator. Returns a table. 
     378local function SplitString( s, sep ) 
     379    sep = sep or ' ' 
     380    local t = {} 
     381    for token in s:gmatch( "[^"..sep.."]+" ) do 
     382        table.insert( t, token ) 
     383    end 
     384    return t 
     385end 
     386 
    293387------------------------------------------------------------------------------------------------------------------------ 
    294388-- Misc plugin functions 
     
    303397        crc = CRC8_TABLE[bit.bxor( crc, data[i] ) + 1] 
    304398    end 
    305     debug( "(EnOceanPlugin::ComputeCrc) crc=".. ToHex( crc )) 
     399    --debug( "(EnOceanPlugin::ComputeCrc) crc=".. ToHex( crc )) 
    306400    return crc 
     401end 
     402 
     403 
     404local function GetBaseIdArray() 
     405    -- If the base ID array is in cache, return the cached version, 
     406    -- otherwise compute it. 
     407    -- The 'c' prefix means the variable is the cache. 
     408    if c_baseIdArray then 
     409        return c_baseIdArray 
     410    else 
     411        c_baseIdArray = { 0, 0, 0, 0 } 
     412        for nibbleNum = 1, 4 do 
     413            local numBitsShift = (4 - nibbleNum) * 8 
     414            local mask = bit.lshift( 0xFF, numBitsShift ) 
     415            c_baseIdArray[nibbleNum] = bit.rshift( bit.band( g_baseId, mask ), numBitsShift ) 
     416        end 
     417        return c_baseIdArray 
     418    end 
     419end 
     420 
     421 
     422-- Splits the given number into an array of bits indexed from 1. 
     423-- The first bit (bit 0) is the first element in the array. 
     424local function SplitToBits( number, numBits ) 
     425    local bits = {} 
     426 
     427    local table_insert = table.insert 
     428    while number > 0 do 
     429        table_insert( bits, number % 2 ) 
     430        number = math.floor( number / 2 ) 
     431    end 
     432    -- Pad with 0 up to the number of required bits. 
     433    for i = #bits + 1, numBits do 
     434        table_insert( bits, 0 ) 
     435    end 
     436 
     437    return bits 
     438end 
     439 
     440 
     441-- Padds the value to occupy the given number of bits, 
     442-- and inserts it in the data, which is an array of bytes. 
     443local function InsertValueAtLocation (data, value, offset, numBits) 
     444    -- Split value into an array of bits indexed from 1. 
     445    local bits = SplitToBits( value, numBits ) 
     446    -- Insert all the bits starting from the last bit. 
     447    for bitIdx = #bits, 1, -1 do 
     448        -- Find byte index, indexed from 1. 
     449        local byteIdx = math.floor( offset / 8 ) + 1 
     450        -- Find bit position, indexed from 0. 
     451        local bitPos = byteIdx * 8 - offset - 1 
     452        if bits[bitIdx] == 1 then 
     453            data[byteIdx] = bit.set( data[byteIdx], bit.lshift( 1, bitPos )) 
     454        else 
     455            data[byteIdx] = bit.unset( data[byteIdx], bit.lshift( 1, bitPos )) 
     456        end 
     457        offset = offset + 1 
     458    end 
    307459end 
    308460 
     
    374526 
    375527        local firstAction = GetValueAtLocation( data, LOCATIONS["RPS_R1"] ) 
    376         local altid = senderId ..'-'.. ROCKER_BUTTONS[firstAction] 
     528        local altid = senderId ..'-'.. ROCKER_BUTTONS[firstAction]:sub( 1, 1 ) 
    377529        debug( "(EnOceanPlugin::RPS) altid: ".. altid ) 
    378530 
    379531        -------------------------------------------------------------------------------------------- 
    380         -- The device is learned. 
     532        -- The device is learned and it's not a "number of buttons pressed" telegram. 
    381533        -------------------------------------------------------------------------------------------- 
    382534        if g_childDevices[altid] then 
    383             debug( "(EnOceanPlugin::RPS) Device known: ".. g_childDevices[altid] ) 
    384             local pressed = GetValueAtLocation( data, LOCATIONS["RPS_EB"] ) 
     535            log( "(EnOceanPlugin::RPS) Device known: ".. g_childDevices[altid] ) 
     536            -- Ignore "number of buttons pressed" telegrams. 
     537            if GetValueAtLocation( { status }, LOCATIONS["RPS_NU"] ) == 0 then 
     538                debug( "(EnOceanPlugin::RPS) 'Number of buttons pressed' telegram. Return" ) 
     539                return 
     540            end 
     541            -- Ignore "button released" telegrams. 
     542            if GetValueAtLocation( data, LOCATIONS["RPS_EB"] ) == 0 then 
     543                debug( "(EnOceanPlugin::RPS) 'Button released' telegram. Return" ) 
     544                return 
     545            end 
     546            local pressed = ( ROCKER_BUTTONS[firstAction]:sub( 2, 2 ) == "I" ) and "1" or "0" 
    385547            luup.variable_set( SID.SW_POWER, "Status", pressed, g_childDevices[altid] ) 
    386548 
    387549        -------------------------------------------------------------------------------------------- 
    388550        -- The device is not learned, and we're in Learn Mode. 
    389         -- RPS devices don't have Teach-in telegrams. 
     551        -- RPS devices don't have TeachIn-in telegrams. 
    390552        -------------------------------------------------------------------------------------------- 
    391553        elseif F_LEARN_MODE and not TableContains( g_knownSenderIds, senderId ) then 
    392554            -- We don't want to add the same device twice. 
    393555            set.add( g_knownSenderIds, senderId ) 
    394             g_numDevices = g_numDevices + 1 
     556            g_maxId = g_maxId + 1 
    395557 
    396558            -- Find the device type. If T21 is 1, this is a 2 rocker switch, otherwise it's a 4 rocker switch. 
     
    400562            -- Create the devices. 
    401563            log( "(EnOceanPlugin::RPS) T21: ".. t21 ..", num rockers: ".. numRockers ) 
    402             ShowSysMessage( "Found ".. numRockers .." Rocker Switch (EO-".. PadLeft( g_numDevices ) ..")" ) 
    403             -- We have 2 buttons for each rocker, so we'll create a switch for each button. 
     564            ShowSysMessage( "Found ".. numRockers .." Rocker Switch (EO-".. PadLeft( g_maxId ) ..")" ) 
     565            -- Create a switch for each rocker. 
    404566            local deviceName 
    405567            local parameters = SID.SW_POWER ..",Status=0" 
    406             for i = 0, 2 * numRockers - 1 do 
    407                 altid = senderId ..'-'.. ROCKER_BUTTONS[i] 
    408                 deviceName = "(EO-".. PadLeft( g_numDevices ) ..") Button ".. ROCKER_BUTTONS[i] 
     568            for i = 0, numRockers - 1 do 
     569                -- Get only the button, ignore the state. 
     570                local button = ROCKER_BUTTONS[2 * i]:sub( 1, 1 ) 
     571                altid = senderId ..'-'.. button 
     572                deviceName = "(EO-".. PadLeft( g_maxId ) ..") Button ".. button 
    409573                log(  "(EnOceanPlugin::RPS) New device: ".. deviceName  ) 
    410574                table.insert( g_newDevices, { 
     
    417581                } ) 
    418582            end 
     583        else 
     584            log( "(EnOceanPlugin::RPS) Unkown device" ) 
    419585        end 
    420586    end, 
     
    427593 
    428594        local isDataTelegram = GetValueAtLocation( data, LOCATIONS["1BS_LRN"] ) == 1 
    429         debug(  "(EnOceanPlugin::1BS) ".. (isDataTelegram and "Data" or "Teach-in") .." telegram"  ) 
     595        debug(  "(EnOceanPlugin::1BS) ".. (isDataTelegram and "Data" or "TeachIn-in") .." telegram"  ) 
    430596 
    431597        -------------------------------------------------------------------------------------------- 
     
    433599        -------------------------------------------------------------------------------------------- 
    434600        if g_childDevices[senderId] and isDataTelegram then 
    435             debug(  "(EnOceanPlugin::1BS) Device known: ".. g_childDevices[senderId]  ) 
     601            log(  "(EnOceanPlugin::1BS) Device known: ".. g_childDevices[senderId]  ) 
    436602            local tripped = 1 - GetValueAtLocation( data, LOCATIONS["1BS_CO"] ) 
    437603            luup.variable_set( SID.SECURITY, "Tripped", tripped, g_childDevices[senderId] ) 
     
    439605        -------------------------------------------------------------------------------------------- 
    440606        -- The device is not learned, and we're in Learn Mode. 
    441         -- Only learn the device if this is a Teach-in telegram, even if it doesn't contain any useful information. 
     607        -- Only learn the device if this is a TeachIn-in telegram, even if it doesn't contain any useful information. 
    442608        -- We don't want to accidentaly learn devices that the user doesn't want. 
    443609        -------------------------------------------------------------------------------------------- 
     
    445611            -- We don't want to add the same device twice. 
    446612            set.add( g_knownSenderIds, senderId ) 
    447             g_numDevices = g_numDevices + 1 
     613            g_maxId = g_maxId + 1 
    448614            log( "(EnOceanPlugin::1BS) New device: ".. deviceName  ) 
    449             ShowSysMessage( "Found Single Input Contact (EO-".. PadLeft( g_numDevices ) ..")" ) 
    450             deviceName = "(EO-".. PadLeft( g_numDevices ) ..") Single Input Contact" 
     615            ShowSysMessage( "Found Single Input Contact (EO-".. PadLeft( g_maxId ) ..")" ) 
     616            deviceName = "(EO-".. PadLeft( g_maxId ) ..") Single Input Contact" 
    451617            table.insert( g_newDevices, { 
    452618                ["altid"]      = senderId, 
     
    457623                ["parameters"] = SID.SECURITY ..",Tripped=0\n".. SID.SECURITY ..",Armed=0" 
    458624            } ) 
     625        else 
     626            log( "(EnOceanPlugin::1BS) Unkown device or TeachIn-in telegram" ) 
    459627        end 
    460628    end, 
     
    464632    -------------------------------------------------------------------------------------------------------------------- 
    465633    [TELEGRAM_TYPES["4BS"]] = function (senderId, data, status) 
    466         debug(  "(EnOceanPlugin::4BS) 4BS telegram handler" ) 
     634        debug( "(EnOceanPlugin::4BS) 4BS telegram handler" ) 
    467635 
    468636        local isDataTelegram = GetValueAtLocation( data, LOCATIONS["4BS_LRN"] ) == 1 
    469         debug(  "(EnOceanPlugin::4BS) ".. (isDataTelegram and "Data" or "Teach-in") .." telegram" ) 
     637        debug( "(EnOceanPlugin::4BS) ".. (isDataTelegram and "Data" or "TeachIn-in") .." telegram" ) 
    470638 
    471639        -------------------------------------------------------------------------------------------- 
     
    481649            if eepFunc == 0x02 then 
    482650                if not g_childDevices[altid] then 
    483                     debug( "(EnOceanPlugin::4BS) Device not known. Return" ) 
     651                    log( "(EnOceanPlugin::4BS) Device not known. Return" ) 
    484652                    return 
    485653                end 
    486                 debug( "(EnOceanPlugin::4BS) Device known: ".. g_childDevices[altid] ) 
     654                log( "(EnOceanPlugin::4BS) Device known: ".. g_childDevices[altid] ) 
    487655 
    488656                local sensorType = math.floor( eepType / 0x10 ) 
     
    522690                local altidTemp = altid .."-temp" 
    523691                if not g_childDevices[altidHum] then 
    524                     debug( "(EnOceanPlugin::4BS) Device not known. Return" ) 
     692                    log( "(EnOceanPlugin::4BS) Device not known. Return" ) 
    525693                    return 
    526694                end 
    527                 debug( "(EnOceanPlugin::4BS) Device known: hum ".. g_childDevices[altidHum] ..", temp "..( g_childDevices[altidTemp] or "NONE" )) 
     695                log( "(EnOceanPlugin::4BS) Device known: hum ".. g_childDevices[altidHum] ..", temp "..( g_childDevices[altidTemp] or "NONE" )) 
    528696 
    529697                -- Get the humidity. 
     
    543711            elseif eepFunc == 0x06 then 
    544712                if not g_childDevices[altid] then 
    545                     debug( "(EnOceanPlugin::4BS) Device not known. Return" ) 
     713                    log( "(EnOceanPlugin::4BS) Device not known. Return" ) 
    546714                    return 
    547715                end 
    548                 debug( "(EnOceanPlugin::4BS) Device known: ".. g_childDevices[altid] ) 
     716                log( "(EnOceanPlugin::4BS) Device known: ".. g_childDevices[altid] ) 
    549717 
    550718                local range = GetValueAtLocation( data, LOCATIONS["4BS_06_RS"] ) 
     
    569737            elseif eepFunc == 0x07 then 
    570738                if not g_childDevices[altid] then 
    571                     debug( "(EnOceanPlugin::4BS) Device not known. Return" ) 
     739                    log( "(EnOceanPlugin::4BS) Device not known. Return" ) 
    572740                    return 
    573741                end 
    574                 debug( "(EnOceanPlugin::4BS) Device known: ".. g_childDevices[altid] ) 
     742                log( "(EnOceanPlugin::4BS) Device known: ".. g_childDevices[altid] ) 
    575743 
    576744                local tripped = GetValueAtLocation( data, LOCATIONS["4BS_07_PIRS"] ) -- 0...255 
     
    579747            end 
    580748        -------------------------------------------------------------------------------------------- 
    581         -- The device is not learned, we're in Learn Mode, and we received a Teach-in telegram. 
     749        -- The device is not learned, we're in Learn Mode, and we received a TeachIn-in telegram. 
    582750        -------------------------------------------------------------------------------------------- 
    583751        elseif F_LEARN_MODE and not isDataTelegram and not g_childDevices[senderId] and not TableContains( g_knownSenderIds, senderId ) then 
    584752            local lrnType = GetValueAtLocation( data, LOCATIONS["4BS_LRN_TYPE"] ) 
    585753            if lrnType ~= 1 and lrnType ~= 2 then 
    586                 log( "(EnOceanPlugin::4BS) Teach-in telegram variation 1, discard it" ) 
     754                log( "(EnOceanPlugin::4BS) TeachIn-in telegram variation 1, discard it" ) 
    587755                return 
    588756            end 
     
    590758            -- We don't want to add the same device twice. 
    591759            set.add( g_knownSenderIds, senderId ) 
    592             g_numDevices = g_numDevices + 1 
     760            g_maxId = g_maxId + 1 
    593761 
    594762            local eepFunc = GetValueAtLocation( data, LOCATIONS["4BS_LRN_EEP_FUNC"] ) 
    595763            local eepType = GetValueAtLocation( data, LOCATIONS["4BS_LRN_EEP_TYPE"] ) 
    596             local manufId = GetValueAtLocation( data, LOCATIONS["4BS_LRN_MANUF_ID"] ) 
     764            local manufId = GetValueAtLocation( data, LOCATIONS["4BS_LRN_MANUF_ID"] ) -- not used in this version 
    597765 
    598766            log( "(EnOceanPlugin::4BS) Func ".. ToHex( eepFunc ) ..", Type ".. ToHex( eepType )) 
     
    603771            -- 
    604772            if eepFunc == 0x02 then 
    605                 local deviceName = "(EO-".. PadLeft( g_numDevices ) ..") Temperature Sensor" 
     773                local deviceName = "(EO-".. PadLeft( g_maxId ) ..") Temperature Sensor" 
    606774                log( "(EnOceanPlugin::4BS) New device: ".. deviceName ) 
    607                 ShowSysMessage( "Found Temperature sensor (EO-".. PadLeft( g_numDevices ) ..")" ) 
     775                ShowSysMessage( "Found Temperature sensor (EO-".. PadLeft( g_maxId ) ..")" ) 
    608776                table.insert( g_newDevices, { 
    609777                    ["altid"]      = senderId, 
     
    618786            -- 
    619787            elseif eepFunc == 0x04 then 
    620                 ShowSysMessage( "Found Temperature and Humidity sensor (EO-".. PadLeft( g_numDevices ) ..")" ) 
     788                ShowSysMessage( "Found Temperature and Humidity sensor (EO-".. PadLeft( g_maxId ) ..")" ) 
    621789                -- Create the humidity sensor. 
    622                 deviceName = "(EO-".. PadLeft( g_numDevices ) ..") Humidity Sensor" 
     790                deviceName = "(EO-".. PadLeft( g_maxId ) ..") Humidity Sensor" 
    623791                log( "(EnOceanPlugin::4BS) New device: ".. deviceName ) 
    624792                table.insert( g_newDevices, { 
     
    631799                } ) 
    632800                -- Create the temperature sensor. 
    633                 local deviceName = "(EO-".. PadLeft( g_numDevices ) ..") Temperature Sensor" 
     801                local deviceName = "(EO-".. PadLeft( g_maxId ) ..") Temperature Sensor" 
    634802                log( "(EnOceanPlugin::4BS) New device: ".. deviceName ) 
    635803                table.insert( g_newDevices, { 
     
    645813            -- 
    646814            elseif eepFunc == 0x06 then 
    647                 local deviceName = "(EO-".. PadLeft( g_numDevices ) ..") Light Sensor" 
     815                local deviceName = "(EO-".. PadLeft( g_maxId ) ..") Light Sensor" 
    648816                log( "(EnOceanPlugin::4BS) New device: ".. deviceName ) 
    649                 ShowSysMessage( "Found Light sensor (EO-".. PadLeft( g_numDevices ) ..")" ) 
     817                ShowSysMessage( "Found Light sensor (EO-".. PadLeft( g_maxId ) ..")" ) 
    650818                table.insert( g_newDevices, { 
    651819                    ["altid"]      = senderId, 
     
    660828            -- 
    661829            elseif eepFunc == 0x07 then 
    662                 local deviceName = "(EO-".. PadLeft( g_numDevices ) ..") Occupancy Sensor" 
     830                local deviceName = "(EO-".. PadLeft( g_maxId ) ..") Occupancy Sensor" 
    663831                log( "(EnOceanPlugin::4BS) New device: ".. deviceName ) 
    664                 ShowSysMessage( "Found Occupancy sensor (EO-".. PadLeft( g_numDevices ) ..")" ) 
     832                ShowSysMessage( "Found Occupancy sensor (EO-".. PadLeft( g_maxId ) ..")" ) 
    665833                table.insert( g_newDevices, { 
    666834                    ["altid"]      = senderId, 
     
    675843                ShowSysMessage( "Found unknown device A5-".. ToHex( eepFunc ) .."-".. ToHex( eepType ) ) 
    676844            end 
     845        else 
     846            log( "(EnOceanPlugin::4BS) Unkown device or TeachIn-in telegram" ) 
     847        end 
     848    end, 
     849 
     850    -------------------------------------------------------------------------------------------------------------------- 
     851    -- ADT 
     852    -------------------------------------------------------------------------------------------------------------------- 
     853    [TELEGRAM_TYPES["ADT"]] = function (senderId, data, status) 
     854        debug( "(EnOceanPlugin::ADT) ADT telegram handler" ) 
     855 
     856        -- Check if we're in a teach-in procedure and we're expecting a Query ID answer. 
     857        if g_teachInStage == TEACHIN_STAGES.RECV_QUERY_ID_ANSWER then 
     858            -- Check if this telegram is a Query ID answer telegram: 
     859            -- 1. Check if the encapsulated RORG is the one we expect. 
     860            if data[1] ~= TELEGRAM_TYPES.SYS_EX then 
     861                log( "(EnOceanPlugin::ADT) Unhandled encapsulated RORG: ".. data[1] ) 
     862                return 
     863            end 
     864            -- 2. Check if the RMC function code is the one we expect. 
     865            local functionCode = GetValueAtLocation( data, LOCATIONS["SYS_EX_FUNC_CODE"] ) 
     866            if functionCode ~= RMCC.QUERY_ID_ANSWER then 
     867                log( "(EnOceanPlugin::ADT) Unhandled remote management control command: ".. functionCode ) 
     868                return 
     869            end 
     870            -- Get the manufacturer ID. (not used in this version) 
     871            local manufId = GetValueAtLocation( data, LOCATIONS["SYS_EX_MANUF_ID"] ) 
     872 
     873            -------------------------------------------------------------------- 
     874            -- Create the device and move to the next stage. 
     875            -------------------------------------------------------------------- 
     876            g_teachInStage = TEACHIN_STAGES.SEND_START_LEARN_RPC 
     877            luup.variable_set( SID.ENOCEAN, "TeachInStage", g_teachInStage, g_parentDevice ) 
     878 
     879            g_maxId = g_maxId + 1 
     880            luup.variable_set( SID.ENOCEAN, "MaxID", g_maxId, g_parentDevice ) 
     881 
     882            local ptr = luup.chdev.start( g_parentDevice ) 
     883            luup.chdev.append( g_parentDevice, ptr, senderId, "(EO-".. PadLeft( g_maxId ) ..") Actuator", 
     884                DEVICE_TYPES.BINARY_LIGHT, DEVICE_FILES.BINARY_LIGHT, "", SID.SW_POWER ..",Status=0", false ) 
     885            luup.chdev.sync( g_parentDevice, ptr ) 
    677886        end 
    678887    end 
     
    697906            .. ", senderId: ".. senderId 
    698907            .. ", status: ".. ToHex( status ) 
    699             .. ", data: ".. ConcatTableHex( data, '-' )) 
     908            .. ", data: ".. ConcatTableHex( data )) 
    700909 
    701910        if RADIO_TELEGRAM_HANDLERS[radioTelegramType] then 
     
    741950    [STATE.GET_SYNC_STATE] = function (rxByte) 
    742951        if rxByte == SYNC_BYTE then 
    743             debug( "(EnOceanPlugin::GET_SYNC_STATE) Got sync byte, go to GET_HEADER_STATE" ) 
     952            --debug( "(EnOceanPlugin::GET_SYNC_STATE) Got sync byte, go to GET_HEADER_STATE" ) 
    744953            state = STATE.GET_HEADER_STATE 
    745954            crc   = 0 
     
    755964        -- All the header bytes received? 
    756965        if rxCount == 4 then 
    757             debug( "(EnOceanPlugin::GET_HEADER_STATE) Received all header bytes, go to CHECK_CRC8H_STATE" ) 
     966            --debug( "(EnOceanPlugin::GET_HEADER_STATE) Received all header bytes, go to CHECK_CRC8H_STATE" ) 
    758967            state = STATE.CHECK_CRC8H_STATE 
    759968        end 
     
    764973        -- Header CRC correct? 
    765974        if ComputeCrc( rxBuf, 1, rxCount ) ~= rxByte then 
    766             debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) Incorrect header CRC" ) 
     975            --debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) Incorrect header CRC" ) 
    767976            -- No. Check if there is a sync byte (0x55) in the header. 
    768977            local pos = -1 
     
    777986            if pos == -1 and rxByte ~= SYNC_BYTE then 
    778987                -- Neither the header nor the CRC8H contain the sync byte 
    779                 debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) Neither the header nor the CRC8H contain the sync byte, go to GET_SYNC_STATE" ) 
     988                --debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) Neither the header nor the CRC8H contain the sync byte, go to GET_SYNC_STATE" ) 
    780989                state = STATE.GET_SYNC_STATE 
    781990                return 
     
    783992                -- The header does not contain the sync byte but the CRC8H does 
    784993                -- The sync byte could be the beginning of a packet 
    785                 debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) The header does not contain the sync byte but the CRC8H does, go to GET_HEADER_STATE" ) 
     994                --debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) The header does not contain the sync byte but the CRC8H does, go to GET_HEADER_STATE" ) 
    786995                state = STATE.GET_HEADER_STATE 
    787996                rxCount = 0 
     
    8021011 
    8031012            if rxCount < 4 then 
    804                 debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) There are still header bytes to receive, go to GET_HEADER_STATE" ) 
     1013                --debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) There are still header bytes to receive, go to GET_HEADER_STATE" ) 
    8051014                state = STATE.GET_HEADER_STATE 
    8061015            end 
    8071016 
    808             debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) All header bytes received, stay in CHECK_CRC8H_STATE" ) 
     1017            --debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) All header bytes received, stay in CHECK_CRC8H_STATE" ) 
    8091018            return 
    8101019        end 
     
    8131022        rxDataLen = rxBuf[1] * 0x100 + rxBuf[2] 
    8141023        rxOptDataLen = rxBuf[3] 
    815         debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) Header CRC correct, rxDataLen=".. rxDataLen ..", rxOptDataLen=".. rxOptDataLen ) 
     1024        --debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) Header CRC correct, rxDataLen=".. rxDataLen ..", rxOptDataLen=".. rxOptDataLen ) 
    8161025        if (rxDataLen + rxOptDataLen) == 0 then 
    8171026            -- No. Sync byte received? 
    8181027            if rxByte == SYNC_BYTE then 
    8191028                -- Yes 
    820                 debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) Header CRC incorrect, sync byte received, go to GET_HEADER_STATE" ) 
     1029                --debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) Header CRC incorrect, sync byte received, go to GET_HEADER_STATE" ) 
    8211030                state = STATE.GET_HEADER_STATE 
    8221031                rxCount = 0 
     
    8251034 
    8261035            -- Packet with correct CRC8H, but wrong length fields 
    827             debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) Packet with correct CRC8H, but wrong length fields, go to GET_SYNC_STATE" ) 
     1036            --debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) Packet with correct CRC8H, but wrong length fields, go to GET_SYNC_STATE" ) 
    8281037            state = STATE.GET_SYNC_STATE 
    8291038            return 
     
    8311040 
    8321041        -- Correct header CRC8. Go to the reception of data. 
    833         debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) Correct header CRC8, go to GET_DATA_STATE" ) 
     1042        --debug( "(EnOceanPlugin::CHECK_CRC8H_STATE) Correct header CRC8, go to GET_DATA_STATE" ) 
    8341043        state = STATE.GET_DATA_STATE 
    8351044        rxCount = 0 
     
    8441053        -- When all the expected bytes are received, go to calculate data checksum. 
    8451054        if rxCount == (rxDataLen + rxOptDataLen) then 
    846             debug( "(EnOceanPlugin::GET_DATA_STATE) All expected bytes received, go to CHECK_CRC8D_STATE" ) 
     1055            --debug( "(EnOceanPlugin::GET_DATA_STATE) All expected bytes received, go to CHECK_CRC8D_STATE" ) 
    8471056            state = STATE.CHECK_CRC8D_STATE 
    8481057        end 
     
    8561065        if ComputeCrc( rxBuf, 1, rxCount ) == rxByte then 
    8571066            -- Correct packet received 
    858             debug( "(EnOceanPlugin::CHECK_CRC8D_STATE) Correct packet received, handle it and go to GET_SYNC_STATE" ) 
     1067            --debug( "(EnOceanPlugin::CHECK_CRC8D_STATE) Correct packet received, handle it and go to GET_SYNC_STATE" ) 
    8591068            if PACKET_HANDLERS[rxPacketType] then 
    8601069                PACKET_HANDLERS[rxPacketType]( ExtractSubtable( rxBuf, 1, rxDataLen ), ExtractSubtable( rxBuf, rxDataLen + 1, rxOptDataLen )) 
     
    8681077        -- If the received byte is the sync byte, then it could be the sync byte for next packet. 
    8691078        if rxByte == SYNC_BYTE then 
    870             debug( "(EnOceanPlugin::CHECK_CRC8D_STATE) Incorrect CRC8, the received byte is the sync byte, go to GET_HEADER_STATE" ) 
     1079            --debug( "(EnOceanPlugin::CHECK_CRC8D_STATE) Incorrect CRC8, the received byte is the sync byte, go to GET_HEADER_STATE" ) 
    8711080            state = STATE.GET_HEADER_STATE 
    8721081            rxCount = 0 
     
    8801089function HandleIncoming (lul_data) 
    8811090    local rxByte = string.byte( lul_data ) 
    882     debug( "(EnOceanPlugin::HandleIncoming) state ".. state ..", rxByte=".. ToHex( rxByte )) 
     1091    --debug( "(EnOceanPlugin::HandleIncoming) state ".. state ..", rxByte=".. ToHex( rxByte )) 
    8831092    if STATE_MACHINE[state] then 
    8841093        STATE_MACHINE[state]( rxByte ) 
     
    8951104------------------------------------------------------------------------------------------------------------------------ 
    8961105 
    897 local function AddToSendQueue (packetType, data, optData, waitForResponse) 
    898     local insert = table.insert 
    899     local char   = string.char 
    900  
    901     local packet = { SYNC_BYTE }              -- The sync byte 
    902  
    903     insert( packet, char( #data / 0x100 ))    -- First byte of the data length 
    904     insert( packet, char( #data % 0x100 ))    -- Seconds byte of the data length 
    905     insert( packet, char( #optData ))         -- Length of the optional data 
    906     insert( packet, packetType )              -- The packet type 
    907     insert( packet, ComputeCrc(packet, 2, 4)) -- The header CRC8 
    908  
    909     insert( packet, data )                    -- The user data 
    910     insert( packet, optData )                 -- The optional user data 
    911     insert( packet, ComputeCrc( packet, 7, #data + #optData )) -- The data CRC8 
    912  
    913     table.insert( g_messageQueue, {content = table.concat(packet), waitForResponse = (waitForResponse or false)} ) 
     1106local function CreateSysExTelegram (rmcc, data) 
     1107    -- 9 bytes - The first byte is always the RORG. 
     1108    local sysEx = { TELEGRAM_TYPES["SYS_EX"], 0, 0, 0, 0, 0, 0, 0, 0 } 
     1109    local offset = 8 
     1110 
     1111    -- SEQ - 2 bits 
     1112    local numBits = 2 
     1113    math.randomseed( os.time() ) 
     1114    local seq = math.random(4) - 1 
     1115    InsertValueAtLocation( sysEx, seq, offset, numBits ) 
     1116    offset = offset + numBits 
     1117 
     1118    -- IDX - 6 bits 
     1119    numBits = 6 
     1120    InsertValueAtLocation( sysEx, 0, offset, numBits ) 
     1121    offset = offset + numBits 
     1122 
     1123    -- Data length - 9 bits 
     1124    numBits = 9 
     1125    InsertValueAtLocation( sysEx, DATA_LENGTHS[rmcc], offset, numBits ) 
     1126    offset = offset + numBits 
     1127 
     1128    -- Manufacturer ID - 11 bits 
     1129    numBits = 11 
     1130    InsertValueAtLocation( sysEx, 0x7FF, offset, numBits ) 
     1131    offset = offset + numBits 
     1132 
     1133    -- Function number - 12 bits 
     1134    numBits = 12 
     1135    InsertValueAtLocation( sysEx, rmcc, offset, numBits ) 
     1136    offset = offset + numBits 
     1137 
     1138    -- Data - 32 bits 
     1139    numBits = 32 
     1140    InsertValueAtLocation( sysEx, data, offset, numBits ) 
     1141 
     1142    return sysEx 
     1143end 
     1144 
     1145 
     1146local function AddToSendQueue (packetType, data, optData) 
     1147    optData = optData or {} 
     1148 
     1149    -- Declare often used functions locally for better performance. 
     1150    local table_insert = table.insert 
     1151 
     1152    -- The sync byte 
     1153    local packet = { SYNC_BYTE } 
     1154 
     1155    -- The header 
     1156    table_insert( packet, string.char( #data / 0x100 )) -- First byte of the data length 
     1157    table_insert( packet, string.char( #data % 0x100 )) -- Seconds byte of the data length 
     1158    table_insert( packet, string.char( #optData ))      -- Length of the optional data 
     1159    table_insert( packet, packetType )                  -- The packet type 
     1160    table_insert( packet, ComputeCrc(packet, 2, 4))     -- The header CRC8 
     1161 
     1162    -- The data 
     1163    AppendArray( packet, data )    -- The user data 
     1164    AppendArray( packet, optData ) -- The optional user data 
     1165    table_insert( packet, ComputeCrc( packet, 7, #data + #optData )) -- The data CRC8 
     1166 
     1167    table_insert( g_messageQueue, packet ) 
    9141168    if not F_SENDING_IN_PROGRESS then 
    9151169        SendPacket() 
     
    9191173 
    9201174function SendPacket() 
     1175    -- If we don't have any message to send, return. 
     1176    if #g_messageQueue == 0 then 
     1177        F_SENDING_IN_PROGRESS = false 
     1178        return 
     1179    end 
     1180 
    9211181    F_SENDING_IN_PROGRESS = true 
    9221182 
    923     if F_WAIT_FOR_RESPONSE then 
    924         g_triesCount = g_triesCount + 1 
    925         if g_triesCount < 3 then 
    926             debug( "(EnOceanPlugin::SendPacket) F_WAIT_FOR_RESPONSE flag is set, g_triesCount=".. g_triesCount .."; try again in ".. DELAYS.SEND_NEXT_MESSAGE .." seconds." ) 
    927             luup.call_delay( "SendPacket", DELAYS.SEND_NEXT_MESSAGE ) 
    928             return 
    929         else 
    930             log( "(EnOceanPlugin::SendPacket) Didn't receive response for the last message. Stop waiting for response." ) 
    931             F_WAIT_FOR_RESPONSE = false 
    932             g_triesCount = 0 
    933         end 
    934     end 
    935  
    936     local message 
    937     if #g_messageQueue > 0 then 
    938         message = g_messageQueue[1] 
    939     else 
    940         return 
    941     end 
    942  
    943     debug( "(EnOceanPlugin::SendPacket) Wait for response: ".. message.waitForResponse ) 
    944  
    945     if not luup.io.write( message.content ) then 
     1183    local message = g_messageQueue[1] 
     1184    log( "(EnOceanPlugin::SendPacket) Send message: ".. ConcatTableHex( message )) 
     1185    if not luup.io.write( table.concat( message )) then 
    9461186        ShowSysMessage( "Failed to send message", SYS_MESSAGE_TYPES.ERROR ) 
    9471187        return 
    9481188    end 
    9491189 
    950     F_WAIT_FOR_RESPONSE = message.waitForResponse 
    9511190    table.remove( g_messageQueue, 1 ) 
    9521191 
     
    9731212    -- If we learned any new device, create its Luup counterpart and update the number of EnOcean devices learned. 
    9741213    elseif #g_newDevices > 0 then 
    975         luup.variable_set( SID.ENOCEAN, "NumDevices", g_numDevices, g_parentDevice ) 
     1214        luup.variable_set( SID.ENOCEAN, "MaxID", g_maxId, g_parentDevice ) 
    9761215        local ptr = luup.chdev.start( g_parentDevice ) 
    9771216        for i, v in ipairs( g_newDevices ) do 
     
    9821221    -- We exited Learn Mode and no new device was learned. Display a message and do nothing. 
    9831222    else 
    984         ShowSysMessage( "Exited Learn Mode. No new device was learned." ) 
    985     end 
     1223        ShowSysMessage( "Exited Learn Mode. No new device was learned" ) 
     1224    end 
     1225end 
     1226 
     1227 
     1228function TeachIn (stage, pinCode) 
     1229    g_teachInStage = tonumber( stage, 10 ) or TEACHIN_STAGES.NONE 
     1230 
     1231    log( "(EnOceanPlugin::TeachIn) Stage ".. g_teachInStage .." (".. TEACHIN_STAGE_STRINGS[g_teachInStage] ..")" ) 
     1232    luup.variable_set( SID.ENOCEAN, "TeachInStage", g_teachInStage, g_parentDevice ) 
     1233    ShowSysMessage( "Teach-in stage ".. g_teachInStage .."/".. NUM_TEACHIN_STAGES, SYS_MESSAGE_TYPES.BUSY, true ) 
     1234 
     1235    ------------------------------------------------------------------------------------------------ 
     1236    -- Stage 1: Send 'Unlock' remote management control command (RMCC) 
     1237    ------------------------------------------------------------------------------------------------ 
     1238    if g_teachInStage == TEACHIN_STAGES.SEND_UNLOCK_RMCC then 
     1239 
     1240        pinCode = tonumber( pinCode ) or 0 
     1241        if pinCode == 0 then 
     1242            luup.variable_set( SID.ENOCEAN, "TeachInStage", TEACHIN_STAGES.NONE, g_parentDevice ) 
     1243            ShowSysMessage( "ERROR: missing or invalid PIN code", SYS_MESSAGE_TYPES.ERROR ) 
     1244            return 
     1245        end 
     1246        -- Save the PIN code to be used for the 'Lock' command. 
     1247        luup.variable_set( SID.ENOCEAN, "PinCode", pinCode, g_parentDevice ) 
     1248        ------------------------------------------------------------------------ 
     1249        -- Compose the 'Unlock' command. 
     1250        ------------------------------------------------------------------------ 
     1251        local data = {} 
     1252        -- RORG - 1 byte 
     1253        table.insert( data, TELEGRAM_TYPES["ADT"] ) 
     1254        -- Encapsulate a SYS_EX telegram - 9 bytes 
     1255        AppendArray( data, CreateSysExTelegram( RMCC.UNLOCK, pinCode )) 
     1256        -- Destination ID - 4 bytes 
     1257        AppendArray( data, { 0xFF, 0xFF, 0xFF, 0xFF } ) 
     1258        -- Sender ID - 4 bytes 
     1259        AppendArray( data, GetBaseIdArray() ) 
     1260        -- Status - 1 byte 
     1261        table.insert( data, 0xF ) 
     1262        -- Compose and send the packet. 
     1263        AddToSendQueue( PACKET_TYPES.RADIO, data ) 
     1264        -- Wait a few seconds before moving on to the next stage. 
     1265        log( "(EnOceanPlugin::TeachIn) 'Unlock' command added in the send queue,".. 
     1266            " wait ".. DELAYS.TEACHIN_STAGES .." seconds and move to the next stage" ) 
     1267        luup.call_delay( "TeachIn", DELAYS.TEACHIN_STAGES, TEACHIN_STAGES.SEND_QUERY_ID_RMCC ) 
     1268    ------------------------------------------------------------------------------------------------ 
     1269    -- Stage 2: Send 'Query ID' RMCC 
     1270    ------------------------------------------------------------------------------------------------ 
     1271    elseif g_teachInStage == TEACHIN_STAGES.SEND_QUERY_ID_RMCC then 
     1272 
     1273        local data = {} 
     1274        -- RORG - 1 byte 
     1275        table.insert( data, TELEGRAM_TYPES["ADT"] ) 
     1276        -- Encapsulate a SYS_EX telegram - 9 bytes 
     1277        AppendArray( data, CreateSysExTelegram( RMCC.QUERY_ID, 0 )) 
     1278        -- Destination ID - 4 bytes 
     1279        AppendArray( data, { 0xFF, 0xFF, 0xFF, 0xFF } ) 
     1280        -- Sender ID - 4 bytes 
     1281        AppendArray( data, GetBaseIdArray() ) 
     1282        -- Status - 1 byte 
     1283        table.insert( data, 0xF ) 
     1284        -- Compose and send the packet. 
     1285        AddToSendQueue( PACKET_TYPES.RADIO, data ) 
     1286        -------------------------------------------------------------------------------------------- 
     1287        -- Stage 3: Receive the Query ID answer and create the device. 
     1288        -------------------------------------------------------------------------------------------- 
     1289        g_teachInStage = TEACHIN_STAGES.RECV_QUERY_ID_ANSWER 
     1290        luup.variable_set( SID.ENOCEAN, "TeachInStage", g_teachInStage, g_parentDevice ) 
     1291        ShowSysMessage( "Teach-in stage ".. g_teachInStage .."/".. NUM_TEACHIN_STAGES, SYS_MESSAGE_TYPES.BUSY, true ) 
     1292        log( "(EnOceanPlugin::TeachIn) 'Query ID' command added in the send queue, wait for answer" ) 
     1293    ------------------------------------------------------------------------------------------------ 
     1294    -- Stage 4: Send 'Enter learn mode' remote procedure call (RPC) 
     1295    ------------------------------------------------------------------------------------------------ 
     1296    elseif g_teachInStage == TEACHIN_STAGES.SEND_START_LEARN_RPC then 
     1297 
     1298        local data = {} 
     1299        -- RORG - 1 byte 
     1300        table.insert( data, TELEGRAM_TYPES["ADT"] ) 
     1301        -- Encapsulate a SYS_EX telegram - 9 bytes 
     1302        -- Meaning of 1: EEP=0, Flag=1 (start learn) 
     1303        AppendArray( data, CreateSysExTelegram( RPC.REMOTE_LEARN, 1 )) 
     1304        -- Destination ID - 4 bytes 
     1305        AppendArray( data, { 0xFF, 0xFF, 0xFF, 0xFF } ) 
     1306        -- Sender ID - 4 bytes 
     1307        AppendArray( data, GetBaseIdArray() ) 
     1308        -- Status - 1 byte 
     1309        table.insert( data, 0xF ) 
     1310        -- Compose and send the packet. 
     1311        AddToSendQueue( PACKET_TYPES.RADIO, data ) 
     1312        -- Wait a few seconds before moving on to the next stage. 
     1313        log( "(EnOceanPlugin::TeachIn) 'Remote learn' command added in the send queue,".. 
     1314            " wait ".. DELAYS.TEACHIN_STAGES .." seconds and move to the next stage" ) 
     1315        luup.call_delay( "TeachIn", DELAYS.TEACHIN_STAGES, TEACHIN_STAGES.SEND_TEACHIN_TELEGRAM ) 
     1316    ------------------------------------------------------------------------------------------------ 
     1317    -- Stage 5: Send Teach-in RPS telegram 
     1318    ------------------------------------------------------------------------------------------------ 
     1319    elseif g_teachInStage == TEACHIN_STAGES.SEND_TEACHIN_TELEGRAM then 
     1320 
     1321        local data = {} 
     1322        -- RORG - 1 byte 
     1323        table.insert( data, TELEGRAM_TYPES["RPS"] ) 
     1324        -- Data byte 
     1325        -- Meaning of 0x10: button AI pressed, no 2nd action 
     1326        table.insert( data, 0x10 ) 
     1327        -- Sender ID - 4 bytes 
     1328        AppendArray( data, GetBaseIdArray() ) 
     1329        -- Status - 1 byte 
     1330        -- Meaning of 0x30: T21=1, NU=1 
     1331        table.insert( data, 0x30 ) 
     1332        -- Compose and send the packet. 
     1333        AddToSendQueue( PACKET_TYPES.RADIO, data ) 
     1334        -- Wait a few seconds before moving on to the next stage. 
     1335        log( "(EnOceanPlugin::TeachIn) RPS telegram for teach-in added in the send queue,".. 
     1336            " wait ".. DELAYS.TEACHIN_STAGES .." seconds and move to the next stage" ) 
     1337        luup.call_delay( "TeachIn", DELAYS.TEACHIN_STAGES, TEACHIN_STAGES.SEND_STOP_LEARN_RPC ) 
     1338    ------------------------------------------------------------------------------------------------ 
     1339    -- Stage 6: Send 'Stop learn' RPC 
     1340    ------------------------------------------------------------------------------------------------ 
     1341    elseif g_teachInStage == TEACHIN_STAGES.SEND_STOP_LEARN_RPC then 
     1342 
     1343        local data = {} 
     1344        -- RORG - 1 byte 
     1345        table.insert( data, TELEGRAM_TYPES["ADT"] ) 
     1346        -- Encapsulate a SYS_EX telegram - 9 bytes 
     1347        -- Meaning of 1: EEP=0, Flag=3 (stop learn) 
     1348        AppendArray( data, CreateSysExTelegram( RPC.REMOTE_LEARN, 3 )) 
     1349        -- Destination ID - 4 bytes 
     1350        AppendArray( data, { 0xFF, 0xFF, 0xFF, 0xFF } ) 
     1351        -- Sender ID - 4 bytes 
     1352        AppendArray( data, GetBaseIdArray() ) 
     1353        -- Status - 1 byte 
     1354        table.insert( data, 0xF ) 
     1355        -- Compose and send the packet. 
     1356        AddToSendQueue( PACKET_TYPES.RADIO, data ) 
     1357        -- Wait a few seconds before moving on to the next stage. 
     1358        log( "(EnOceanPlugin::TeachIn) 'Remote learn' command added in the send queue,".. 
     1359            " wait ".. DELAYS.TEACHIN_STAGES .." seconds and move to the next stage" ) 
     1360        luup.call_delay( "TeachIn", DELAYS.TEACHIN_STAGES, TEACHIN_STAGES.SEND_LOCK_RMCC ) 
     1361    ------------------------------------------------------------------------------------------------ 
     1362    -- Stage 7: Send 'Lock' RMCC 
     1363    ------------------------------------------------------------------------------------------------ 
     1364    elseif g_teachInStage == TEACHIN_STAGES.SEND_LOCK_RMCC then 
     1365 
     1366        local pinCode = luup.variable_get( SID.ENOCEAN, "PinCode", g_parentDevice ) 
     1367        pinCode = tonumber(  pinCode, 10 ) or 0 
     1368        if pinCode == 0 then 
     1369            luup.variable_set( SID.ENOCEAN, "TeachInStage", TEACHIN_STAGES.NONE, g_parentDevice ) 
     1370            ShowSysMessage( "ERROR: missing or invalid PIN code", SYS_MESSAGE_TYPES.ERROR ) 
     1371            return 
     1372        end 
     1373        -- Clear the PinCode variable. 
     1374        luup.variable_set( SID.ENOCEAN, "PinCode", "", g_parentDevice ) 
     1375        ------------------------------------------------------------------------ 
     1376        -- Compose the 'Lock' command. 
     1377        ------------------------------------------------------------------------ 
     1378        local data = {} 
     1379        -- RORG - 1 byte 
     1380        table.insert( data, TELEGRAM_TYPES["ADT"] ) 
     1381        -- Encapsulate a SYS_EX telegram - 9 bytes 
     1382        AppendArray( data, CreateSysExTelegram( RMCC.LOCK, pinCode )) 
     1383        -- Destination ID - 4 bytes 
     1384        AppendArray( data, { 0xFF, 0xFF, 0xFF, 0xFF } ) 
     1385        -- Sender ID - 4 bytes 
     1386        AppendArray( data, GetBaseIdArray() ) 
     1387        -- Status - 1 byte 
     1388        table.insert( data, 0xF ) 
     1389        -- Compose and send the packet. 
     1390        AddToSendQueue( PACKET_TYPES.RADIO, data ) 
     1391        -- This was the last stage. 
     1392        log( "(EnOceanPlugin::TeachIn) 'Lock' command added in the send queue. Set stage to NONE" ) 
     1393        luup.variable_set( SID.ENOCEAN, "TeachInStage", TEACHIN_STAGES.NONE, g_parentDevice ) 
     1394    ------------------------------------------------------------------------------------------------ 
     1395    else 
     1396        log( "(EnOceanPlugin::TeachIn) ERROR: invalid stage" ) 
     1397        luup.variable_set( SID.ENOCEAN, "TeachInStage", stage, g_parentDevice ) 
     1398        luup.variable_set( SID.ENOCEAN, "PinCode", "", g_parentDevice ) 
     1399    end 
     1400end 
     1401 
     1402 
     1403-- Send a RPS radio telegram (F6-xx profile). 
     1404function SetTarget (newTargetValue, device) 
     1405    -- Get the destination ID. 
     1406    local destId = luup.devices[device].id:match("^%x%x%-%x%x%-%x%x%-%x%x") 
     1407    if not destId then 
     1408        ShowSysMessage( "ERROR: invalid destination ID", SYS_MESSAGE_TYPES.ERROR ) 
     1409        return 
     1410    end 
     1411    local destIdArray = SplitString( destId, '-' ) 
     1412    -- Convert the elements of the destIdArray to numbers. 
     1413    for i = 1, #destIdArray do 
     1414        destIdArray[i] = tonumber( destIdArray[i], 16 ) or 0 
     1415    end 
     1416 
     1417    ---------------------------------------------------------------------------- 
     1418    -- Compose the data. 
     1419    ---------------------------------------------------------------------------- 
     1420    local data = {} 
     1421    -- RORG - 1 byte 
     1422    table.insert( data, TELEGRAM_TYPES["RPS"] ) 
     1423    -- Data byte 
     1424    if tostring( newTargetValue ) == "1" then 
     1425        -- 0x10: button AI pressed, no 2nd action 
     1426        table.insert( data, 0x10 ) 
     1427    else 
     1428        -- 0x10: button A0 pressed, no 2nd action 
     1429        table.insert( data, 0x30 ) 
     1430    end 
     1431    -- Sender ID - 4 bytes 
     1432    AppendArray( data, GetBaseIdArray() ) 
     1433    -- Status - 1 byte 
     1434    -- Meaning of 0x30: T21=1, NU=1 
     1435    table.insert( data, 0x30 ) 
     1436    ---------------------------------------------------------------------------- 
     1437    -- Compose the optional data. 
     1438    ---------------------------------------------------------------------------- 
     1439    local optData = {} 
     1440    -- The sub-telegram number (apparently it should be 3) 
     1441    table.insert( optData, 0x3 ) 
     1442    -- The destination ID 
     1443    AppendArray( optData, destIdArray ) 
     1444    -- The dBm 
     1445    table.insert( optData, 0xFF ) 
     1446    -- The security level 
     1447    table.insert( optData, 0x0 ) 
     1448    -- Compose and send the packet. 
     1449    AddToSendQueue( PACKET_TYPES.RADIO, data, optData ) 
    9861450end 
    9871451 
     
    9901454-- Startup functions 
    9911455------------------------------------------------------------------------------------------------------------------------ 
     1456 
     1457local function GenerateBaseId() 
     1458    -- Generate a random number in interval [0 - 65535]. 
     1459    -- 65536 is the number of possible base IDs as of current date (20-03-2013). 
     1460    math.randomseed( os.time() ) 
     1461    local multiplier = math.random( (MAX_BASE_ID - MIN_BASE_ID) / BASE_ID_GAP + 1 ) - 1 
     1462    -- The base ID is a number between MIN_BASE_ID and MAX_BASE_ID, and a multiplier of BASE_ID_GAP. 
     1463    return MIN_BASE_ID + BASE_ID_GAP * multiplier 
     1464end 
     1465 
    9921466 
    9931467local function GetChildDevices() 
    9941468    -- Get the number of learned EnOcean devices. 
    995     g_numDevices = luup.variable_get( SID.ENOCEAN, "NumDevices", g_parentDevice ) 
    996     g_numDevices = tonumber( g_numDevices, 10 ) or 0 
     1469    g_maxId = luup.variable_get( SID.ENOCEAN, "MaxID", g_parentDevice ) 
     1470    g_maxId = tonumber( g_maxId, 10 ) or 0 
    9971471    -- Get a list with all our child devices. 
    9981472    for k, v in pairs( luup.devices ) do 
     
    10331507    luup.variable_set( SID.ENOCEAN, "LearnMode", "0", lul_device ) 
    10341508 
     1509    -- Check if we have a base ID. Generate one if we don't. 
     1510    g_baseId = luup.variable_get( SID.ENOCEAN, "BaseID", lul_device ) or "" 
     1511    if g_eoDeviceId == "" then 
     1512        g_baseId = GenerateBaseId() 
     1513        luup.variable_set( SID.ENOCEAN, "BaseID", g_baseId, lul_device ) 
     1514    end 
     1515 
     1516    -- Get the list of the child devices. 
    10351517    GetChildDevices() 
    10361518 
    1037     -- Get the sender ID of the TCM300. 
    1038     --AddToSendQueue( PACKET_TYPES.COMMON_COMMAND, COMMON_COMMAND.CO_RD_IDBASE, "", true ) 
     1519    -- Get the TeachIn-in stage. 
     1520    local teachInStage = luup.variable_get( SID.ENOCEAN, "TeachInStage", lul_device ) 
     1521    teachInStage = tonumber( teachInStage, 10 ) or 0 
     1522    if teachInStage > 0 then 
     1523        if teachInStage == TEACHIN_STAGES.RECV_QUERY_ID_ANSWER then 
     1524            ShowSysMessage( "Teach-in stage ".. teachInStage .."/".. NUM_TEACHIN_STAGES, SYS_MESSAGE_TYPES.BUSY, true ) 
     1525        else 
     1526            TeachIn( teachInStage ) 
     1527        end 
     1528    end 
    10391529 
    10401530    debug( "(EnOceanPlugin::Init) Success: startup successful" ) 
  • /trunk/D_EnOceanGateway1.json

    r20 r30  
    5757                    "left": "1", 
    5858                    "Label": { 
    59                         "lang_tag": "cmd_learn_mode_on", 
     59                        "lang_tag": "button_learn_mode_on", 
    6060                        "text": "Learn" 
    6161                    }, 
     
    8787                    "left": "0", 
    8888                    "Label": { 
    89                         "lang_tag": "cmd_learn_mode_off", 
     89                        "lang_tag": "button_learn_mode_off", 
    9090                        "text": "Normal" 
    9191                    }, 
     
    110110                    }, 
    111111                    "ControlCode": "learn_mode_off" 
     112                }, 
     113                { 
     114                    "ControlType": "label", 
     115                    "Label": { 
     116                        "lang_tag": "label_pin_code", 
     117                        "text": "PIN Code:" 
     118                    }, 
     119                    "Display": { 
     120                        "Top": 105, 
     121                        "Left": 50, 
     122                        "Width": 75, 
     123                        "Height": 20 
     124                    } 
     125                }, 
     126                { 
     127                    "ControlType": "input", 
     128                    "ID": "pinCode", 
     129                    "Display": { 
     130                        "Top": 125, 
     131                        "Left": 50, 
     132                        "Width": 75, 
     133                        "Height": 20 
     134                    }, 
     135                    "ControlCode": "teach_in_input" 
     136                }, 
     137                { 
     138                    "ControlType": "button", 
     139                    "Label": { 
     140                        "lang_tag": "button_teach_in", 
     141                        "text": "Teach-in" 
     142                    }, 
     143                    "Display": { 
     144                        "Top": 125, 
     145                        "Left": 145, 
     146                        "Width": 75, 
     147                        "Height": 20 
     148                    }, 
     149                    "Command": { 
     150                        "Service": "urn:micasaverde-com:serviceId:EnOceanGateway1", 
     151                        "Action": "TeachIn", 
     152                        "Parameters": [ 
     153                            { 
     154                                "Name": "pinCode", 
     155                                "ID": "pinCode" 
     156                            } 
     157                        ] 
     158                    }, 
     159                    "ControlCode": "teach_in" 
    112160                } 
    113161            ] 
Note: See TracChangeset for help on using the changeset viewer.