diff --git a/Dockerfile b/Dockerfile
index 6795a04..7089253 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -31,6 +31,8 @@ RUN . /opt/axis/acapsdk/environment-setup* && \
# Build ACAP package
WORKDIR "$ACAP_BUILD_DIR"
+COPY html/ ./html
+ADD https://code.jquery.com/jquery-3.7.1.min.js ./html/js/jquery.min.js
COPY LICENSE \
Makefile \
manifest.json \
diff --git a/html/config.html b/html/config.html
new file mode 100644
index 0000000..e68fc2a
--- /dev/null
+++ b/html/config.html
@@ -0,0 +1,92 @@
+
+
+
+
+
+ Modbus ACAP Configuration
+
+
+
+
+
+
+ Modbus ACAP Configuration
+
+
+
+
Modbus server
+
+
+
AOA Scenarios
+
+
+ Name |
+ ID no. |
+ Type |
+
+
+
+
Scenario selected for Modbus output
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/html/js/modbusconfig.js b/html/js/modbusconfig.js
new file mode 100644
index 0000000..9315450
--- /dev/null
+++ b/html/js/modbusconfig.js
@@ -0,0 +1,128 @@
+var params = {};
+var scenarios = {};
+const appname = 'Modbusacap';
+const parambaseurl = '/axis-cgi/param.cgi?action=';
+const paramgeturl = parambaseurl + 'list&group= ' + appname + '.';
+const paramseturl = parambaseurl + 'update&' + appname + '.';
+const scenariourl = '/local/objectanalytics/control.cgi';
+const table = document.getElementById("scenarios");
+const port = document.getElementById("port");
+const server = document.getElementById("server");
+const clientcfg = document.getElementById("clientcfg");
+const modeselection = document.getElementById("modeselection");
+const scenarioselection = document.getElementById("scenarioselection");
+const UpdateInterval = 5;
+const SERVER = 0;
+const CLIENT = 1;
+
+function portChange(newport) {
+ setNewValue('Port', newport);
+}
+
+function modeChange(newmode, setparam = true) {
+ if (setparam) {
+ setNewValue('Mode', newmode);
+ }
+ switch (+newmode) {
+ case SERVER:
+ clientcfg.style.display = 'none';
+ break;
+ case CLIENT:
+ clientcfg.style.display = 'block';
+ break;
+ default:
+ console.error('Unknown application mode: ' + newmode);
+ }
+}
+
+function serverChange(newserver) {
+ setNewValue('Server', newserver);
+}
+
+function scenarioChange(newscenario) {
+ setNewValue('Scenario', newscenario);
+}
+
+function updateScenarioTable() {
+ for (let i = table.rows.length - 1; i > 0; i--) {
+ table.deleteRow(i);
+ }
+ for (const scenario of scenarios) {
+ const row = table.insertRow();
+ var i = 0;
+ const nameCell = row.insertCell(i++);
+ const idCell = row.insertCell(i++);
+ const typeCell = row.insertCell(i++);
+
+ idCell.innerText = scenario.id;
+ nameCell.innerText = scenario.name;
+ typeCell.innerText = scenario.type.replace(/([A-Z])/g, ' $1').toLowerCase();
+ }
+}
+
+function updateScenarioSelection() {
+ while (scenarioselection.options.length > 0) {
+ scenarioselection.remove(0);
+ }
+ for (const scenario of scenarios) {
+ scenarioselection.add(new Option(`${scenario.name} (ID: ${scenario.id})`, scenario.id));
+ }
+ scenarioselection.value = +(params['Scenario']);
+}
+
+async function getScenarios() {
+ try {
+ const scenarioData = await $.post({
+ url: scenariourl,
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ data: '{"apiVersion": "1.2", "method": "getConfiguration"}'
+ });
+ scenarios = scenarioData.data.scenarios;
+ for (i in scenarioData.data.scenarios) {
+ console.log('scenario id: ' + scenarios[i].id + ', scenario name: ' + scenarios[i].name + ', type: ' + scenarios[i].type);
+ }
+ updateScenarioTable();
+ } catch (error) {
+ alert('Failed to get scenario info from AOA');
+ console.error('Failed to get scenario info from AOA: ', error)
+ }
+}
+
+async function getCurrentValue(param) {
+ try {
+ const paramData = await $.get(paramgeturl + param);
+ params[param] = paramData.split('=')[1];
+ console.log('Got ' + param + ' value ' + params[param]);
+ } catch (error) {
+ alert('Failed to get parameter ' + param);
+ }
+}
+
+async function setNewValue(param, value) {
+ try {
+ await $.get(paramseturl + param + '=' + value);
+ params[param] = value;
+ console.log('Set ' + param + ' value to ' + value);
+ } catch (error) {
+ alert('Failed to set parameter ' + param);
+ }
+}
+
+async function updateValues() {
+ await getCurrentValue('Mode');
+ await getCurrentValue('Port');
+ await getCurrentValue('Scenario');
+ await getCurrentValue('Server');
+ await getScenarios();
+ modeChange(+(params['Mode']), false);
+ modeselection.value = +(params['Mode']);
+ port.value = +(params['Port']);
+ updateScenarioSelection();
+ server.value = params['Server'];
+ console.log('Will call again in ' + UpdateInterval + ' seconds to make sure we are in sync with device');
+ setTimeout(updateValues, 1000 * UpdateInterval);
+}
+
+updateValues();
diff --git a/manifest.json b/manifest.json
index dac7e1c..02f70ec 100644
--- a/manifest.json
+++ b/manifest.json
@@ -7,9 +7,10 @@
"vendor": "Axis Communications AB",
"embeddedSdkVersion": "3.0",
"runMode": "respawn",
- "version": "1.2.0"
+ "version": "1.2.1"
},
"configuration": {
+ "settingPage": "config.html",
"paramConfig": [
{"name": "Mode", "type": "enum:0|Server, 1|Client", "default": "0"},
{"name": "Port", "type": "int:min=1024,max=65535", "default": "5020"},