• 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

83.33
/src/devices/mercury230_device.cpp
1
#include "mercury230_device.h"
2
#include "crc16.h"
3
#include "serial_config.h"
4
#include <cassert>
5
#include <iostream>
6

7
namespace
8
{
9
    // clang-format off
10
    const TRegisterTypes RegisterTypes{
11
        { TMercury230Device::REG_VALUE_ARRAY,       "array",               "power_consumption", U32, true },
12
        { TMercury230Device::REG_VALUE_ARRAY12,     "array12",             "power_consumption", U32, true },
13
        { TMercury230Device::REG_PARAM,             "param",               "value",             U24, true },
14
        { TMercury230Device::REG_PARAM_SIGN_ACT,    "param_sign_active",   "value",             S24, true },
15
        { TMercury230Device::REG_PARAM_SIGN_REACT,  "param_sign_reactive", "value",             S24, true },
16
        { TMercury230Device::REG_PARAM_SIGN_IGNORE, "param_sign_ignore",   "value",             U24, true },
17
        { TMercury230Device::REG_PARAM_BE,          "param_be",            "value",             S24, true }
18
    };
19
    // clang-format on
20

21
    class TMercury230DeviceRegisterAddressFactory: public TStringRegisterAddressFactory
22
    {
23
    public:
24
        TRegisterDesc LoadRegisterAddress(const Json::Value& regCfg,
39✔
25
                                          const IRegisterAddress& deviceBaseAddress,
26
                                          uint32_t stride,
27
                                          uint32_t registerByteWidth) const override
28
        {
29
            auto addr = LoadRegisterBitsAddress(regCfg, SerialConfig::ADDRESS_PROPERTY_NAME);
39✔
30
            TRegisterDesc res;
39✔
31
            auto type = regCfg["reg_type"];
78✔
32
            if (type == "array" || type == "array12") {
39✔
33
                res.DataOffset = (addr.Address & 0x03);
8✔
34
                res.Address = std::make_shared<TUint32RegisterAddress>(addr.Address >> 4);
8✔
35
            } else {
36
                res.Address = std::make_shared<TUint32RegisterAddress>(addr.Address);
31✔
37
            }
38
            return res;
78✔
39
        }
40
    };
41
}
42

43
void TMercury230Device::Register(TSerialDeviceFactory& factory)
143✔
44
{
45
    factory.RegisterProtocol(new TUint32SlaveIdProtocol("mercury230", RegisterTypes, true),
429✔
46
                             new TBasicDeviceFactory<TMercury230Device, TMercury230DeviceRegisterAddressFactory>(
47
                                 "#/definitions/simple_device_with_broadcast",
48
                                 "#/definitions/common_channel"));
286✔
49
}
143✔
50

51
TMercury230Device::TMercury230Device(PDeviceConfig device_config, PProtocol protocol)
9✔
52
    : TEMDevice(device_config, protocol)
9✔
53
{}
9✔
54

55
bool TMercury230Device::ConnectionSetup(TPort& port)
10✔
56
{
57
    uint8_t setupCmd[7] = {uint8_t(DeviceConfig()->AccessLevel), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01};
10✔
58

59
    std::vector<uint8_t> password = DeviceConfig()->Password;
20✔
60
    if (password.size()) {
10✔
61
        if (password.size() != 6)
2✔
62
            throw TSerialDeviceException("invalid password size (6 bytes expected)");
×
63
        std::copy(password.begin(), password.end(), setupCmd + 1);
2✔
64
    }
65

66
    uint8_t buf[1];
67
    WriteCommand(port, 0x01, setupCmd, 7);
10✔
68
    try {
69
        return ReadResponse(port, 0x00, buf, 0);
10✔
70
    } catch (TSerialDeviceTransientErrorException&) {
×
71
        // retry upon response from a wrong slave
72
        return false;
×
73
    } catch (TSerialDevicePermanentRegisterException&) {
×
74
        return false;
×
75
    }
76
}
77

78
TEMDevice::ErrorType TMercury230Device::CheckForException(uint8_t* frame, int len, const char** message)
96✔
79
{
80
    *message = 0;
96✔
81
    if (len != 4 || (frame[1] & 0x0f) == 0)
96✔
82
        return TEMDevice::NO_ERROR;
94✔
83
    switch (frame[1] & 0x0f) {
2✔
84
        case 1:
×
85
            *message = "Invalid command or parameter";
×
86
            return TEMDevice::PERMANENT_ERROR;
×
87
        case 2:
1✔
88
            *message = "Internal meter error";
1✔
89
            break;
1✔
90
        case 3:
×
91
            *message = "Insufficient access level";
×
92
            break;
×
93
        case 4:
×
94
            *message = "Can't correct the clock more than once per day";
×
95
            break;
×
96
        case 5:
1✔
97
            *message = "Connection closed";
1✔
98
            return TEMDevice::NO_OPEN_SESSION;
1✔
99
        default:
×
100
            *message = "Unknown error";
×
101
    }
102
    return TEMDevice::OTHER_ERROR;
1✔
103
}
104

105
const TMercury230Device::TValueArray& TMercury230Device::ReadValueArray(TPort& port, uint32_t address, int resp_len)
14✔
106
{
107
    int key = address;
14✔
108
    auto it = CachedValues.find(key);
14✔
109
    if (it != CachedValues.end())
14✔
110
        return it->second;
8✔
111

112
    uint8_t cmdBuf[2];
113
    cmdBuf[0] = (uint8_t)(address & 0xff);        // high nibble = array number, lower nibble = month
6✔
114
    cmdBuf[1] = (uint8_t)((address >> 8) & 0x0f); // tariff
6✔
115
    uint8_t buf[MAX_LEN], *p = buf;
6✔
116
    TValueArray a;
117
    Talk(port, 0x05, cmdBuf, 2, -1, buf, resp_len * 4);
6✔
118
    for (int i = 0; i < resp_len; i++, p += 4) {
28✔
119
        a.values[i] = ((uint32_t)p[1] << 24) + ((uint32_t)p[0] << 16) + ((uint32_t)p[3] << 8) + (uint32_t)p[2];
22✔
120
    }
121

122
    return CachedValues.insert(std::make_pair(key, a)).first->second;
20✔
123
}
124

125
uint32_t TMercury230Device::ReadParam(TPort& port, uint32_t address, unsigned resp_payload_len, RegisterType reg_type)
79✔
126
{
127
    uint8_t cmdBuf[2];
128
    cmdBuf[0] = (address >> 8) & 0xff; // param
79✔
129
    cmdBuf[1] = address & 0xff;        // subparam (BWRI)
79✔
130

131
    assert(resp_payload_len <= 3);
79✔
132
    uint8_t buf[3] = {};
79✔
133
    Talk(port, 0x08, cmdBuf, 2, -1, buf, resp_payload_len);
80✔
134

135
    if (resp_payload_len == 3) {
78✔
136
        if ((reg_type == REG_PARAM_SIGN_ACT) || (reg_type == REG_PARAM_SIGN_REACT) ||
65✔
137
            (reg_type == REG_PARAM_SIGN_IGNORE))
138
        {
139
            uint32_t magnitude = (((uint32_t)buf[0] & 0x3f) << 16) + ((uint32_t)buf[2] << 8) + (uint32_t)buf[1];
24✔
140

141
            int active_power_sign = (buf[0] & (1 << 7)) ? -1 : 1;
24✔
142
            int reactive_power_sign = (buf[0] & (1 << 6)) ? -1 : 1;
24✔
143

144
            int sign = 1;
24✔
145

146
            if (reg_type == REG_PARAM_SIGN_ACT) {
24✔
147
                sign = active_power_sign;
18✔
148
            } else if (reg_type == REG_PARAM_SIGN_REACT) {
6✔
149
                sign = reactive_power_sign;
6✔
150
            }
151

152
            return (uint32_t)(((int32_t)magnitude * sign));
24✔
153
        } else {
154
            return ((uint32_t)buf[0] << 16) + ((uint32_t)buf[2] << 8) + (uint32_t)buf[1];
41✔
155
        }
156
    } else {
157
        if (reg_type == REG_PARAM_BE) {
13✔
158
            return ((uint32_t)buf[0] << 8) + ((uint32_t)buf[1]);
10✔
159
        } else {
160
            return ((uint32_t)buf[1] << 8) + ((uint32_t)buf[0]);
3✔
161
        }
162
    }
163
}
164

165
TRegisterValue TMercury230Device::ReadRegisterImpl(TPort& port, const TRegisterConfig& reg)
93✔
166
{
167
    auto addr = GetUint32RegisterAddress(reg.GetAddress());
93✔
168
    switch (reg.Type) {
93✔
169
        case REG_VALUE_ARRAY:
10✔
170
            return TRegisterValue{ReadValueArray(port, addr, 4).values[reg.GetDataOffset() & 0x03]};
10✔
171
        case REG_VALUE_ARRAY12:
4✔
172
            return TRegisterValue{ReadValueArray(port, addr, 3).values[reg.GetDataOffset() & 0x03]};
4✔
173
        case REG_PARAM:
79✔
174
        case REG_PARAM_SIGN_ACT:
175
        case REG_PARAM_SIGN_REACT:
176
        case REG_PARAM_SIGN_IGNORE:
177
        case REG_PARAM_BE:
178
            return TRegisterValue{ReadParam(port, addr & 0xffff, reg.GetByteWidth(), (RegisterType)reg.Type)};
79✔
179
        default:
×
180
            throw TSerialDeviceException("mercury230: invalid register type");
×
181
    }
182
}
183

184
void TMercury230Device::InvalidateReadCache()
44✔
185
{
186
    CachedValues.clear();
44✔
187
    TSerialDevice::InvalidateReadCache();
44✔
188
}
44✔
189

190
std::chrono::milliseconds TMercury230Device::GetFrameTimeout(TPort& port) const
98✔
191
{
192
    /*
193
        Mercury 230 documentation:
194
        ~2 ms  - 34800
195
        ~3 ms  - 19200
196
        ~5 ms  - 9600
197
        ~10 ms - 4800
198
        ~20 ms - 2400
199
        etc
200

201
        Mercury 200 documentation says about 5-6 bytes delay. It is similar to table for Mercury 230.
202
    */
203
    return std::max(DeviceConfig()->FrameTimeout,
196✔
204
                    std::chrono::ceil<std::chrono::milliseconds>(port.GetSendTimeBytes(6)));
196✔
205
}
206

207
std::chrono::milliseconds TMercury230Device::GetResponseTimeout(TPort& port) const
96✔
208
{
209
    /*
210
        Mercury 230 documentation:
211
        150 ms  - 9600-34800
212
        180 ms  - 4800
213
        250 ms  - 2400
214
        400 ms  - 1200
215
        800 ms  - 600
216
        1600 ms - 300
217
    */
218
    const std::chrono::milliseconds minTimeout(150);
96✔
219
    auto timeout = std::max(minTimeout,
220
                            std::chrono::milliseconds(115) +
96✔
221
                                std::chrono::ceil<std::chrono::milliseconds>(port.GetSendTimeBytes(35)));
96✔
222
    return std::max(DeviceConfig()->FrameTimeout, timeout);
96✔
223
}
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