From 950cd725256ec7895935e9fc994bc7d045e259f4 Mon Sep 17 00:00:00 2001 From: Roy Gollub Date: Wed, 14 Aug 2024 14:49:18 +0200 Subject: [PATCH 1/4] # Release 4.0.0 ## New features - Supports FlowConfig feature to link data provider to put data to FTP server - Provide version of module via 'OnNewStatusModuleVersion' - Check if features of module can be used on device and provide this via 'OnNewStatusModuleIsActive' event / 'getStatusModuleActive' function - Function 'getParameters' to provide PersistentData parameters - It is now possible to register to events to put received data (JPG or DATA) to the FTP server (see 'addRegistration') ## Improvements - New UI design available (e.g. selectable via CSK_Module_PersistentData v4.1.0 or higher), see 'OnNewStatusCSKStyle' - 'loadParameters' returns its success - 'sendParameters' can control if sent data should be saved directly by CSK_Module_PersistentData - Changed log level of some messages from 'info' to 'fine' - Added UI icon and browser tab information --- CHANGELOG.md | 16 + .../assets/CSK_Module_FTPClient/UI_sample.png | Bin 0 -> 30963 bytes .../CSK_Module_FTPClient.css | 73 +- .../CSK_Module_FTPClient.html | 707 ++++-- CSK_Module_FTPClient/pages/src/converter.ts | 61 + CSK_Module_FTPClient/pages/src/index.ts | 9 + CSK_Module_FTPClient/project.mf.xml | 134 +- .../scripts/CSK_Module_FTPClient.lua | 5 + .../FTPClient/FTPClient_Controller.lua | 220 +- .../FTPClient/FTPClient_Model.lua | 147 +- .../FlowConfig/FTPClient_FlowConfig.lua | 16 + .../FTPClient/FlowConfig/FTPClient_Put.lua | 87 + .../FTPClient/helper/checkAPIs.lua | 14 +- .../Communication/FTPClient/helper/funcs.lua | 27 + README.md | 10 +- docu/CSK_Module_FTPClient.html | 2006 ++++++++++++++--- docu/media/UI_Screenshot.png | Bin 16674 -> 43393 bytes docu/media/src/UI_sample.pptx | Bin 0 -> 75130 bytes 18 files changed, 2931 insertions(+), 601 deletions(-) create mode 100644 CSK_Module_FTPClient/pages/assets/CSK_Module_FTPClient/UI_sample.png create mode 100644 CSK_Module_FTPClient/scripts/Communication/FTPClient/FlowConfig/FTPClient_FlowConfig.lua create mode 100644 CSK_Module_FTPClient/scripts/Communication/FTPClient/FlowConfig/FTPClient_Put.lua create mode 100644 docu/media/src/UI_sample.pptx diff --git a/CHANGELOG.md b/CHANGELOG.md index 486947e..5331552 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ # Changelog All notable changes to this project will be documented in this file. +## Release 4.0.0 + +### New features +- Supports FlowConfig feature to link data provider to put data to FTP server +- Provide version of module via 'OnNewStatusModuleVersion' +- Check if features of module can be used on device and provide this via 'OnNewStatusModuleIsActive' event / 'getStatusModuleActive' function +- Function 'getParameters' to provide PersistentData parameters +- It is now possible to register to events to put received data (JPG or DATA) to the FTP server (see 'addRegistration') + +### Improvements +- New UI design available (e.g. selectable via CSK_Module_PersistentData v4.1.0 or higher), see 'OnNewStatusCSKStyle' +- 'loadParameters' returns its success +- 'sendParameters' can control if sent data should be saved directly by CSK_Module_PersistentData +- Changed log level of some messages from 'info' to 'fine' +- Added UI icon and browser tab information + ## Release 3.0.0 ### Improvements diff --git a/CSK_Module_FTPClient/pages/assets/CSK_Module_FTPClient/UI_sample.png b/CSK_Module_FTPClient/pages/assets/CSK_Module_FTPClient/UI_sample.png new file mode 100644 index 0000000000000000000000000000000000000000..a9f10184aaba1e0eef393977cb13fa2e4fdd43db GIT binary patch literal 30963 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FTP Passive Mode - - - - - - - Asynchronous mode: - - - - - - - Verbose Mode - - - - - - - Current Status - - - - - Connect - - - - - Disconnect - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Load Config - - - - - - - - - Save Config - - - - - - - - - - - - - - - - - - Please login with user level Maintenance to see this page. - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FTP Passive Mode + + + + + + + + + Asynchronous mode: + + + + + + + + + Verbose Mode + + + + + + + + + + + + + + + + + + Disconnect + + + + + + + Connect + + + + + + + + + + + Current Status + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DATA + + + JPG + + + + + + + + Auto name + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Please login with user level Maintenance to see this page. + + + + + + + + + + + + + Module is not supported on this device... + + UI sample + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CSK_Module_FTPClient/pages/src/converter.ts b/CSK_Module_FTPClient/pages/src/converter.ts index 5556714..966cb20 100644 --- a/CSK_Module_FTPClient/pages/src/converter.ts +++ b/CSK_Module_FTPClient/pages/src/converter.ts @@ -1,3 +1,64 @@ export function convertToList(value) { return JSON.parse(value) +} + +export function changeStyle(theme) { + const style: HTMLStyleElement = document.createElement('style'); + style.id ='blub' + if (theme == 'CSK_Style'){ + var headerToolbar = `.sopasjs-ui-header-toolbar-wrapper { background-color: #FFFFFF; }` + var uiHeader = `.sopasjs-ui-header>.app-logo { margin-right:0px; }` + var appLogo = `.app-logo { background-color:#FFFFFF; background-image: url(data:image/png;base64,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) }` + var uiNavbar =`.sopasjs-ui-navbar-wrapper { background-color: #737F85; }` + var navbarMenuLiActive = `.sopasjs-navbar-menu>li.active { background-color: #283c45; }` + var navbarMenuLiActiveA = `.sopasjs-navbar-menu>li.active>a { background-color: #283c45; }` + var navbarMeluLi = `.sopasjs-navbar-menu>li { color: #FFFFFF; }` + var navbarMeluLiA = `.sopasjs-navbar-menu>li>a { color: #FFFFFF; }` + var headerToolbarButtonHighlight = `.sopasjs-ui-header-toolbar-button.sopasjs-ui-navigation-navbutton>a.highlight { background-color: #737F85; }` + var toolbarButton = `.sopasjs-ui-header-toolbar-button>a { color: #283c45; }` + + var customBackground = `.CSK_Module_FTPClient .myCustomBackground_CSK_Module_FTPClient { background-color: #737F8522; }` // font-family: "Open Sans"; }` + + style.innerHTML = headerToolbar; + style.innerHTML += uiHeader; + style.innerHTML += appLogo; + style.innerHTML += uiNavbar; + style.innerHTML += navbarMenuLiActive; + style.innerHTML += navbarMenuLiActiveA; + style.innerHTML += navbarMeluLi; + style.innerHTML += navbarMeluLiA; + style.innerHTML += headerToolbarButtonHighlight; + style.innerHTML += toolbarButton; + + style.innerHTML += customBackground; + } + else if (theme == 'None'){ + var headerToolbar = `.sopasjs-ui-header-toolbar-wrapper { background-color: #007fc3; }` + var uiHeader = `.sopasjs-ui-header>.app-logo { margin-right:10px; }` + var appLogo = `.app-logo { background-color:#007fc3; background-image: url('data:image/png;base64,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') }` + var uiNavbar =`.sopasjs-ui-navbar-wrapper { background-color: #f6f8f9; }` + var navbarMenuLiActive = `.sopasjs-navbar-menu>li.active { background-color: #007fc3; }` + var navbarMenuLiActiveA = `.sopasjs-navbar-menu>li.active>a { background-color: #007fc3; }` + var navbarMeluLi = `.sopasjs-navbar-menu>li { color: #697987; }` + var navbarMeluLiA = `.sopasjs-navbar-menu>li>a { color: #505f6b; }` + var headerToolbarButtonHighlight = `.sopasjs-ui-header-toolbar-button.sopasjs-ui-navigation-navbutton>a.highlight { background-color: #006093; }` + var toolbarButton = `.sopasjs-ui-header-toolbar-button>a { color: #cce5f3; }` + + var customBackground = `.CSK_Module_FTPClient .myCustomBackground_CSK_Module_FTPClient { background-color: #fff; }` // font-family: "sans-serif"; }` + + style.innerHTML = headerToolbar; + style.innerHTML += uiHeader; + style.innerHTML += appLogo; + style.innerHTML += uiNavbar; + style.innerHTML += navbarMenuLiActive; + style.innerHTML += navbarMenuLiActiveA; + style.innerHTML += navbarMeluLi; + style.innerHTML += navbarMeluLiA; + style.innerHTML += headerToolbarButtonHighlight; + style.innerHTML += toolbarButton; + + style.innerHTML += customBackground; + } + document.head.append(style); + return theme } \ No newline at end of file diff --git a/CSK_Module_FTPClient/pages/src/index.ts b/CSK_Module_FTPClient/pages/src/index.ts index e69de29..af9c77a 100644 --- a/CSK_Module_FTPClient/pages/src/index.ts +++ b/CSK_Module_FTPClient/pages/src/index.ts @@ -0,0 +1,9 @@ +document.addEventListener('sopasjs-ready', () => { + const page_1 = document.querySelector('div.sopasjs-ui-navbar-wrapper > div > ul > li:nth-child(3) > a > i'); + page_1.classList.remove('fa-file'); + page_1.classList.add('fa-database'); + + setTimeout(() => { + document.title = 'CSK_Module_FTPClient' + }, 500); +}) \ No newline at end of file diff --git a/CSK_Module_FTPClient/project.mf.xml b/CSK_Module_FTPClient/project.mf.xml index df580da..53ca510 100644 --- a/CSK_Module_FTPClient/project.mf.xml +++ b/CSK_Module_FTPClient/project.mf.xml @@ -26,7 +26,15 @@ E.g. via function "sendData", "sendImage" + Per default this module sends the content via FTP and waits till the process was finished. + If it is supported by the device and firmware, it is also possible to use the asynchronous mode. + It will send the content then in its own thread, but other apps can further run in parallel. + -CSK_FTPClient.setAsyncMode(status) + +CSK_FTPClient.setAsyncMode(status) + +{empty} + +**5) Register to events to receive data** + +It is possible to register to events of other apps to put the received data to the FTP server via 'addRegistration'. + + + Type of data to save on FTP server. + DATA + JPG + Notify FTP server IP. @@ -95,6 +103,38 @@ CSK_FTPClient.setAsyncMode(status) + Notify if FTP connection is running in verbose mode. + + Notify preset name of event to register for incoming data. + + + + Notify preset type of data for registered event. + + + + Notify JSON list of registered events incl. related type of data. + + + + Notify if filename should be generated by timestamp. + + + + Notify if FlowConfig should have priority for FlowConfig relevant configurations. + + + + Notify UI style to use for CSK modules. + + + + Notify version of module. + + + + Notify if module can be used on device. + + Function to register "OnResume" of the module UI (only as helper function). @@ -170,9 +210,7 @@ If TRUE it will send the content in its own thread, but other apps can further r Send parameters to CSK_PersistentData module if possible to save them. - - - Load parameters for this module from the CSK_PersistentData module if possible and use them. + Send data to connected FTP server. @@ -195,10 +233,96 @@ If TRUE it will send the content in its own thread, but other apps can further r Function to configure verbose mode of FTP connection. + + Function to preset event name to register to receive content to save on FTP server. + + + + Function to preconfigure type of data which is received via event to store on FTP server. + + + + Function to register to preconfigured event via UI. + + + Function to set selection via UI table. + + + + Function to delete via UI selected event registration. + + + Function to delete event registration. + + + + Function to add event registration. + + + + + + Function to set status if filename of received data (via registered events) to put to FTP server should be generated by timestamp. + + + + Function to clear FlowConfig relevant configurations. + + + Function to configure if FlowConfig should have priority for FlowConfig relevant configuration. + + + + Function to get status if module is active. + + + + Load parameters for this module from the CSK_PersistentData module if possible and use them. + + + + released + Function to get all parameters of the client in JSON format. + + + + Function to reset main configuration of module. + + + released + Crown to provide CSK_FlowConfig relevant features. + + + Set source of incoming data to put to FTP server. + + + Internally used CSK_FlowConfig create function. + + + + + + + + + + + + data-flow + Set source of incoming data to put to FTP server. + + + + + + + + + SICK AG - 3.0.0 + 4.0.0 low false false diff --git a/CSK_Module_FTPClient/scripts/CSK_Module_FTPClient.lua b/CSK_Module_FTPClient/scripts/CSK_Module_FTPClient.lua index 2fa969b..c5ec72f 100644 --- a/CSK_Module_FTPClient/scripts/CSK_Module_FTPClient.lua +++ b/CSK_Module_FTPClient/scripts/CSK_Module_FTPClient.lua @@ -42,6 +42,11 @@ _G.logHandle:applyConfig() -- Loading script regarding ftpClient_Model -- Check this script regarding ftpClient_Model parameters and functions _G.ftpClient_Model = require('Communication/FTPClient/FTPClient_Model') +require('Communication/FTPClient/FlowConfig/FTPClient_FlowConfig') + +if _G.availableAPIs.default == false or _G.availableAPIs.specific == false then + _G.logger:warning("CSK_FTPClient: Relevant CROWN(s) not available on device. Module is not supported...") +end --************************************************************************** --**********************End Global Scope *********************************** diff --git a/CSK_Module_FTPClient/scripts/Communication/FTPClient/FTPClient_Controller.lua b/CSK_Module_FTPClient/scripts/Communication/FTPClient/FTPClient_Controller.lua index 641847f..55e177c 100644 --- a/CSK_Module_FTPClient/scripts/Communication/FTPClient/FTPClient_Controller.lua +++ b/CSK_Module_FTPClient/scripts/Communication/FTPClient/FTPClient_Controller.lua @@ -20,6 +20,10 @@ local ftpClient_Model -- ************************ UI Events Start ******************************** +Script.serveEvent('CSK_FTPClient.OnNewStatusModuleVersion', 'FTPClient_OnNewStatusModuleVersion') +Script.serveEvent('CSK_FTPClient.OnNewStatusCSKStyle', 'FTPClient_OnNewStatusCSKStyle') +Script.serveEvent('CSK_FTPClient.OnNewStatusModuleIsActive', 'FTPClient_OnNewStatusModuleIsActive') + Script.serveEvent("CSK_FTPClient.OnNewServerIP", "FTPClient_OnNewServerIP") Script.serveEvent("CSK_FTPClient.OnNewPort", "FTPClient_OnNewPort") Script.serveEvent("CSK_FTPClient.OnNewStatusConnected", "FTPClient_OnNewStatusConnected") @@ -31,6 +35,13 @@ Script.serveEvent('CSK_FTPClient.OnNewStatusVerboseMode', 'FTPClient_OnNewStatus Script.serveEvent("CSK_FTPClient.OnNewIPCheck", "FTPClient_OnNewIPCheck") +Script.serveEvent('CSK_FTPClient.OnNewStatusRegisteredEventName', 'FTPClient_OnNewStatusRegisteredEventName') +Script.serveEvent('CSK_FTPClient.OnNewStatusDataType', 'FTPClient_OnNewStatusDataType') +Script.serveEvent('CSK_FTPClient.OnNewStatusAutoFilename', 'FTPClient_OnNewStatusAutoFilename') + +Script.serveEvent('CSK_FTPClient.OnNewStatusRegistrationList', 'FTPClient_OnNewStatusRegistrationList') + +Script.serveEvent('CSK_FTPClient.OnNewStatusFlowConfigPriority', 'FTPClient_OnNewStatusFlowConfigPriority') Script.serveEvent("CSK_FTPClient.OnNewStatusLoadParameterOnReboot", "FTPClient_OnNewStatusLoadParameterOnReboot") Script.serveEvent("CSK_FTPClient.OnPersistentDataModuleAvailable", "FTPClient_OnPersistentDataModuleAvailable") Script.serveEvent("CSK_FTPClient.OnNewParameterName", "FTPClient_OnNewParameterName") @@ -131,6 +142,10 @@ local function handleOnExpiredTmrFTPClient() updateUserLevel() + Script.notifyEvent("FTPClient_OnNewStatusModuleVersion", ftpClient_Model.version) + Script.notifyEvent("FTPClient_OnNewStatusCSKStyle", ftpClient_Model.styleForUI) + Script.notifyEvent("FTPClient_OnNewStatusModuleIsActive", _G.availableAPIs.default and _G.availableAPIs.specific) + Script.notifyEvent('FTPClient_OnNewServerIP', ftpClient_Model.parameters.serverIP) Script.notifyEvent('FTPClient_OnNewPort', ftpClient_Model.parameters.port) Script.notifyEvent('FTPClient_OnNewUsername', ftpClient_Model.parameters.user) @@ -138,10 +153,20 @@ local function handleOnExpiredTmrFTPClient() Script.notifyEvent('FTPClient_OnNewPassiveModeStatus', ftpClient_Model.parameters.passiveMode) Script.notifyEvent('FTPClient_OnNewStatusAsyncMode', ftpClient_Model.parameters.asyncMode) Script.notifyEvent('FTPClient_OnNewStatusVerboseMode', ftpClient_Model.parameters.verboseMode) - Script.notifyEvent('FTPClient_OnNewStatusConnected', ftpClient_Model.ftpClient:isConnected()) + if _G.availableAPIs.specific == true then + Script.notifyEvent('FTPClient_OnNewStatusConnected', ftpClient_Model.ftpClient:isConnected()) + end + + Script.notifyEvent("FTPClient_OnNewStatusRegisteredEventName", ftpClient_Model.registeredEventName) + Script.notifyEvent("FTPClient_OnNewStatusDataType", ftpClient_Model.dataType) + Script.notifyEvent("FTPClient_OnNewStatusAutoFilename", ftpClient_Model.autoFilename) + Script.notifyEvent("FTPClient_OnNewStatusRegistrationList", ftpClient_Model.helperFuncs.createSpecificJsonList(ftpClient_Model.parameters.registeredEvents, ftpClient_Model.eventSelection)) + + Script.notifyEvent("FTPClient_OnNewStatusFlowConfigPriority", ftpClient_Model.parameters.flowConfigPriority) Script.notifyEvent('FTPClient_OnNewStatusLoadParameterOnReboot', ftpClient_Model.parameterLoadOnReboot) Script.notifyEvent('FTPClient_OnPersistentDataModuleAvailable', ftpClient_Model.persistentModuleAvailable) Script.notifyEvent("FTPClient_OnNewParameterName", ftpClient_Model.parametersName) + end Timer.register(tmrFTPClient, "OnExpired", handleOnExpiredTmrFTPClient) @@ -187,7 +212,7 @@ Script.serveFunction("CSK_FTPClient.getFTPStatus", getFTPStatus) local function setFTPServerIP(ip) if checkIP(ip) == true then ftpClient_Model.parameters.serverIP = ip - _G.logger:info(nameOfModule .. ': Set FTP server IP to: ' .. ip) + _G.logger:fine(nameOfModule .. ': Set FTP server IP to: ' .. ip) Script.notifyEvent('FTPClient_OnNewIPCheck', false) else _G.logger:warning(nameOfModule .. ': Not possible to set FTP server IP to: ' .. ip) @@ -202,7 +227,7 @@ end Script.serveFunction("CSK_FTPClient.getFTPServerIP", getFTPServerIP) local function setFTPPort(port) - _G.logger:info(nameOfModule .. ': Set FTP port to: ' .. tostring(port)) + _G.logger:fine(nameOfModule .. ': Set FTP port to: ' .. tostring(port)) ftpClient_Model.parameters.port = port end Script.serveFunction("CSK_FTPClient.setFTPPort", setFTPPort) @@ -213,7 +238,7 @@ end Script.serveFunction("CSK_FTPClient.getFTPPort", getFTPPort) local function setUsername(user) - _G.logger:info(nameOfModule .. ': Set username to: ' .. tostring(user)) + _G.logger:fine(nameOfModule .. ': Set username to: ' .. tostring(user)) ftpClient_Model.parameters.user = user end Script.serveFunction("CSK_FTPClient.setUsername", setUsername) @@ -224,7 +249,7 @@ end Script.serveFunction("CSK_FTPClient.getUsername", getUsername) local function setPassword(password) - _G.logger:info(nameOfModule .. ': Set password.') + _G.logger:fine(nameOfModule .. ': Set password.') ftpClient_Model.parameters.password = password end Script.serveFunction("CSK_FTPClient.setPassword", setPassword) @@ -235,7 +260,7 @@ end Script.serveFunction("CSK_FTPClient.getPassword", getPassword) local function setPassiveMode(status) - _G.logger:info(nameOfModule .. ': Set passive mode to: ' .. tostring(status)) + _G.logger:fine(nameOfModule .. ': Set passive mode to: ' .. tostring(status)) ftpClient_Model.parameters.passiveMode = status end Script.serveFunction("CSK_FTPClient.setPassiveMode", setPassiveMode) @@ -246,7 +271,7 @@ end Script.serveFunction("CSK_FTPClient.getPassiveMode", getPassiveMode) local function setAsyncMode(status) - _G.logger:info(nameOfModule .. ': Set async mode to: ' .. tostring(status)) + _G.logger:fine(nameOfModule .. ': Set async mode to: ' .. tostring(status)) ftpClient_Model.parameters.asyncMode = status end Script.serveFunction("CSK_FTPClient.setAsyncMode", setAsyncMode) @@ -257,7 +282,7 @@ end Script.serveFunction("CSK_FTPClient.getAsyncMode", getAsyncMode) local function setImageName (imageName) - _G.logger:info(nameOfModule .. ': Set image name to: ' .. tostring(imageName)) + _G.logger:fine(nameOfModule .. ': Set image name to: ' .. tostring(imageName)) ftpClient_Model.parameters.imageName = imageName end Script.serveFunction("CSK_FTPClient.setImageName", setImageName) @@ -268,27 +293,140 @@ end Script.serveFunction("CSK_FTPClient.getImageName", getImageName) local function setVerboseMode(status) - _G.logger:info(nameOfModule .. ': Set verbose mode to: ' .. tostring(status)) + _G.logger:fine(nameOfModule .. ': Set verbose mode to: ' .. tostring(status)) ftpClient_Model.parameters.verboseMode = status end Script.serveFunction('CSK_FTPClient.setVerboseMode', setVerboseMode) +local function setRegistereEventName(name) + _G.logger:fine(nameOfModule .. ': Set eventname to: ' .. tostring(name)) + ftpClient_Model.registeredEventName = name +end +Script.serveFunction('CSK_FTPClient.setRegistereEventName', setRegistereEventName) + +local function setDataType(dataType) + _G.logger:fine(nameOfModule .. ': Set dataType to: ' .. tostring(dataType)) + ftpClient_Model.dataType = dataType +end +Script.serveFunction('CSK_FTPClient.setDataType', setDataType) + +local function setAutoFilename(status) + _G.logger:fine(nameOfModule .. ': Set autoFilename status to: ' .. tostring(status)) + ftpClient_Model.autoFilename = status +end +Script.serveFunction('CSK_FTPClient.setAutoFilename', setAutoFilename) + +local function addRegistration(eventName, dataType, autoFilename) + + if not ftpClient_Model.parameters.registeredEvents[eventName] then + ftpClient_Model.parameters.registeredEvents[eventName] = {} + ftpClient_Model.parameters.registeredEvents[eventName].eventName = eventName + ftpClient_Model.parameters.registeredEvents[eventName].dataType = dataType + ftpClient_Model.parameters.registeredEvents[eventName].autoFilename = autoFilename + + ftpClient_Model.registerEvent(eventName, dataType, autoFilename) + else + _G.logger:fine(nameOfModule .. ": Event already exists") + end + handleOnExpiredTmrFTPClient() +end +Script.serveFunction('CSK_FTPClient.addRegistration', addRegistration) + +local function addRegistrationViaUI() + addRegistration(ftpClient_Model.registeredEventName, ftpClient_Model.dataType, ftpClient_Model.autoFilename) +end +Script.serveFunction('CSK_FTPClient.addRegistrationViaUI', addRegistrationViaUI) + +local function deleteRegistration(eventName) + if ftpClient_Model.parameters.registeredEvents[eventName] then + ftpClient_Model.deregisterEvent(eventName, ftpClient_Model.parameters.registeredEvents[eventName].dataType, ftpClient_Model.parameters.registeredEvents[eventName].autoFilename) + ftpClient_Model.parameters.registeredEvents[eventName] = nil + else + _G.logger:fine(nameOfModule .. ": Registration does not exists") + end + handleOnExpiredTmrFTPClient() +end +Script.serveFunction('CSK_FTPClient.deleteRegistration', deleteRegistration) + +local function deleteRegistrationViaUI() + deleteRegistration(ftpClient_Model.eventSelection) +end +Script.serveFunction('CSK_FTPClient.deleteRegistrationViaUI', deleteRegistrationViaUI) + +--- Function to check if selection in UIs DynamicTable can find related pattern +---@param selection string Full text of selection +---@param pattern string Pattern to search for +local function checkSelection(selection, pattern) + if selection ~= "" then + local _, pos = string.find(selection, pattern) + if pos == nil then + else + pos = tonumber(pos) + if pattern ~= '"selected":true' then + local endPos = string.find(selection, '"', pos+1) + local tempSelection = string.sub(selection, pos+1, endPos-1) + if tempSelection ~= nil and tempSelection ~= '-' then + return tempSelection + end + else + return '' + end + end + end + return nil +end + +local function setUITableSelection(selection) + local tempSelection = checkSelection(selection, '"DTC_EventName":"') + if tempSelection then + local isSelected = checkSelection(selection, '"selected":true') + if isSelected then + _G.logger:fine(nameOfModule .. ": Selected event " .. tostring(tempSelection)) + ftpClient_Model.eventSelection = tempSelection + else + ftpClient_Model.eventSelection = '' + end + handleOnExpiredTmrFTPClient() + end + +end +Script.serveFunction('CSK_FTPClient.setUITableSelection', setUITableSelection) + +local function getStatusModuleActive() + return _G.availableAPIs.default and _G.availableAPIs.specific +end +Script.serveFunction('CSK_FTPClient.getStatusModuleActive', getStatusModuleActive) + +local function clearFlowConfigRelevantConfiguration() + for key, value in pairs(ftpClient_Model.parameters.registeredEvents) do + deleteRegistration(key) + end +end +Script.serveFunction('CSK_FTPClient.clearFlowConfigRelevantConfiguration', clearFlowConfigRelevantConfiguration) + +local function getParameters() + return ftpClient_Model.helperFuncs.json.encode(ftpClient_Model.parameters) +end +Script.serveFunction('CSK_FTPClient.getParameters', getParameters) + -- ***************************************************************** -- Following functions can be adapted for CSK_PersistentData module usage -- ***************************************************************** local function setParameterName(name) ftpClient_Model.parametersName = name - _G.logger:info(nameOfModule .. ': Set parameter name to: ' .. tostring(name)) + _G.logger:fine(nameOfModule .. ': Set parameter name to: ' .. tostring(name)) end Script.serveFunction("CSK_FTPClient.setParameterName", setParameterName) -local function sendParameters() +local function sendParameters(noDataSave) if ftpClient_Model.persistentModuleAvailable then CSK_PersistentData.addParameter(ftpClient_Model.helperFuncs.convertTable2Container(ftpClient_Model.parameters), ftpClient_Model.parametersName) CSK_PersistentData.setModuleParameterName(nameOfModule, ftpClient_Model.parametersName, ftpClient_Model.parameterLoadOnReboot) - _G.logger:info(nameOfModule .. ": Send FTPClient parameters with name '" .. ftpClient_Model.parametersName .. "' to CSK_PersistentData module.") - CSK_PersistentData.saveData() + _G.logger:fine(nameOfModule .. ": Send FTPClient parameters with name '" .. ftpClient_Model.parametersName .. "' to CSK_PersistentData module.") + if not noDataSave then + CSK_PersistentData.saveData() + end else _G.logger:warning(nameOfModule .. ": CSK_PersistentData module not available.") end @@ -301,51 +439,79 @@ local function loadParameters() if data then _G.logger:info(nameOfModule .. ": Loaded parameters from CSK_PersistentData module.") ftpClient_Model.parameters = ftpClient_Model.helperFuncs.convertContainer2Table(data) + ftpClient_Model.deregisterAllEvents() + ftpClient_Model.registerAllEvents() if ftpClient_Model.parameters.isConnected then CSK_FTPClient.connectFTPClient() end CSK_FTPClient.pageCalled() + return true else _G.logger:warning(nameOfModule .. ": Loading parameters from CSK_PersistentData module did not work.") + return false end else _G.logger:warning(nameOfModule .. ": CSK_PersistentData module not available.") + return false end end Script.serveFunction("CSK_FTPClient.loadParameters", loadParameters) local function setLoadOnReboot(status) ftpClient_Model.parameterLoadOnReboot = status - _G.logger:info(nameOfModule .. ": Set new status to load setting on reboot: " .. tostring(status)) + _G.logger:fine(nameOfModule .. ": Set new status to load setting on reboot: " .. tostring(status)) + Script.notifyEvent("FTPClient_OnNewStatusLoadParameterOnReboot", status) end Script.serveFunction("CSK_FTPClient.setLoadOnReboot", setLoadOnReboot) +local function setFlowConfigPriority(status) + ftpClient_Model.parameters.flowConfigPriority = status + _G.logger:fine(nameOfModule .. ": Set new status of FlowConfig priority: " .. tostring(status)) + Script.notifyEvent("FTPClient_OnNewStatusFlowConfigPriority", ftpClient_Model.parameters.flowConfigPriority) +end +Script.serveFunction('CSK_FTPClient.setFlowConfigPriority', setFlowConfigPriority) + --- Function to react on initial load of persistent parameters local function handleOnInitialDataLoaded() - _G.logger:info(nameOfModule .. ': Try to initially load parameter from CSK_PersistentData module.') + if _G.availableAPIs.default and _G.availableAPIs.specific then + _G.logger:fine(nameOfModule .. ': Try to initially load parameter from CSK_PersistentData module.') - if string.sub(CSK_PersistentData.getVersion(), 1, 1) == '1' then + if string.sub(CSK_PersistentData.getVersion(), 1, 1) == '1' then - _G.logger:warning(nameOfModule .. ': CSK_PersistentData module is too old and will not work. Please update CSK_PersistentData module.') - ftpClient_Model.persistentModuleAvailable = false - else + _G.logger:warning(nameOfModule .. ': CSK_PersistentData module is too old and will not work. Please update CSK_PersistentData module.') + ftpClient_Model.persistentModuleAvailable = false + else - local parameterName, loadOnReboot = CSK_PersistentData.getModuleParameterName(nameOfModule) + local parameterName, loadOnReboot = CSK_PersistentData.getModuleParameterName(nameOfModule) - if parameterName then - ftpClient_Model.parametersName = parameterName - ftpClient_Model.parameterLoadOnReboot = loadOnReboot - end + if parameterName then + ftpClient_Model.parametersName = parameterName + ftpClient_Model.parameterLoadOnReboot = loadOnReboot + end - if ftpClient_Model.parameterLoadOnReboot then - loadParameters() + if ftpClient_Model.parameterLoadOnReboot then + loadParameters() + end + Script.notifyEvent('FTPClient_OnDataLoadedOnReboot') end - Script.notifyEvent('FTPClient_OnDataLoadedOnReboot') end end Script.register("CSK_PersistentData.OnInitialDataLoaded", handleOnInitialDataLoaded) +local function resetModule() + if _G.availableAPIs.default and _G.availableAPIs.specific then + clearFlowConfigRelevantConfiguration() + local connected = ftpClient_Model.ftpClient:isConnected() + if connected then + disconnectFTPClient() + end + pageCalled() + end +end +Script.serveFunction('CSK_FTPClient.resetModule', resetModule) +Script.register("CSK_PersistentData.OnResetAllModules", resetModule) + -- ************************************************* -- END of functions for CSK_PersistentData module usage -- ************************************************* diff --git a/CSK_Module_FTPClient/scripts/Communication/FTPClient/FTPClient_Model.lua b/CSK_Module_FTPClient/scripts/Communication/FTPClient/FTPClient_Model.lua index d3fbe16..50a5afc 100644 --- a/CSK_Module_FTPClient/scripts/Communication/FTPClient/FTPClient_Model.lua +++ b/CSK_Module_FTPClient/scripts/Communication/FTPClient/FTPClient_Model.lua @@ -31,22 +31,35 @@ setFTPClient_Model_Handle(ftpClient_Model) ftpClient_Model.helperFuncs = require('Communication/FTPClient/helper/funcs') -- Create a FTP client instance -ftpClient_Model.ftpClient = FTPClient.create() -- FTP client to use for FTP connection +if _G.availableAPIs.specific == true then + ftpClient_Model.ftpClient = FTPClient.create() -- FTP client to use for FTP connection + ftpClient_Model.formatter = Image.Format.JPEG.create() -- Formatter instance (JPG per default) + + -- Function to be processed asynchronously if 'asyncMode' is used + ftpClient_Model.async = Engine.AsyncFunction.create() + ftpClient_Model.async:setFunction("FTPClient.put", ftpClient_Model.ftpClient) +end ftpClient_Model.counter = 1 -- Internal counter, e.g. used to count sent data and use it for naming -ftpClient_Model.formatter = Image.Format.JPEG.create() -- Formatter instance (JPG per default) --- Function to be processed asynchronously if 'asyncMode' is used -ftpClient_Model.async = Engine.AsyncFunction.create() -ftpClient_Model.async:setFunction("FTPClient.put", ftpClient_Model.ftpClient) +ftpClient_Model.eventSelection = '' -- Event selection in UI table +ftpClient_Model.registeredEventName = 'CSK_Module.EventName' -- Preset event name to register for incoming data +ftpClient_Model.dataType = 'DATA' -- Preset type of incoming data of event to save on FTP server +ftpClient_Model.autoFilename = false -- Preset status if filename should be generated by timestamp + +ftpClient_Model.styleForUI = 'None' -- Optional parameter to set UI style +ftpClient_Model.version = Engine.getCurrentAppVersion() -- Version of module -- Parameters to be saved permanently if wanted ftpClient_Model.parameters = {} +ftpClient_Model.parameters.flowConfigPriority = CSK_FlowConfig ~= nil or false -- Status if FlowConfig should have priority for FlowConfig relevant configurations ftpClient_Model.parameters.serverIP = '192.168.0.201' -- IP of FTP server ftpClient_Model.parameters.imageName = 'unknown' -- Use to give a name for the image to send ftpClient_Model.parameters.isConnected = false -- Status if FTP connection should be established ftpClient_Model.parameters.mode = 'FTP' -- FTP / SFTP -- Mode of FTP connection if ftpClient_Model.parameters.mode == 'SFTP' then - ftpClient_Model.ftpClient:setSecurityProtocol('SFTP') + if _G.availableAPIs.specific == true then + ftpClient_Model.ftpClient:setSecurityProtocol('SFTP') + end ftpClient_Model.parameters.port = 22 -- FTP = 21 / SFTP = 22 -- FTP port to use else ftpClient_Model.parameters.port = 21 -- FTP = 21 / SFTP = 22 @@ -58,17 +71,11 @@ ftpClient_Model.parameters.passiveMode = true -- FTP passive mode ftpClient_Model.parameters.asyncMode = false -- asyncMode ftpClient_Model.parameters.verboseMode = false -- verbose Mode of FTP connection --- For future usage ---ftpClient_Model.parameters.registeredEvents = {} -- Events to listen for incoming data to store on FTP server - +ftpClient_Model.parameters.registeredEvents = {} -- Events to listen for incoming data to store on FTP server -- Sample of data content of entries within the "registeredEvents" ---[[ -local tempInfo = {} -tempInfo.eventName = nil -- Name of the event to register on, e.g. 'CSK_ImagePlayer.OnNewImage' -tempInfo.contentType = nil -tempInfo.contentType = nil -table.insert(ftpClient_Model.parameters.registeredEvents, tempInfo) -]] +-- ftpClient_Model.parameters.registeredEvents[id].eventName -- Name of event +-- ftpClient_Model.parameters.registeredEvents[id].dataType -- Type of data to save +-- ftpClient_Model.parameters.registeredEvents[id].autoFilename -- Status if filename should be created by timestamp --************************************************************************** --********************** End Global Scope ********************************** @@ -76,22 +83,42 @@ table.insert(ftpClient_Model.parameters.registeredEvents, tempInfo) --**********************Start Function Scope ******************************* --************************************************************************** +--- Function to react on UI style change +local function handleOnStyleChanged(theme) + ftpClient_Model.styleForUI = theme + Script.notifyEvent("FTPClient_OnNewStatusCSKStyle", ftpClient_Model.styleForUI) +end +Script.register('CSK_PersistentData.OnNewStatusCSKStyle', handleOnStyleChanged) + --- Checking of the asynchronous FTP transfer process ---@param futureHandle Engine.AsyncFunction.Future The future object identifying the function call local function checkDataTransfer(futureHandle) local suc = futureHandle:isFailed() if suc then - _G.logger:info(nameOfModule .. ": FTP put OK.") + _G.logger:fine(nameOfModule .. ": FTP put OK.") else _G.logger:warning(nameOfModule .. ": FTP put error.") end end -Engine.AsyncFunction.register(ftpClient_Model.async, "OnFinished", checkDataTransfer) +if _G.availableAPIs.default and _G.availableAPIs.specific == true then + Engine.AsyncFunction.register(ftpClient_Model.async, "OnFinished", checkDataTransfer) +end + +--[[ +--- Function to create date/time info as string +---@return string result Data plus time +local function createTimeInfo() + local day, month, year, hour, minute, second = DateTime.getDateTimeValuesLocal() + local customDate = year .. string.format("%02d%02d", month, day) + local customTime = string.format("%02d%02d%02d", hour, minute, second) + return customDate .. '_' .. customTime +end +]] local function sendData(data, filename) if ftpClient_Model.ftpClient:isConnected() then - _G.logger:info(nameOfModule .. ": Try to send data") + _G.logger:fine(nameOfModule .. ": Try to send data") if ftpClient_Model.parameters.asyncMode then --> Asynchronous image transfer ftpClient_Model.async:launch(filename, data) @@ -100,7 +127,7 @@ local function sendData(data, filename) local suc = ftpClient_Model.ftpClient:put(filename, data) if suc then - _G.logger:info(nameOfModule .. ": FTP put OK.") + _G.logger:fine(nameOfModule .. ": FTP put OK.") else _G.logger:warning(nameOfModule .. ": FTP put error.") end @@ -116,9 +143,18 @@ end Script.serveFunction("CSK_FTPClient.sendData", sendData) ftpClient_Model.sendData = sendData +--- Function to add date/time info to data to send to FTP server +---@param data auto Data to send to FTP server +local function addDataFilename(data) + local day, month, year, hour, minute, second = DateTime.getDateTimeValuesLocal() + local customDate = year .. string.format("%02d%02d", month, day) + local customTime = string.format("%02d%02d%02d", hour, minute, second) + sendData(data, customDate .. '_' .. customTime .. '.dat') +end + local function sendImage(img, filename) if ftpClient_Model.ftpClient:isConnected() then - _G.logger:info(nameOfModule .. ": Try to send image") + _G.logger:fine(nameOfModule .. ": Try to send image") local compImg = ftpClient_Model.formatter:encode(img) if ftpClient_Model.parameters.asyncMode then --> Asynchronous image transfer @@ -144,7 +180,7 @@ local function sendImage(img, filename) suc = ftpClient_Model.ftpClient:put(ftpClient_Model.parameters.imageName .. '.jpg', compImg) end if suc then - _G.logger:info(nameOfModule .. ": FTP put OK.") + _G.logger:fine(nameOfModule .. ": FTP put OK.") else _G.logger:warning(nameOfModule .. ": FTP put error.") end @@ -158,4 +194,71 @@ end Script.serveFunction("CSK_FTPClient.sendImage", sendImage) ftpClient_Model.sendImage = sendImage +--- Function to add date/time info to iamge to send to FTP server +---@param data auto Image to send to FTP server +local function addImageFilename(data) + local day, month, year, hour, minute, second = DateTime.getDateTimeValuesLocal() + local customDate = year .. string.format("%02d%02d", month, day) + local customTime = string.format("%02d%02d%02d", hour, minute, second) + sendImage(data, customDate .. '_' .. customTime) +end + +--- Function to register to event to receive data to send to FTP server +---@param eventName string Name of event +---@param dataType string Type of data +---@param autoFilename bool Status if filename should be created by date/time info +local function registerEvent(eventName, dataType, autoFilename) + if dataType == 'DATA' then + if autoFilename then + Script.register(eventName, addDataFilename) + else + Script.register(eventName, sendData) + end + elseif dataType == 'JPG' then + if autoFilename then + Script.register(eventName, addImageFilename) + else + Script.register(eventName, sendImage) + end + end +end +ftpClient_Model.registerEvent = registerEvent + +--- Function to deregister from event to send data/image to FTP server +---@param eventName string Name of event +---@param dataType string Type of data +---@param autoFilename bool Status if filename should be created by date/time info +local function deregisterEvent(eventName, dataType, autoFilename) + if dataType == 'DATA' then + if autoFilename then + Script.deregister(eventName, addDataFilename) + else + Script.deregister(eventName, sendData) + end + elseif dataType == 'JPG' then + if autoFilename then + Script.deregister(eventName, addImageFilename) + else + Script.deregister(eventName, sendImage) + end + end +end +ftpClient_Model.deregisterEvent = deregisterEvent + +--- Function to register to all events +local function registerAllEvents() + for key, _ in pairs(ftpClient_Model.parameters.registeredEvents) do + registerEvent(key, ftpClient_Model.parameters.registeredEvents[key].dataType, ftpClient_Model.parameters.registeredEvents[key].autoFilename) + end +end +ftpClient_Model.registerAllEvents = registerAllEvents + +--- Function to deregister from all events +local function deregisterAllEvents() + for key, _ in pairs(ftpClient_Model.parameters.registeredEvents) do + deregisterEvent(key, ftpClient_Model.parameters.registeredEvents[key].dataType, ftpClient_Model.parameters.registeredEvents[key].autoFilename) + end +end +ftpClient_Model.deregisterAllEvents = deregisterAllEvents + return ftpClient_Model \ No newline at end of file diff --git a/CSK_Module_FTPClient/scripts/Communication/FTPClient/FlowConfig/FTPClient_FlowConfig.lua b/CSK_Module_FTPClient/scripts/Communication/FTPClient/FlowConfig/FTPClient_FlowConfig.lua new file mode 100644 index 0000000..dac6be2 --- /dev/null +++ b/CSK_Module_FTPClient/scripts/Communication/FTPClient/FlowConfig/FTPClient_FlowConfig.lua @@ -0,0 +1,16 @@ +--***************************************************************** +-- Here you will find all the required content to provide specific +-- features of this module via the 'CSK FlowConfig'. +--***************************************************************** + +require('Communication.FTPClient.FlowConfig.FTPClient_Put') + +--- Function to react if FlowConfig was updated +local function handleOnClearOldFlow() + if _G.availableAPIs.default and _G.availableAPIs.specific then + if ftpClient_Model.parameters.flowConfigPriority then + CSK_FTPClient.clearFlowConfigRelevantConfiguration() + end + end +end +Script.register('CSK_FlowConfig.OnClearOldFlow', handleOnClearOldFlow) \ No newline at end of file diff --git a/CSK_Module_FTPClient/scripts/Communication/FTPClient/FlowConfig/FTPClient_Put.lua b/CSK_Module_FTPClient/scripts/Communication/FTPClient/FlowConfig/FTPClient_Put.lua new file mode 100644 index 0000000..e960761 --- /dev/null +++ b/CSK_Module_FTPClient/scripts/Communication/FTPClient/FlowConfig/FTPClient_Put.lua @@ -0,0 +1,87 @@ +-- Block namespace +local BLOCK_NAMESPACE = 'FTPClient_FC.Put' +local nameOfModule = 'CSK_FTPClient' + +--************************************************************* +--************************************************************* + +-- Required to keep track of already allocated resource +local instanceTable = {} + +local function put(handle, data1, data2, data3, data4) + + local dataType1 = Container.get(handle, 'DataType1') + local dataType2 = Container.get(handle, 'DataType2') + local dataType3 = Container.get(handle, 'DataType3') + local dataType4 = Container.get(handle, 'DataType4') + local autoName1 = Container.get(handle, 'AutoName1') + local autoName2 = Container.get(handle, 'AutoName2') + local autoName3 = Container.get(handle, 'AutoName3') + local autoName4 = Container.get(handle, 'AutoName4') + + if data1 then + CSK_FTPClient.addRegistration(data1, dataType1, autoName1) + end + if data2 then + CSK_FTPClient.addRegistration(data2, dataType2, autoName2) + end + if data3 then + CSK_FTPClient.addRegistration(data3, dataType3, autoName3) + end + if data4 then + CSK_FTPClient.addRegistration(data4, dataType4, autoName4) + end +end +Script.serveFunction(BLOCK_NAMESPACE .. '.put', put) + +--************************************************************* +--************************************************************* + +local function create(dataType1, dataType2, dataType3, dataType4, autoName1, autoName2, autoName3, autoName4) + if autoName1 == nil then + autoName1 = true + end + if autoName2 == nil then + autoName2 = true + end + if autoName3 == nil then + autoName3 = true + end + if autoName4 == nil then + autoName4 = true + end + + if nil ~= instanceTable['Solo'] then + _G.logger:warning(nameOfModule .. ": Instance already in use, please choose another one") + return nil + else + -- Otherwise create handle and store the restriced resource + local handle = Container.create() + instanceTable['Solo'] = 'Solo' + + local dataTypes = { dataPos1=dataType1, dataPos2=dataType2, dataPos3=dataType3, dataPos4=dataType4 } + local autoNames = { dataPos1=autoName1, dataPos2=autoName2, dataPos3=autoName3, dataPos4=autoName4 } + + for key, value in pairs(dataTypes) do + local pos = string.sub(key, #key, #key) + if value == '' then + Container.add(handle, 'DataType' .. pos, 'DATA') + else + Container.add(handle, 'DataType' .. pos, value) + end + end + for key, value in pairs(autoNames) do + local pos = string.sub(key, #key, #key) + Container.add(handle, 'AutoName' .. pos, value) + end + return handle + end +end +Script.serveFunction(BLOCK_NAMESPACE .. '.create', create) + +--- Function to reset instances if FlowConfig was cleared +local function handleOnClearOldFlow() + Script.releaseObject(instanceTable) + instanceTable = {} +end +Script.register('CSK_FlowConfig.OnClearOldFlow', handleOnClearOldFlow) \ No newline at end of file diff --git a/CSK_Module_FTPClient/scripts/Communication/FTPClient/helper/checkAPIs.lua b/CSK_Module_FTPClient/scripts/Communication/FTPClient/helper/checkAPIs.lua index 6c714d4..a475901 100644 --- a/CSK_Module_FTPClient/scripts/Communication/FTPClient/helper/checkAPIs.lua +++ b/CSK_Module_FTPClient/scripts/Communication/FTPClient/helper/checkAPIs.lua @@ -5,17 +5,19 @@ local availableAPIs = {} +-- Function to load all default APIs local function loadAPIs() CSK_FTPClient = require 'API.CSK_FTPClient' + Log = require 'API.Log' + Log.Handler = require 'API.Log.Handler' + Log.SharedLogger = require 'API.Log.SharedLogger' + Container = require 'API.Container' + DateTime = require 'API.DateTime' Engine = require 'API.Engine' Engine.AsyncFunction = require 'API.Engine.AsyncFunction' - Image = require 'API.Image' - Log = require 'API.Log' - Log.Handler = require 'API.Log.Handler' - Log.SharedLogger = require 'API.Log.SharedLogger' Object = require 'API.Object' Timer = require 'API.Timer' @@ -26,12 +28,16 @@ local function loadAPIs() CSK_PersistentData = require 'API.CSK_PersistentData' elseif appList[i] == 'CSK_Module_UserManagement' then CSK_UserManagement = require 'API.CSK_UserManagement' + elseif appList[i] == 'CSK_Module_FlowConfig' then + CSK_FlowConfig = require 'API.CSK_FlowConfig' end end end +-- Function to load specific APIs local function loadSpecificAPIs() FTPClient = require 'API.FTPClient' + Image = require 'API.Image' Image.Format = {} Image.Format.JPEG = require 'API.Image.Format.JPEG' end diff --git a/CSK_Module_FTPClient/scripts/Communication/FTPClient/helper/funcs.lua b/CSK_Module_FTPClient/scripts/Communication/FTPClient/helper/funcs.lua index 08ac7e0..156d95b 100644 --- a/CSK_Module_FTPClient/scripts/Communication/FTPClient/helper/funcs.lua +++ b/CSK_Module_FTPClient/scripts/Communication/FTPClient/helper/funcs.lua @@ -125,6 +125,33 @@ local function createStringListBySimpleTable(content) end funcs.createStringListBySimpleTable = createStringListBySimpleTable +--- Function to create a JSON string out of a table content +---@param content string[] Lua Table with entries for list +---@param selectedParam string Currently selected parameter +---@return string jsonstring List created of table entries +local function createSpecificJsonList(content, selectedParam) + local commandList = {} + if content == nil then + commandList = {{DTC_EventName = '-', DTC_DataType = '-', DTC_AutoFilename = '-'},} + else + local size = 0 + for key, value in pairs(content) do + local isSelected = false + if key == selectedParam then + isSelected = true + end + table.insert(commandList, {DTC_EventName = key, DTC_DataType = content[key].dataType, DTC_AutoFilename = content[key].autoFilename, selected = isSelected}) + size = size + 1 + end + if size == 0 then + commandList = {{DTC_EventName = '-', DTC_DataType = '-', DTC_AutoFilename = '-'},} + end + end + local jsonstring = funcs.json.encode(commandList) + return jsonstring +end +funcs.createSpecificJsonList = createSpecificJsonList + return funcs --************************************************************************** diff --git a/README.md b/README.md index 7693dfb..557d9b0 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,13 @@ For further information check out the [documentation](https://raw.githack.com/SI Tested on -1. SIM1012 - Firmware 2.2.0 -2. SICK AppEngine - Firmware 1.3.2 -3. TDC-E - Firmware L4M 2023.1 +|Device|Firmware version|Module version| +|--|--|--| +|SICK AppEngine|V1.7.0|V4.0.0| +|SICK AppEngine|V1.3.2| -Documentation - CSK_Module_FTPClient 3.0.0 +Documentation - CSK_Module_FTPClient 4.0.0