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

wirenboard / wb-mqtt-serial / 2

29 Dec 2025 12:28PM UTC coverage: 76.817% (+4.0%) from 72.836%
2

Pull #1045

github

54aa0c
pgasheev
up changelog
Pull Request #1045: Fix firmware version in WB-M1W2 template

6873 of 9161 branches covered (75.02%)

12966 of 16879 relevant lines covered (76.82%)

1651.61 hits per line

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

66.91
/src/devices/milur_device.cpp
1
#include "milur_device.h"
2
#include "bcd_utils.h"
3

4
namespace
5
{
6

7
    TPort::TFrameCompletePred ExpectNBytes(size_t slave_id_width, size_t n)
132✔
8
    {
9
        return [slave_id_width, n](uint8_t* buf, size_t size) {
3,164✔
10
            if (size < slave_id_width + 1)
1,156✔
11
                return false;
152✔
12
            if (buf[slave_id_width] & 0x80)
1,004✔
13
                return size >= 5 + slave_id_width; // exception response
20✔
14
            return size >= n;
984✔
15
        };
132✔
16
    }
17

18
    const TRegisterTypes RegisterTypes{{TMilurDevice::REG_PARAM, "param", "value", U24, true},
19
                                       {TMilurDevice::REG_POWER, "power", "power", S32, true},
20
                                       {TMilurDevice::REG_ENERGY, "energy", "power_consumption", BCD32, true},
21
                                       {TMilurDevice::REG_FREQ, "freq", "value", U16, true},
22
                                       {TMilurDevice::REG_POWERFACTOR, "power_factor", "value", S16, true}};
23
}
24

25
void TMilurDevice::Register(TSerialDeviceFactory& factory)
288✔
26
{
27
    factory.RegisterProtocol(
288✔
28
        new TUint32SlaveIdProtocol("milur", RegisterTypes),
576✔
29
        new TBasicDeviceFactory<TMilurDevice>("#/definitions/simple_device", "#/definitions/common_channel"));
576✔
30
}
288✔
31

32
TMilurDevice::TMilurDevice(PDeviceConfig device_config, PProtocol protocol): TEMDevice(device_config, protocol)
18✔
33
{
34
    /* FIXME: Milur driver should set address width based on slave_id string:
35
    0xFF: 1-byte address
36
    255: 1-byte address
37
    163050000049932: parse as serial number, use 4-byte addressing with slave id = 49932
38
    */
39

40
    if (SlaveId > 0xFF) {
18✔
41
        SlaveIdWidth = 4;
2✔
42
    }
43
}
18✔
44

45
bool TMilurDevice::ConnectionSetup(TPort& port)
20✔
46
{
47
    // full: 0xff, 0x08, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x5f, 0xed
48
    uint8_t setupCmd[7] = {uint8_t(DeviceConfig()->AccessLevel), 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
20✔
49

50
    std::vector<uint8_t> password = DeviceConfig()->Password;
40✔
51
    if (password.size()) {
20✔
52
        if (password.size() != 6)
4✔
53
            throw TSerialDeviceException("invalid password size (6 bytes expected)");
×
54
        std::copy(password.begin(), password.end(), setupCmd + 1);
4✔
55
    }
56

57
    uint8_t buf[MAX_LEN];
20✔
58
    WriteCommand(port, 0x08, setupCmd, 7);
20✔
59
    try {
60
        if (!ReadResponse(port, 0x08, buf, 1, ExpectNBytes(SlaveIdWidth, SlaveIdWidth + 4)))
20✔
61
            return false;
×
62
        if (buf[0] != uint8_t(DeviceConfig()->AccessLevel))
20✔
63
            throw TSerialDeviceException("invalid milur access level in response");
×
64
        return true;
20✔
65
    } catch (TSerialDeviceTransientErrorException&) {
×
66
        // retry upon response from a wrong slave
67
        return false;
×
68
    }
20✔
69
}
70

71
TEMDevice::ErrorType TMilurDevice::CheckForException(uint8_t* frame, int len, const char** message)
134✔
72
{
73
    if (len != 6 || !(frame[1] & 0x80)) {
134✔
74
        *message = 0;
130✔
75
        return TEMDevice::NO_ERROR;
130✔
76
    }
77

78
    switch (frame[2]) {
4✔
79
        case 0x01:
×
80
            *message = "Illegal function";
×
81
            break;
×
82
        case 0x02:
×
83
            *message = "Illegal data address";
×
84
            break;
×
85
        case 0x03:
×
86
            *message = "Illegal data value";
×
87
            break;
×
88
        case 0x04:
×
89
            *message = "Slave device failure";
×
90
            break;
×
91
        case 0x05:
×
92
            *message = "Acknowledge";
×
93
            break;
×
94
        case 0x06:
×
95
            *message = "Slave device busy";
×
96
            break;
×
97
        case 0x07:
2✔
98
            *message = "EEPROM access error";
2✔
99
            break;
2✔
100
        case 0x08:
2✔
101
            *message = "Session closed";
2✔
102
            return TEMDevice::NO_OPEN_SESSION;
2✔
103
        case 0x09:
×
104
            *message = "Access denied";
×
105
            break;
×
106
        case 0x0a:
×
107
            *message = "CRC error";
×
108
            break;
×
109
        case 0x0b:
×
110
            *message = "Frame incorrect";
×
111
            break;
×
112
        case 0x0c:
×
113
            *message = "Jumper absent";
×
114
            break;
×
115
        case 0x0d:
×
116
            *message = "Passwd incorrect";
×
117
            break;
×
118
        default:
×
119
            *message = "Unknown error";
×
120
    }
121
    return TEMDevice::OTHER_ERROR;
2✔
122
}
123

124
TRegisterValue TMilurDevice::ReadRegisterImpl(TPort& port, const TRegisterConfig& reg)
112✔
125
{
126
    TRegisterValue retVal;
112✔
127
    uint8_t addr = GetUint32RegisterAddress(reg.GetAddress());
112✔
128
    int size = GetExpectedSize(reg.Type);
112✔
129
    uint8_t buf[MAX_LEN], *p = buf;
112✔
130
    Talk(port, 0x01, &addr, 1, 0x01, buf, size + 2, ExpectNBytes(SlaveIdWidth, size + 5 + SlaveIdWidth));
114✔
131
    if (*p++ != addr)
110✔
132
        throw TSerialDeviceTransientErrorException("bad register address in the response");
×
133
    if (*p != size)
110✔
134
        throw TSerialDeviceTransientErrorException("bad register size in the response");
×
135

136
    switch (reg.Type) {
110✔
137
        case TMilurDevice::REG_PARAM:
50✔
138
            retVal.Set(BuildIntVal(buf + 2, 3));
50✔
139
            break;
50✔
140
        case TMilurDevice::REG_POWER:
16✔
141
            retVal.Set(BuildIntVal(buf + 2, 4));
16✔
142
            break;
16✔
143
        case TMilurDevice::REG_ENERGY:
26✔
144
            retVal.Set(BuildBCB32(buf + 2));
26✔
145
            break;
26✔
146
        case TMilurDevice::REG_POWERFACTOR:
18✔
147
        case TMilurDevice::REG_FREQ:
148
            retVal.Set(BuildIntVal(buf + 2, 2));
18✔
149
            break;
18✔
150
        default:
×
151
            throw TSerialDeviceTransientErrorException("bad register type");
×
152
    }
153
    return retVal;
332✔
154
}
155

156
void TMilurDevice::PrepareImpl(TPort& port)
4✔
157
{
158
    /* Milur 104 ignores the request after receiving any packet
159
    with length of 8 bytes. The last answer of the previously polled device
160
    could make Milur 104 unresponsive. To make sure the meter is operational,
161
    we send dummy packet (0xFF in this case) which will restore normal meter operation. */
162

163
    uint8_t buf[] = {0xFF};
4✔
164
    port.WriteBytes(buf, sizeof(buf) / sizeof(buf[0]));
4✔
165
    TSerialDevice::PrepareImpl(port);
4✔
166
    port.SkipNoise();
4✔
167
}
4✔
168

169
uint64_t TMilurDevice::BuildIntVal(uint8_t* p, int sz) const
84✔
170
{
171
    uint64_t r = 0;
84✔
172
    for (int i = 0; i < sz; ++i) {
334✔
173
        r += p[i] << (i * 8);
250✔
174
    }
175
    return r;
84✔
176
}
177

178
// We transfer BCD byte arrays as unsigned little endian integers with swapped nibbles.
179
// To convert it to our standard transport BCD representation (ie. integer with hexadecimal
180
// that reads exactly as original BCD if printed) we just have to swap nibbles of each byte
181
// that is decimal value 87654321 comes as {0x12, 0x34, 0x56, 0x78} and becomes {0x21, 0x43, 0x65, 0x87}.
182
uint64_t TMilurDevice::BuildBCB32(uint8_t* psrc) const
26✔
183
{
184
    uint32_t r = 0;
26✔
185
    uint8_t* pdst = reinterpret_cast<uint8_t*>(&r);
26✔
186
    for (int i = 0; i < 4; ++i) {
130✔
187
        auto t = psrc[i];
104✔
188
        pdst[i] = (t >> 4) | (t << 4);
104✔
189
    }
190
    return r;
26✔
191
}
192

193
int TMilurDevice::GetExpectedSize(int type) const
112✔
194
{
195
    auto t = static_cast<TMilurDevice::RegisterType>(type);
112✔
196
    switch (t) {
112✔
197
        case TMilurDevice::REG_PARAM:
52✔
198
            return 3;
52✔
199
        case TMilurDevice::REG_POWER:
42✔
200
        case TMilurDevice::REG_ENERGY:
201
            return 4;
42✔
202
        case TMilurDevice::REG_FREQ:
18✔
203
        case TMilurDevice::REG_POWERFACTOR:
204
            return 2;
18✔
205
        default:
×
206
            throw TSerialDeviceTransientErrorException("bad register type");
×
207
    }
208
}
209

210
std::chrono::milliseconds TMilurDevice::GetFrameTimeout(TPort& port) const
138✔
211
{
212
    return std::max(DeviceConfig()->FrameTimeout,
276✔
213
                    std::chrono::ceil<std::chrono::milliseconds>(port.GetSendTimeBytes(3.5)));
276✔
214
}
215

216
#if 0
217
#include <iomanip>
218
#include <iostream>
219
int main(int, char**)
220
{
221
    try {
222
        TSerialPortSettings settings("/dev/ttyNSC0", 9600, 'N', 8, 2, 1000);
223
        PPort port(new TSerialPort(settings, true));
224
        port->Open();
225
        TMilurDevice milur(port);
226
        std::ios::fmtflags f(std::cerr.flags());
227
        int v = milur.ReadRegister(0xff, 102, 0, U24);
228
        std::cerr << "value of mod 0xff reg 0x66: 0x" << std::setw(8) << std::hex << v << std::endl;
229
        std::cerr.flags(f);
230
        std::cerr << "dec value: " << v << std::endl;
231

232
        int v1 = milur.ReadRegister(0xff, 118, 0, BCD32);
233
        std::cerr << "value of mod 0xff reg 0x76: " << v1 << std::endl;
234
    } catch (const TSerialDeviceException& e) {
235
        std::cerr << "milur: " << e.what() << std::endl;
236
    }
237
    return 0;
238
}
239
#endif
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