--[[ Version 1.1 02.11.2021 - some better memory usage ]] Interface = {} function Interface:new(appID, interval) o = {} setmetatable(o, self) self.__index = self o.IN_counter = 0 o.OUT_counter = 0 o.debug__output = false o.appID = appID o.msg_to = "sol.TO_"..o.appID o.msg_from = "sol.FROM_"..o.appID o.cts_msg_to = o.msg_to.."_CTS" o.cts_msg_from = o.msg_from.."_CTS" --reset the counter ac.store(o.msg_to, "#"..o.OUT_counter) ac.store(o.msg_from, "#"..o.IN_counter) o.send__buffer = "" o.last_send = os.clock() o.update_interval = interval or 0.25 --4 times every second per default o.lowPriorityStep = 0 o.lowPriority = 4 o.send_order_list = {} --table of outgoing orders o.order_executor_list = nil --table of functions to handle incomming orders o.clear_send_order_list = false o.executeAfterSendWasReceived = nil o.bResetAfterSendWasRecieved = false return o end function input__test() local msg = "" msg = msg.."#178" msg = msg.."$SET_VALUE:nerd__ambient_adjust.Level:2.00" msg = msg.."$SET_VALUE:nerd__ambient_adjust.Saturation:3.00" msg = msg.."#179" msg = msg.."$SET_VALUE:nerd__ambient_adjust.Hue:0.01" return msg end function Interface:setOrderExecutorList(list) if list then self.order_executor_list = table__deepcopy(list) end end function Interface:is_digit(c) if c=='0' or c=='1' or c=='2' or c=='3' or c=='4' or c=='5' or c=='6' or c=='7' or c=='8' or c=='9' then return true end return false end function Interface:split_string(s, sep) if sep == nil then sep = "%s" end local t={} for str in string.gmatch(s, "([^"..sep.."]+)") do table.insert(t, str) end return t end function Interface:decodeValue(strg) if strg == "true" then return true elseif strg == "false" then return false else return tonumber(strg) end end function Interface:check_order(parts) local order = nil if parts and #parts>0 then if parts[1] == "SET_VALUE" then if parts[2] and parts[3] and parts[4] then order = { type="SET_VALUE", content = { section=parts[2], key=parts[3], order = 0 }, } order.content.value = self:decodeValue(parts[4]) end elseif parts[1] == "INIT_VALUE" then if parts[2] and parts[3] and parts[4] then order = { type="INIT_VALUE", content = { section=parts[2], key=parts[3], order = 0 }, } order.content.value = self:decodeValue(parts[4]) end elseif parts[1] == "CMD" then order = { type="CMD", content = {} } for i=1,#parts-1 do order.content["CMD"..i]=parts[i+1] end else end end return order end function Interface:generate_orders(command) local orders = {} local msg_orders = self:split_string(command, "$") for i, v in ipairs(msg_orders) do local msg_parts = self:split_string(v, ":") local tmp = self:check_order(msg_parts) if tmp then table.insert(orders, tmp) end end return orders end function Interface:execute_orders(orders) if self.order_executor_list then if orders then for i=1,#orders do if self.order_executor_list[orders[i].type] then if self.order_executor_list[orders[i].type].call then _G[self.order_executor_list[orders[i].type].call](orders[i]) end end end end end end function Interface:get_commandos(msg) local commandos = {} local msg_commandos = self:split_string(msg, "#") for i, v in ipairs(msg_commandos) do for ii = 1, #v do -- seperate counter if string.sub(v, ii, ii) == "$" then local temp = string.sub(v, 1, ii-1) if temp and #temp>0 then local new_counter = tonumber(temp) if type(new_counter) == "number" then if new_counter > self.IN_counter or new_counter == 0 then -- counter may reset to 0 table.insert(commandos, string.sub(v, ii, #v)) self.IN_counter = new_counter end end end break end end end return commandos end function Interface:add_order(type, parts) if type and #type>0 then local n = #self.send_order_list local replace = 0 if type=="SET_VALUE" or type == "INIT_VALUE" then if parts and #parts>=3 then for i=1,n do if self.send_order_list[i][1] == type then if self.send_order_list[i][2] == parts[1] then if self.send_order_list[i][3] == parts[2] then replace = i break end end end end local order = ""..tostring(parts[3]) if parts[4] then order = order..":relative" end if replace > 0 then self.send_order_list[replace] = { type, parts[1], parts[2], order } else self.send_order_list[n+1] = { type, parts[1], parts[2], order } end end elseif type=="CMD" then if parts then for i=1,n do local e = 0 if #parts == #self.send_order_list[i]-1 then for ii=1,#parts-1 do if self.send_order_list[i][ii+1] == parts[ii] then e=e+1 end end if e == #parts-1 then replace = i break end end end if replace > 0 then self.send_order_list[replace][#parts+1] = parts[#parts] else self.send_order_list[n+1] = {} self.send_order_list[n+1][1]="CMD" for ii=1,#parts do self.send_order_list[n+1][ii+1] = parts[ii] end end end end end end function Interface:clear_order_list_after_send() self.clear_send_order_list = true end function Interface:receive() local msg = ac.load(self.msg_from) --input__test() if msg and #msg > 0 then local cmds = self:get_commandos(msg) if cmds then for i=1,#cmds do local orders = self:generate_orders(cmds[i]) if orders then self:execute_orders(orders) end end else end end -- tell data was received ac.store(self.cts_msg_from, "OK") -- clear the memory ac.store(self.msg_from, "") end function Interface:build_message_from_table(t, msg) --[[ local k local v local temp = ""..msg if type(t) == "table" then for k, v in pairs(t) do if type(v) == "table" then self:build_message_from_table(v, temp..tostring(k)..":") else self.send__buffer = self.send__buffer.."$"..temp..tostring(k)..":"..tostring(v) if self.debug__output then ac.debug(self.appID+" $"..temp..tostring(k), tostring(v)) end end end end ]] local msg = "" for i=1,#t do msg = msg.."$" for ii=1,#t[i] do if ii>1 then msg = msg..":" end msg = msg..t[i][ii] end end self.send__buffer = self.send__buffer..msg return nil end function Interface:executeFunctionAfterSendWasRecieved(func) self.executeAfterSendWasReceived = func end function Interface:resetAfterSendWasRecieved() self.bResetAfterSendWasRecieved = true end function Interface:send(forced) forced = forced or false local msg = ac.load(self.cts_msg_to) if forced or (msg and #msg > 0 and msg == "OK") then if #self.send_order_list > 0 then self:build_message_from_table(self.send_order_list, "") --_l_to_configAPP_orders = {} if self.send__buffer and #self.send__buffer>0 then self.OUT_counter = self.OUT_counter + 1 if self.OUT_counter > 10000 then self.OUT_counter = 0 end local msg_count = "#"..tostring(self.OUT_counter) ac.store(self.msg_to, msg_count..self.send__buffer) self.send__buffer = "" -- reset CTS status ac.store(self.cts_msg_to, "WAITING") --if self.clear_send_order_list then self.send_order_list = {} self.clear_send_order_list = false --end end end --elseif (msg and #msg > 0 and msg == "SLEEPING") end end function Interface:update() if os.clock() - self.last_send > self.update_interval then local msg self:receive() self:send() self.last_send = os.clock() if self.executeAfterSendWasReceived then msg = ac.load(self.cts_msg_to) if msg == "OK" then -- tell data was received ac.store(self.cts_msg_from, "RESET") self.executeAfterSendWasReceived() end elseif self.bResetAfterSendWasRecieved then msg = ac.load(self.cts_msg_to) if msg == "OK" then self.OUT_counter = self.OUT_counter + 1 local msg_count = "#"..tostring(self.OUT_counter) ac.store(self.cts_msg_from, "RESET") ac.store(self.msg_to, msg_count.."$CMD:system:ResetWeatherFX") end end -- tidy up memory after massive string usage collectgarbage() end end