• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wirenboard / wb-mqtt-serial / 1

08 Jul 2025 01:20PM UTC coverage: 73.854% (+1.0%) from 72.836%
1

Pull #963

github

39d9bc
KraPete
Bump version
Pull Request #963: Bump version

6444 of 9057 branches covered (71.15%)

12341 of 16710 relevant lines covered (73.85%)

305.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

14.85
/src/devices/curtains/a_ok_device.cpp
1
#include "a_ok_device.h"
2
#include "bin_utils.h"
3

4
/**
5
 * A-OK communication protocol
6
 *
7
 * WB to motor frame:
8
 * +---------+------+-------+------+-----------------------+
9
 * |Head code|ID    |Chanel |Data  |Verify                 |
10
 * +---------+------+-------+------+-----------------------+
11
 * |0x9a     |1 byte|2 bytes|2 byte|1 byte: ID^Channel^Data|
12
 * +---------+------+-------+------+-----------------------+
13
 *
14
 * Motor to WB frame (for status inquiry commands only):
15
 * +---------+------+-------+------+-----------------------+
16
 * |Head code|ID    |Chanel |Data  |Verify                 |
17
 * +---------+------+-------+------+-----------------------+
18
 * |0xd8     |1 byte|2 bytes|5 byte|1 byte: ID^Channel^Data|
19
 * +---------+------+-------+------+-----------------------+
20
 */
21

22
using namespace BinUtils;
23

24
namespace
25
{
26
    enum TRegTypes
27
    {
28
        POSITION,
29
        COMMAND,
30
        PARAM,
31
        STATUS,
32
        ZONEBIT
33
    };
34

35
    const TRegisterTypes RegTypes{{POSITION, "position", "value", U8},
36
                                  {COMMAND, "command", "value", U8},
37
                                  {PARAM, "param", "value", U8},
38
                                  {STATUS, "status", "value", U64},
39
                                  {ZONEBIT, "zonebit", "value", U8}};
40

41
    enum THeadCodes
42
    {
43
        REQUEST = 0x9a,
44
        RESPONSE = 0xd8
45
    };
46

47
    enum TCommandTypes
48
    {
49
        CONTROL = 0x0a,
50
        SET_POSITION = 0xdd,
51
        SET_CURTAIN_MOTOR_SETTING = 0xd5,
52
        MOTOR_STATUS = 0xcc,
53
        CURTAIN_MOTOR_STATUS = 0xca,
54
        TUBULAR_MOTOR_STATUS = 0xcb
55
    };
56

57
    const size_t CRC_SIZE = 1;
58
    const size_t MOTOR_STATUS_RESPONSE_SIZE = 10;
59
    const size_t MOTOR_STATUS_DATA_OFFSET = 4;
60
    // const size_t MOTOR_STATUS_DATA_SIZE = 5;
61
    const size_t MOTOR_STATUS_POSITION_OFFSET = 3;
62
    const size_t MOTOR_STATUS_ZONEBIT_OFFSET = 4;
63

64
    const size_t MOTOR_ID_POS = 1;
65
    const size_t LOW_CHANNEL_ID_POS = 2;
66
    const size_t HIGH_CHANNEL_ID_POS = 3;
67

68
    uint8_t CalcCrc(const std::vector<uint8_t>& bytes)
9✔
69
    {
70
        uint8_t xorResult = 0;
9✔
71
        for (uint8_t value: bytes) {
54✔
72
            xorResult ^= value;
45✔
73
        }
74
        return xorResult;
9✔
75
    }
76

77
    TPort::TFrameCompletePred ExpectNBytes(size_t n)
×
78
    {
79
        return [=](uint8_t* buf, size_t size) {
×
80
            if (size < n)
×
81
                return false;
×
82
            // Got expected bytes count, head code and CRC is ok
83
            if (size == n && buf[0] == RESPONSE && buf[n - 1] == CalcCrc({buf + 1, buf + n - 1})) {
×
84
                return true;
×
85
            }
86
            // Malformed packet or packet with error code. Wait for frame timeout
87
            return false;
×
88
        };
×
89
    }
90
}
91

92
void Aok::TDevice::Register(TSerialDeviceFactory& factory)
143✔
93
{
94
    factory.RegisterProtocol(new TUint32SlaveIdProtocol("a_ok", RegTypes),
429✔
95
                             new TBasicDeviceFactory<Aok::TDevice>("#/definitions/simple_device_no_channels"));
286✔
96
    // Deprecated, use "a_ok" protocol instead, for backward compatibility only
97
    factory.RegisterProtocol(new TUint32SlaveIdProtocol("dauerhaft", RegTypes),
429✔
98
                             new TBasicDeviceFactory<Aok::TDevice>("#/definitions/simple_device_no_channels"));
286✔
99
}
143✔
100

101
Aok::TDevice::TDevice(PDeviceConfig config, PProtocol protocol)
×
102
    : TSerialDevice(config, protocol),
103
      TUInt32SlaveId(config->SlaveId),
×
104
      MotorId((SlaveId >> 16) & 0xFF),
×
105
      LowChannelId((SlaveId >> 8) & 0xFF),
×
106
      HighChannelId(SlaveId & 0xFF)
×
107
{}
108

109
TRegisterValue Aok::TDevice::GetCachedResponse(TPort& port,
×
110
                                               uint8_t command,
111
                                               uint8_t data,
112
                                               size_t bitOffset,
113
                                               size_t bitWidth)
114
{
115
    TRegisterValue val;
×
116
    uint16_t key = (command << 8) | data;
×
117
    auto it = DataCache.find(key);
×
118
    if (it != DataCache.end()) {
×
119
        val = it->second;
×
120
    } else {
121
        TRequest req;
×
122
        req.Data = MakeRequest(MotorId, LowChannelId, HighChannelId, command, data);
×
123
        req.ResponseSize = MOTOR_STATUS_RESPONSE_SIZE;
×
124
        auto resp = ExecCommand(port, req);
×
125
        // some tabular motors can answer any request,
126
        // so check that motor id in the response matches the requested one
127
        if (resp[MOTOR_ID_POS] != MotorId || resp[LOW_CHANNEL_ID_POS] != LowChannelId ||
×
128
            resp[HIGH_CHANNEL_ID_POS] != HighChannelId)
×
129
        {
130
            throw TSerialDeviceTransientErrorException("Invalid response");
×
131
        }
132
        val.Set(Get<uint64_t>(resp.begin() + MOTOR_STATUS_DATA_OFFSET, resp.end() - CRC_SIZE));
×
133
        DataCache[key] = val;
×
134
    }
135
    if (bitOffset || bitWidth) {
×
136
        return TRegisterValue{(val.Get<uint64_t>() >> bitOffset) & GetLSBMask(bitWidth)};
×
137
    }
138
    return val;
×
139
}
140

141
std::vector<uint8_t> Aok::TDevice::ExecCommand(TPort& port, const TRequest& request)
×
142
{
143
    port.SleepSinceLastInteraction(GetFrameTimeout(port));
×
144
    port.SkipNoise();
×
145
    port.WriteBytes(request.Data);
×
146
    std::vector<uint8_t> respBytes(request.ResponseSize);
×
147
    if (request.ResponseSize != 0) {
×
148
        auto bytesRead = port.ReadFrame(respBytes.data(),
×
149
                                        respBytes.size(),
150
                                        GetResponseTimeout(port),
×
151
                                        GetFrameTimeout(port),
×
152
                                        ExpectNBytes(request.ResponseSize))
×
153
                             .Count;
×
154
        respBytes.resize(bytesRead);
×
155
    }
156
    return respBytes;
×
157
}
158

159
TRegisterValue Aok::TDevice::ReadRegisterImpl(TPort& port, const TRegisterConfig& reg)
×
160
{
161
    switch (reg.Type) {
×
162
        case POSITION: {
×
163
            return GetCachedResponse(port, MOTOR_STATUS, 0, MOTOR_STATUS_POSITION_OFFSET * 8, 8);
×
164
        }
165
        case STATUS: {
×
166
            auto addr = GetUint32RegisterAddress(reg.GetAddress());
×
167
            return GetCachedResponse(port, (addr >> 8) & 0xFF, addr & 0xFF, reg.GetDataOffset(), reg.GetDataWidth());
×
168
        }
169
        case ZONEBIT: {
×
170
            auto addr = GetUint32RegisterAddress(reg.GetAddress());
×
171
            return GetCachedResponse(port,
172
                                     CURTAIN_MOTOR_STATUS,
173
                                     CURTAIN_MOTOR_STATUS,
174
                                     MOTOR_STATUS_ZONEBIT_OFFSET * 8 + addr,
175
                                     1);
×
176
        }
177
    }
178
    throw TSerialDevicePermanentRegisterException("Unsupported register type");
×
179
}
180

181
void Aok::TDevice::WriteRegisterImpl(TPort& port, const TRegisterConfig& reg, const TRegisterValue& regValue)
×
182
{
183
    auto value = regValue.Get<uint64_t>();
×
184
    switch (reg.Type) {
×
185
        case COMMAND: {
×
186
            auto addr = GetUint32RegisterAddress(reg.GetWriteAddress());
×
187
            TRequest req;
×
188
            req.Data = MakeRequest(MotorId, LowChannelId, HighChannelId, CONTROL, addr);
×
189
            ExecCommand(port, req);
×
190
            return;
×
191
        }
192
        case POSITION: {
×
193
            TRequest req;
×
194
            req.Data = MakeRequest(MotorId, LowChannelId, HighChannelId, SET_POSITION, value);
×
195
            ExecCommand(port, req);
×
196
            return;
×
197
        }
198
        case PARAM: {
×
199
            auto addr = GetUint32RegisterAddress(reg.GetWriteAddress());
×
200
            TRequest req;
×
201
            req.Data = MakeRequest(MotorId, LowChannelId, HighChannelId, addr, value);
×
202
            ExecCommand(port, req);
×
203
            return;
×
204
        }
205
        case ZONEBIT: {
×
206
            auto addr = GetUint32RegisterAddress(reg.GetWriteAddress());
×
207
            TRegisterValue val =
208
                GetCachedResponse(port, CURTAIN_MOTOR_STATUS, CURTAIN_MOTOR_STATUS, MOTOR_STATUS_ZONEBIT_OFFSET * 8, 8);
×
209
            TRequest req;
×
210
            req.Data = MakeRequest(MotorId,
×
211
                                   LowChannelId,
212
                                   HighChannelId,
213
                                   SET_CURTAIN_MOTOR_SETTING,
214
                                   (val.Get<uint8_t>() & ~(1 << addr)) | (value << addr));
×
215
            ExecCommand(port, req);
×
216
            return;
×
217
        }
218
    }
219
    throw TSerialDevicePermanentRegisterException("Unsupported register type");
×
220
}
221

222
void Aok::TDevice::InvalidateReadCache()
×
223
{
224
    DataCache.clear();
×
225
}
226

227
std::vector<uint8_t> Aok::MakeRequest(uint8_t id,
9✔
228
                                      uint8_t channelLow,
229
                                      uint8_t channelHigh,
230
                                      uint8_t command,
231
                                      uint8_t data)
232
{
233
    std::vector<uint8_t> res{REQUEST, id, channelLow, channelHigh, command, data, 0x00};
9✔
234
    res.back() = CalcCrc({res.begin() + 1, res.end() - CRC_SIZE});
9✔
235
    return res;
9✔
236
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc