LuCi new app RPC error

Hello.
I had a task to implement the tio utility in the web of my OpenWRT project.
I figured out what LuCi is, took its sources and tried to make my own app.
I took other apps as an example.
I changed the names everywhere, for directories, for files, for variables as needed, but when loading pages I get the following error:

RPCError

RPC call to uci/get failed with ubus code 4: Ресурс не найден
  at handleCallReply (http://192.168.0.171/luci-static/resources/rpc.js?v=24.178.33018~6ae2763:15:3)

I wrote the following file, located in the applications/luci-app-tiod/root/usr/share/rpcd/acl.d/luci-app-tiod.json directory:

{
	"luci-app-tiod": {
		"description": "Grant access to LuCI app tiod",
		"read": {
			"uci": [ "tiod" ]
		},
		"write": {
			"uci": [ "tiod" ]
		}
	}
}

On the Internet, this code solves this problem, but it doesn’t help me, it feels like my app is not registered in LuCI, namely in uci.

Also if in the file applications/luci-app-tiod/root/usr/share/luci/menu.d/luci-app-tiod.json:

{
	"admin/services/tiod": {
		"title": "RS232",
		"action": {
			"type": "firstchild"
		},
		"depends": {
			"acl": [ "luci-app-tiod" ],
			"uci": { "tiod": true }
		}
	},

	"admin/services/tiod/tiod": {
		"title": "Terminal",
		"order": 1,
		"action": {
			"type": "view",
			"path": "tiod/term"
		}
	},

	"admin/services/tiod/config": {
		"title": "Config",
		"order": 2,
		"action": {
			"type": "view",
			"path": "tiod/config"
		}
	}
}

specify instead of "uci": { "tiod": true } another application, for example ttyd, then everything will work, but its pages will be replaced with ttyd pages.

Did you restart the rpcd daemon after your changes? If not, try ...

/etc/init.d/rpcd restart

Now this error:

RPCError

RPC call to uci/get failed with ubus code 4: Resource not found
  at handleCallReply (http://192.168.0.171/luci-static/resources/rpc.js?v=24.177.33927~9980db1:15:3)

Your config exists in /etc/config/tiod? If not, generate a dummy via touch /etc/config/tiod.

Did you use a vanilla OpenWrt? The used directories for your json files looks odd, usually the files are placed in ...

/usr/share/rpcd/acl.d/luci-app-tiod.json
/usr/share/luci/menu.d/luci-app-tiod.json

No, I don't have a config folder, but I don't want to create one on the system.
I have the LuCi source code, how can I solve this problem? I need to solve it in the source code.

It's OpenWRT Linux 6.1.53.
Yes, the file tree of this application looks like this:
Снимок экрана от 2024-06-27 08-10-40

I now realized that it’s worth posting the page code that actually uses config.
applications/luci-app-tiod/htdocs/luci-static/resources/view/tiod/config.js:

'use strict';
'require view';
'require form';

return view.extend({
	render: function() {
		var m, s, o;

		m = new form.Map('tiod');

		s = m.section(form.TypedSection, 'tiod', _('Сессия'));
		s.anonymous   = true;
		s.addremove   = true;
		s.addbtntitle = _('Добавить сессию');

		s = m.section(form.TypedSection, "controlport", _("Контрольный порт"));
		s.anonymous = true;

		o = s.option(form.Value, "device", _("Устройство"), _("Имя устройства для подключения.<br/>Должно быть в виде /dev/."));
		o.rmempty = false;
		o.default = "/dev/ttyS4";

		s = m.section(form.TypedSection, "default", _("Настройки по умолчанию"));
		s.anonymous = true;

		o = s.option(form.ListValue, "speed", _("Скорость передачи в бодах"), _("Скорость, на которой должен работать порт устройства."));
		o.rmempty = false;
		o.value(300);
		o.value(1200);
		o.value(2400);
		o.value(4800);
		o.value(9600);
		o.value(19200);
		o.value(38400);
		o.value(57600);
		o.value(115200);
		o.default = 9600;

		o = s.option(form.ListValue, "databits", _("Биты данных"));
		o.rmempty = false;
		o.value(8);
		o.value(7);
		o.value(6);
		o.value(5);
		o.default = 8;

		o = s.option(form.ListValue, "parity", _("Чётность"));
		o.rmempty = false;
		o.value("none", _("Отсутствует"));
		o.value("even", _("Чётный"));
		o.value("odd", _("Нечётный"));
		o.default = "none";

		o = s.option(form.ListValue, "stopbits", _("Стоповые биты"));
		o.rmempty = false;
		o.value(1);
		o.value(2);
		o.default = 1;

		s.option(form.Flag, "rtscts", _("Использовать линии RTS и CTS"));

		return m.render();
	}
});

applications/luci-app-tiod/htdocs/luci-static/resources/view/tiod/term.js:

'use strict';
'require view';
'require form';

return view.extend({
	render: function() {
		var m, s, o;

		m = new form.Map('tiod');

		s = m.section(form.TypedSection, "controlport", _("Контрольный порт"));
		s.anonymous = true;

		return m.render();
	}
});

This helped, but I have two questions:

  1. How can I make sure that when LuCi is turned on, I immediately create a config for my application.
  2. The sections that I specified appeared, but all my options did not appear, why?

The base package (tiod?) should provide such config.

a 'require uci'; is missing ... for more please consult the "luci-app-example" (https://github.com/openwrt/luci/tree/master/applications/luci-app-example)

This is not a basic package, but my own, custom one.

Okay, I figured out the config and bash scripts, but this does not solve the problem that I specify my options in the section, but they are not displayed. Why??
Everything else is the same as in the example, but it doesn’t work.

Is your /etc/config/tiod defining a section?

You should have a /etc/config/tiod with a content like this:

config controlport
    option device /dev/ttyS4

config default
    option speed 300
    option databits 8

Since your model does not set the .addremove = true flag for section instances, the gui will only display sections already present in the config.

Earlier you stated that you do not have (and do not want?) an /etc/config/tiod - do you actually want or need uci for your app? If you just want a form and process the resulting data otherwise, e.g. for sending to an api, you might want to look into form.JSONMap

I've already implemented the config, but it doesn't really help me because I found an interesting problem.
Снимок экрана от 2024-06-27 12-34-55
I took an example from luci-app-example, it’s like this:

'use strict';
'require view';
'require form';

// Project code format is tabs, not spaces
return view.extend({
	render: function() {
		var m, s, o;

		/*
		The first argument to form.Map() maps to the configuration file available
		via uci at /etc/config/. In this case, 'example' maps to /etc/config/example.

		If the file is completely empty, the form sections will indicate that the
		section contains no values yet. As such, your package installation (LuCI app
		or software that the app configures) should lay down a basic configuration
		file with all the needed sections.

		The relevant ACL path for reading a configuration with UCI this way is
		read > uci > ["example"]

		The relevant ACL path for writing back the configuration is
		write > uci > ["example"]
		*/
		m = new form.Map('example', _('Example Form'),
			_('Example Form Configuration.'));

		s = m.section(form.TypedSection, 'first', _('first section'));
		s.anonymous = true;

		s.option(form.Value, 'first_option', _('First Option'),
			_('Input for the first option'));

		s = m.section(form.TypedSection, 'second', _('second section'));
		s.anonymous = true;

		o = s.option(form.Flag, 'flag', _('Flag Option'),
			_('A boolean option'));
		o.default = '1';
		o.rmempty = false;

		o = s.option(form.ListValue, 'select', _('Select Option'),
			_('A select option'));
		o.placeholder = 'placeholder';
		o.value('key1', 'value1');
		o.value('key2', 'value2');
		o.rmempty = false;
		o.editable = true;

		s = m.section(form.TypedSection, 'third', _('third section'));
		s.anonymous = true;
		o = s.option(form.Value, 'password_option', _('Password Option'),
			_('Input for a password (storage on disk is not encrypted)'));
		o.password = true;
		o = s.option(form.DynamicList, 'list_option', _('Dynamic list option'));

		return m.render();
	},
});

And everything comes out fine, then I decided to remake it for myself and got the following code:

'use strict';
'require view';
'require form';

// Project code format is tabs, not spaces
return view.extend({
	render: function() {
		var m, s, o;

		/*
		The first argument to form.Map() maps to the configuration file available
		via uci at /etc/config/. In this case, 'example' maps to /etc/config/example.

		If the file is completely empty, the form sections will indicate that the
		section contains no values yet. As such, your package installation (LuCI app
		or software that the app configures) should lay down a basic configuration
		file with all the needed sections.

		The relevant ACL path for reading a configuration with UCI this way is
		read > uci > ["example"]

		The relevant ACL path for writing back the configuration is
		write > uci > ["example"]
		*/
		m = new form.Map('tiod', _('Конфигурация'));

		s = m.section(form.TypedSection, "controlport", _("Контрольный порт"));
		s.anonymous = true;

		o = s.option(form.Value, "device", _("Устройство"), 
			_("Имя устройства для подключения.<br/>Должно быть в виде /dev/."));
		o.rmempty = false;
		o.default = "/dev/ttyS4";

		s = m.section(form.TypedSection, "default", _("Настройки по умолчанию"));
		s.anonymous = true;

		o = s.option(form.ListValue, "speed", _("Скорость передачи в бодах"), _("Скорость, на которой должен работать порт устройства."));
		o.rmempty = false;
		o.value(300);
		o.value(1200);
		o.value(2400);
		o.value(4800);
		o.value(9600);
		o.value(19200);
		o.value(38400);
		o.value(57600);
		o.value(115200);
		o.default = 9600;

		o = s.option(form.ListValue, "databits", _("Биты данных"));
		o.rmempty = false;
		o.value(8);
		o.value(7);
		o.value(6);
		o.value(5);
		o.default = 8;

		o = s.option(form.ListValue, "parity", _("Чётность"));
		o.rmempty = false;
		o.value("none", _("Отсутствует"));
		o.value("even", _("Чётный"));
		o.value("odd", _("Нечётный"));
		o.default = "none";

		o = s.option(form.ListValue, "stopbits", _("Стоповые биты"));
		o.rmempty = false;
		o.value(1);
		o.value(2);
		o.default = 1;

		s.option(form.Flag, "rtscts", _("Использовать линии RTS и CTS"));

		return m.render();
	}
});

But the output is this:


I restarted the processes, the entire router (even during the firmware it creates all the files from scratch), but LuCi is trying to derive objects from the example, not my code.
I get the feeling that my objects are not registering in the form, why?
This confuses me, it’s as if I’m missing something, some kind of declaration file.

It seems to me that I don’t have to use the config, just register my forms. But they are not registered. For example, the ttyd application does not use the config, but everything works fine and is displayed because its forms are registered in LuCi.

Your config has semantic errors. You must use the "config" keyword to declare sections and the "option" keyword to declare keys and values within that section. According to your screenshot, you only declared sections (e.g. config 9600 'speed') and swapped the order of arguments (value first, then key).

The syntax is either config {type} [name] to declare section or option {key} {value} to specify an option within a previously declared section.

As it stands now, your current /etc/config/tiod is invalid. Use uci show tiod on the command line to check for syntax errors and to see whether the current contents yield the correct presentation.

Speaking in .ini file metaphors, your current config is

[/dev/ttyS4 device]

[9600 speed]

[8 databits]

[none parity]

[1 stopbits]

rather than

[default]
speed = 9600
databits = 8
parity = none
stopbits = 1

[controlport]
device = /dev/ttyS4

Taking your current LuCI form model into consideration, the /etc/config/tiod should contain exactly:

config default
  option speed 9600
  option databits 8
  option parity none
  option stopbits 1

config controlport
  option device /dev/ttyS4
1 Like

Okay, I understand that, but then how can I write this in a bash script?
The example provided the following file:

#!/bin/sh

touch /etc/config/example
uci set example.first=first
uci set example.second=second
uci set example.third=third
uci commit

return 0

Either

touch /etc/config/tiod

uci add tiod default
uci set tiod.@default[-1].speed=9600
uci set tiod.@default[-1].databits=8
uci set tiod.@default[-1].parity=none
uci set tiod.@default[-1].stopbits=1

uci add tiod controlport
uci set tiod.@controlport[-1].device=/dev/ttyS4

uci commit tiod

or

cat <<EOT > /etc/config/tiod
config default
  option speed 9600
  option databits 8
  option parity none
  option stopbits 1

config controlport
  option device /dev/ttyS4
EOT

or

touch /etc/config/tiod
cat <<EOT | uci batch -
add tiod default
set tiod.@default[-1].speed=9600
set tiod.@default[-1].databits=8
set tiod.@default[-1].parity=none
set tiod.@default[-1].stopbits=1

add tiod controlport
set tiod.@controlport[-1].device=/dev/ttyS4

commit tiod
EOT

Sorry for the stupid questions, I'm just learning how to work with LuCi.
Yes, now the config is displayed correctly, but my forms remain the same, and I corrected some forms, but they continue to be drawn. How can I reset these already assembled forms and rebuild them from scratch? After each change, I reflash the router, but it saves these pages.

Maybe i have a sintaxis errors?
applications/luci-app-tiod/htdocs/luci-static/resources/view/tiod/config.js:

'use strict';
'require view';
'require form';

// Project code format is tabs, not spaces
return view.extend({
	render: function() {
		var m, s, o;

		m = new form.Map('tiod', _('Конфигурация'));

		s = m.section(form.TypedSection, "controlport", _("Контрольный порт"));
		s.anonymous = true;

		o = s.option(form.Value, "device", _("Устройство"), _("Имя устройства для подключения.<br/>Должно быть в виде /dev/."));
		o.rmempty = false;
		o.default = "/dev/ttyS4";

		s = m.section(form.TypedSection, "default", _("Настройки по умолчанию"));
		s.anonymous = true;

		o = s.option(form.ListValue, "speed", _("Скорость передачи в бодах"), _("Скорость, на которой должен работать порт устройства."));
		o.rmempty = false;
		o.value(300);
		o.value(1200);
		o.value(2400);
		o.value(4800);
		o.value(9600);
		o.value(19200);
		o.value(38400);
		o.value(57600);
		o.value(115200);
		o.default = 9600;

		o = s.option(form.ListValue, "databits", _("Биты данных"));
		o.rmempty = false;
		o.value(8);
		o.value(7);
		o.value(6);
		o.value(5);
		o.default = 8;

		o = s.option(form.ListValue, "parity", _("Чётность"));
		o.rmempty = false;
		o.value("none", _("Отсутствует"));
		o.value("even", _("Чётный"));
		o.value("odd", _("Нечётный"));
		o.default = "none";

		o = s.option(form.ListValue, "stopbits", _("Стоповые биты"));
		o.rmempty = false;
		o.value(1);
		o.value(2);
		o.default = 1;

		return m.render();
	}
});

I DON'T UNDERSTAND!!!
To test this, I decided to remove most of my objects from config.js:

'use strict';
'require view';
'require form';

// Project code format is tabs, not spaces
return view.extend({
	render: function() {
		var m, s, o;

		m = new form.Map('tiod', _('Конфигурация'));

		s = m.section(form.TypedSection, "controlport", _("Контрольный порт"));
		s.anonymous = true;

		

		return m.render();
	}
});

And in the end, this is what came out on the web:


WHAT A F***
I don’t understand, no matter what I do, it doesn’t accept updates, what and where is it, what should I delete?
image
Now almost everything works well, but I would like these changes to appear immediately after flashing, and not after a hundred years and tons of source code changes.

Did you ever clear your browser cache? Another option is to open the browser debug console and temporarily disable caching there, or simply hold down the shift key while pressing your browser reload button or (at least on firefox) hit ctrl-shift-delete and delete page cache data.

You also don't need to reflash to test such trivial JavaScript changes, simply replace the file on target (e.g. scp applications/luci-app-tiod/htdocs/luci-static/resources/view/tiod/config.js root@192.168.1.1:/www/luci-static/resources/view/tiod/config.js)

Another alternative is using SSHFS to directly develop on the target. On OpenWrt, run opkg update; opkg install openssh-sftp-server (or include it in your custom build). Then on your workstation: mkdir /tmp/router; sshfs root@192.168.1.1:/ /tmp/router/ and edit /tmp/router/www/luci-static/resources/view/tiod/config.js

Since most LuCI ui stuff happens in the browser and since static JS files are cached aggressively, you will need to clear your browser cache or do forced reloads after each development iteration.