Skip to content

Commit fc0ddf6

Browse files
committed
feat(generics): support rl6
1 parent 31952b7 commit fc0ddf6

File tree

7 files changed

+259
-12
lines changed

7 files changed

+259
-12
lines changed

Diff for: .luacheckrc

+28-12
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,53 @@
22

33
-- Enapter Rule Engine API
44
local enapter_api = {
5+
'inspect',
56
enapter = {
6-
fields = {'log', 'device', 'register_command_handler'}
7+
fields = { 'log', 'device', 'register_command_handler' },
8+
},
9+
ucm = {
10+
fields = { 'new' },
711
},
812
storage = {
913
fields = {
10-
'read', 'write', 'remove', 'err_to_str',
11-
'enable_error_mode', 'disable_error_mode',
12-
}
14+
'read',
15+
'write',
16+
'remove',
17+
'err_to_str',
18+
'enable_error_mode',
19+
'disable_error_mode',
20+
},
1321
},
1422
modbustcp = {
1523
fields = {
16-
'new'
17-
}
24+
'new',
25+
},
1826
},
1927
modbus = {
2028
fields = {
21-
'read_coils', 'read_discrete_inputs', 'read_holdings', 'read_inputs',
22-
'write_coil', 'write_holding', 'write_multiple_coils', 'write_multiple_holdings',
29+
'read_coils',
30+
'read_discrete_inputs',
31+
'read_holdings',
32+
'read_inputs',
33+
'write_coil',
34+
'write_holding',
35+
'write_multiple_coils',
36+
'write_multiple_holdings',
2337
'err_to_str',
24-
}
25-
}
38+
},
39+
},
2640
}
2741

2842
stds.enapter = {
29-
read_globals = enapter_api
43+
read_globals = enapter_api,
3044
}
3145

3246
std = 'lua53+enapter'
3347

3448
ignore = {
3549
'212/self', -- allow unused `self`
36-
'411/ok', '411/error', '411/err', -- allow to re-define some variables
50+
'411/ok',
51+
'411/error',
52+
'411/err', -- allow to re-define some variables
3753
'421/err', -- allow to shadow some variables
3854
}

Diff for: README.md

+17
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,20 @@ config.init({
5252
```
5353

5454
After that commands to read/write config are registered and values can be get in Lua code via `config.read` and `config.read_all` methods.
55+
56+
57+
## Generics
58+
59+
`enapter.ucm.generics` provides helpers to communicate with [Enapter Generic-IO](https://marketplace.enapter.com/blueprints/generic_io).
60+
61+
### RL6
62+
63+
```lua
64+
local rl6 = require('enapter.ucm.generics.rl6')
65+
66+
local power_relay = rl6.new() -- creates a new instance of relay contact
67+
power_relay:setup('AABBCC', 5) -- setup to operate on contact 5 of generic RL-6 UCM with hardware id AABBCC
68+
power_relay:open() -- open contact
69+
power_relay:close() -- close contact
70+
local power_is_on = power_relay:is_closed() -- check contact status
71+
```

Diff for: spec/generic_rl6_spec.lua

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
_G.inspect = require('inspect')
2+
local stubs = require('enapter.ucm.stubs')
3+
4+
describe('generic rl6', function()
5+
local rl6, peer_stub
6+
7+
before_each(function()
8+
local ucm_stubs = stubs.setup_enapter_ucm()
9+
peer_stub = stubs.new_dummy_ucm_peer()
10+
ucm_stubs.stubs = { peer_stub }
11+
12+
package.loaded['enapter.ucm.generics.rl6'] = false
13+
rl6 = require('enapter.ucm.generics.rl6').new()
14+
end)
15+
16+
after_each(function()
17+
stubs.teardown_generic_rl6()
18+
stubs.teardown_enapter_ucm()
19+
end)
20+
21+
it('should open', function()
22+
rl6:setup('test_open_id', 57, 123)
23+
24+
local s = spy.on(peer_stub, 'execute_command')
25+
assert.is_nil(rl6:open())
26+
assert.spy(s).was_called(1)
27+
assert.spy(s).was_called_with(peer_stub, 'open_channel', { channel = 57 }, { timeout = 123 })
28+
end)
29+
30+
it('should close', function()
31+
rl6:setup('test_close_id', 31)
32+
33+
local s = spy.on(peer_stub, 'execute_command')
34+
assert.is_nil(rl6:close())
35+
assert.spy(s).was_called(1)
36+
assert.spy(s).was_called_with(peer_stub, 'close_channel', { channel = 31 }, { timeout = 1000 })
37+
end)
38+
39+
it('should report state', function()
40+
rl6:setup('test_state_id', 25)
41+
42+
peer_stub.execute_command = function() return 'completed', { closed = true } end
43+
local closed, err = rl6:is_closed()
44+
assert.is_nil(err)
45+
assert.is_true(closed)
46+
end)
47+
48+
it('should return error if state is missed', function()
49+
rl6:setup('test_err_id', 25)
50+
51+
peer_stub.execute_command = function() return 'completed', { is_closed = true } end
52+
local _, err = rl6:is_closed()
53+
assert.is_same('unexpected response from io: {is_closed = true}', err)
54+
end)
55+
56+
it('should return error if state is not boolean', function()
57+
rl6:setup('test_err_id', 25)
58+
59+
peer_stub.execute_command = function() return 'completed', { closed = 'yes' } end
60+
local _, err = rl6:is_closed()
61+
assert.is_same('unexpected response from io: {closed = "yes"}', err)
62+
end)
63+
64+
it('should return execution error (open)', function()
65+
rl6:setup()
66+
peer_stub.execute_command = function() return nil, nil, 'test exec err' end
67+
assert.is_same('test exec err', rl6:open())
68+
end)
69+
70+
it('should return execution error (close)', function()
71+
rl6:setup()
72+
peer_stub.execute_command = function() return nil, nil, 'test exec err' end
73+
assert.is_same('test exec err', rl6:close())
74+
end)
75+
76+
it('should return non-completed error (open)', function()
77+
rl6:setup()
78+
peer_stub.execute_command = function()
79+
return 'non-completed', { errcode = 117, errmsg = 'test open error' }, nil
80+
end
81+
assert.is_same(
82+
'relay module command failed: non-completed: {errcode = 117,errmsg = "test open error"}',
83+
rl6:open()
84+
)
85+
end)
86+
87+
it('should return non-completed error', function()
88+
rl6:setup()
89+
90+
peer_stub.execute_command = function()
91+
return 'non-completed', { errcode = 428, errmsg = 'test close error' }, nil
92+
end
93+
assert.is_same(
94+
'relay module command failed: non-completed: {errcode = 428,errmsg = "test close error"}',
95+
rl6:close()
96+
)
97+
end)
98+
end)

Diff for: src/enapter/ucm/generics/rl6.lua

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
local GENERAL_IO_CHANNEL = 'gio'
2+
local UCM_COMMAND_DEFAULT_TIMEOUT_MS = 1000
3+
4+
local function do_cmd(self, cmd_name)
5+
local peer = ucm.new(self.ucm_id, GENERAL_IO_CHANNEL)
6+
local state, payload, err = peer:execute_command(
7+
cmd_name,
8+
{ channel = self.channel_id },
9+
{ timeout = self.timeout }
10+
)
11+
if err then return nil, err end
12+
if state ~= 'completed' then
13+
return nil,
14+
'relay module command failed: ' .. state .. ': ' .. inspect(
15+
payload,
16+
{ newline = '', indent = '' }
17+
)
18+
end
19+
20+
return payload
21+
end
22+
23+
return {
24+
new = function()
25+
local rl6 = {}
26+
27+
function rl6:setup(ucm_id, channel_id, timeout)
28+
self.ucm_id = ucm_id
29+
self.channel_id = math.tointeger(channel_id)
30+
self.timeout = timeout or UCM_COMMAND_DEFAULT_TIMEOUT_MS
31+
end
32+
33+
function rl6:open()
34+
local _, err = do_cmd(self, 'open_channel')
35+
return err
36+
end
37+
38+
function rl6:close()
39+
local _, err = do_cmd(self, 'close_channel')
40+
return err
41+
end
42+
43+
function rl6:is_closed()
44+
local payload, err = do_cmd(self, 'is_channel_closed')
45+
if err then return nil, err end
46+
if payload.closed == nil or type(payload.closed) ~= 'boolean' then
47+
return nil,
48+
'unexpected response from io: ' .. inspect(payload, { newline = '', indent = '' })
49+
end
50+
return payload.closed
51+
end
52+
53+
return rl6
54+
end,
55+
}

Diff for: src/enapter/ucm/stubs/enapter_ucm.lua

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
local function new_dummy_ucm_peer()
2+
return {
3+
execute_command = function() return 'completed', {} end,
4+
}
5+
end
6+
7+
local function setup_enapter_ucm()
8+
local stub = { stubs = {} }
9+
_G.ucm = {
10+
new = function() return table.remove(stub.stubs, 1) or new_dummy_ucm_peer() end,
11+
}
12+
return stub
13+
end
14+
15+
local function teardown_enapter_ucm() _G.ucm = nil end
16+
17+
return {
18+
new_dummy_ucm_peer = new_dummy_ucm_peer,
19+
setup_enapter_ucm = setup_enapter_ucm,
20+
teardown_enapter_ucm = teardown_enapter_ucm,
21+
}

Diff for: src/enapter/ucm/stubs/generics_rl6.lua

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
local function new_dummy_rl6()
2+
return {
3+
setup = function() end,
4+
is_closed = function() return false end,
5+
}
6+
end
7+
8+
local function setup_generic_rl6()
9+
local stub = { stubs = {} }
10+
package.loaded['enapter.ucm.generics.rl6'] = false
11+
package.preload['enapter.ucm.generics.rl6'] = function()
12+
return {
13+
new = function() return table.remove(stub.stubs, 1) or new_dummy_rl6() end,
14+
}
15+
end
16+
return stub
17+
end
18+
19+
local function teardown_generic_rl6()
20+
package.loaded['enapter.ucm.generics.rl6'] = false
21+
package.preload['enapter.ucm.generics.rl6'] = nil
22+
end
23+
24+
return {
25+
new_dummy_rl6 = new_dummy_rl6,
26+
setup_generic_rl6 = setup_generic_rl6,
27+
teardown_generic_rl6 = teardown_generic_rl6,
28+
}

Diff for: src/enapter/ucm/stubs/init.lua

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
local t = {}
2+
3+
for _, pkg in ipairs({
4+
'enapter.ucm.stubs.enapter_ucm',
5+
'enapter.ucm.stubs.generics_rl6',
6+
}) do
7+
for k, v in pairs(require(pkg)) do
8+
t[k] = v
9+
end
10+
end
11+
12+
return t

0 commit comments

Comments
 (0)