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

wirenboard / wb-mqtt-serial / 666

08 Jul 2025 11:10AM UTC coverage: 73.854% (+0.1%) from 73.706%
666

push

github

web-flow
Port handling refactoring (#960)

Pass port by reference when needed instead of storing it in every device

6444 of 9057 branches covered (71.15%)

526 of 700 new or added lines in 56 files covered. (75.14%)

6 existing lines in 5 files now uncovered.

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

4.59
/src/devices/energomera_ce_device.cpp
1
#include "energomera_ce_device.h"
2
#include "bin_utils.h"
3

4
using namespace BinUtils;
5

6
namespace
7
{
8
    const uint8_t END = 0xC0;
9
    const uint8_t OPT = 0x48;
10

11
    // Taken from example in the documentation
12
    const uint16_t ADDREIA = 253;
13

14
    const uint32_t USR_PASSWORD = 0x00000000;
15

16
    const uint8_t CLASS_ACCESS_COMMAND = 0x05;
17
    const uint8_t CLASS_ACCESS_ERROR = 0x07;
18

19
    const uint8_t DIRECT_REQUEST = 0x01;
20
    // const uint8_t DIRECT_RESPONSE = 0x00;
21

22
    const size_t PACKET_ENVELOPE_SIZE = 8;
23
    const size_t RESPONSE_HEADER_SIZE = 3;
24
    const size_t MAX_PAYLOAD_SIZE = 15;
25
    const size_t MAX_RESPONSE_SIZE = (PACKET_ENVELOPE_SIZE + RESPONSE_HEADER_SIZE + MAX_PAYLOAD_SIZE) * 2;
26
    const size_t MIN_RESPONSE_SIZE = PACKET_ENVELOPE_SIZE + RESPONSE_HEADER_SIZE + 1;
27

28
    const size_t RESPONSE_SERV_POS = 6;
29
    const size_t RESPONSE_DATA_POS = 9;
30

31
    const size_t ENERGY_RESPONSE_DATA_SIZE = 7;
32
    const size_t DATE_TIME_RESPONSE_DATA_SIZE = 7;
33

34
    const std::unordered_map<uint8_t, std::vector<uint8_t>> BYTE_STUFFING_RULES = {{0xC0, {0xDB, 0xDC}},
35
                                                                                   {0xDB, {0xDB, 0xDD}}};
36

37
    enum TEnergomeraCeRegisterType
38
    {
39
        DEFAULT,
40
        DATE,
41
        ENERGY
42
    };
43

44
    const TRegisterTypes RegisterTypes{{TEnergomeraCeRegisterType::DEFAULT, "default", "value", U16, true},
45
                                       {TEnergomeraCeRegisterType::DATE, "date", "text", String, true},
46
                                       {TEnergomeraCeRegisterType::ENERGY, "energy", "value", U32, true}};
47

48
    // Calc crc8 polynomial 0xB5
49
    uint8_t CalcCrc(std::vector<uint8_t>::const_iterator begin, std::vector<uint8_t>::const_iterator end)
×
50
    {
51
        uint8_t crc = 0x00;
×
52
        for (auto it = begin; it != end; ++it) {
×
53
            crc ^= *it;
×
54
            for (size_t i = 0; i < 8; ++i) {
×
55
                crc = crc & 0x80 ? (crc << 1) ^ 0xB5 : crc << 1;
×
56
            }
57
        }
58
        return crc;
×
59
    }
60

61
    uint8_t MakeServField(size_t messageLength)
×
62
    {
63
        return (DIRECT_REQUEST << 7) | (CLASS_ACCESS_COMMAND << 4) | (messageLength & 0x0F);
×
64
    }
65

66
    std::vector<uint8_t> MakePacket(uint16_t addr,
×
67
                                    const std::vector<uint8_t>& password,
68
                                    const std::vector<uint8_t>& data)
69
    {
70
        std::vector<uint8_t> content;
×
71
        auto contentBack = std::back_inserter(content);
×
72
        Append(contentBack, OPT);
×
73
        Append(contentBack, addr);
×
74
        Append(contentBack, ADDREIA);
×
75
        if (password.empty()) {
×
76
            Append(contentBack, USR_PASSWORD);
×
77
        } else {
78
            std::copy(password.begin(), password.end(), contentBack);
×
79
        }
80
        size_t dataSize = data.size() > 2 ? data.size() - 2 : 0;
×
81
        Append(contentBack, MakeServField(dataSize));
×
82
        std::copy(data.begin(), data.end(), contentBack);
×
83
        Append(contentBack, CalcCrc(content.begin(), content.end()));
×
84

85
        std::vector<uint8_t> packet;
×
86
        auto packetBack = std::back_inserter(packet);
×
87
        Append(packetBack, END);
×
88
        ApplyByteStuffing(content, BYTE_STUFFING_RULES, packetBack);
×
89
        Append(packetBack, END);
×
90
        return packet;
×
91
    }
92

93
    std::vector<uint8_t> MakeCommandFromRegisterAddress(uint32_t addr)
×
94
    {
95
        std::vector<uint8_t> data;
×
96
        uint8_t byte = (addr >> 24) & 0xFF;
×
97
        if (!data.empty() || byte != 0) {
×
98
            data.push_back(byte);
×
99
        }
100
        byte = (addr >> 16) & 0xFF;
×
101
        if (!data.empty() || byte != 0) {
×
102
            data.push_back(byte);
×
103
        }
104
        byte = (addr >> 8) & 0xFF;
×
105
        data.push_back(byte);
×
106
        byte = addr & 0xFF;
×
107
        data.push_back(byte);
×
108
        return data;
×
109
    }
110

111
    void CheckResponse(std::vector<uint8_t>& data)
×
112
    {
113
        if (data.size() < MIN_RESPONSE_SIZE) {
×
114
            throw TSerialDeviceTransientErrorException("frame too short");
×
115
        }
116
        if (data.front() != END || data.back() != END || data[1] != OPT) {
×
117
            throw TSerialDeviceTransientErrorException("invalid packet");
×
118
        }
119
        if (CalcCrc(data.begin() + 1, data.end() - 2) != *(data.end() - 2)) {
×
120
            throw TSerialDeviceTransientErrorException("invalid CRC");
×
121
        }
122
        if (((data[RESPONSE_SERV_POS] >> 4) & 0x7) == CLASS_ACCESS_ERROR) {
×
123
            throw TSerialDeviceTransientErrorException("error in response, code: " +
×
124
                                                       WBMQTT::HexDump(&data[RESPONSE_DATA_POS], 1));
×
125
        }
126
    }
127

128
    size_t GetResponseDataSize(const std::vector<uint8_t>& data)
×
129
    {
130
        return data.size() - PACKET_ENVELOPE_SIZE - RESPONSE_HEADER_SIZE;
×
131
    }
132

133
    TRegisterValue GetValue(const std::vector<uint8_t>& data)
×
134
    {
135
        const auto dataSize = GetResponseDataSize(data);
×
136
        switch (dataSize) {
×
137
            case 1:
×
138
                return TRegisterValue(data[RESPONSE_DATA_POS]);
×
139
            case 2:
×
140
                return TRegisterValue(GetFromBigEndian<uint16_t>(data.begin() + RESPONSE_DATA_POS));
×
141
            case 3:
×
142
                return TRegisterValue(
143
                    GetBigEndian<uint32_t>(data.begin() + RESPONSE_DATA_POS, data.begin() + RESPONSE_DATA_POS + 3));
×
144
            case 4:
×
145
                return TRegisterValue(GetFromBigEndian<uint32_t>(data.begin() + RESPONSE_DATA_POS));
×
146
            default:
×
147
                throw TSerialDeviceTransientErrorException("Data size is too big");
×
148
        }
149
    }
150

151
    TRegisterValue GetDate(const std::vector<uint8_t>& data)
×
152
    {
153
        if (GetResponseDataSize(data) != DATE_TIME_RESPONSE_DATA_SIZE) {
×
154
            throw TSerialDeviceTransientErrorException("Data size mismatch");
×
155
        }
156
        std::stringstream ss;
×
157
        ss << std::hex << std::setfill('0') << std::setw(2) << int(data[RESPONSE_DATA_POS + 4]) << "." << std::setw(2)
×
158
           << int(data[RESPONSE_DATA_POS + 5]) << "." << std::setw(2) << int(data[RESPONSE_DATA_POS + 6]) << " "
×
159
           << std::setw(2) << int(data[RESPONSE_DATA_POS + 2]) << ":" << std::setw(2)
×
160
           << int(data[RESPONSE_DATA_POS + 1]) << ":" << std::setw(2) << int(data[RESPONSE_DATA_POS]);
×
161
        return TRegisterValue(ss.str());
×
162
    }
163

164
    TRegisterValue GetEnergy(const std::vector<uint8_t>& data)
×
165
    {
166
        if (GetResponseDataSize(data) != ENERGY_RESPONSE_DATA_SIZE) {
×
167
            throw TSerialDeviceTransientErrorException("Data size mismatch");
×
168
        }
169
        return TRegisterValue(GetFrom<uint32_t>(data.begin() + RESPONSE_DATA_POS + 3));
×
170
    }
171

172
    bool FrameComplete(uint8_t* buf, int size)
×
173
    {
174
        return (static_cast<size_t>(size) >= MIN_RESPONSE_SIZE && buf[0] == END && buf[size - 1] == END);
×
175
    }
176
}
177

178
void TEnergomeraCeDevice::Register(TSerialDeviceFactory& factory)
143✔
179
{
180
    factory.RegisterProtocol(
143✔
181
        new TUint32SlaveIdProtocol("energomera_ce", RegisterTypes),
286✔
182
        new TBasicDeviceFactory<TEnergomeraCeDevice>("#/definitions/simple_device", "#/definitions/common_channel"));
286✔
183
}
143✔
184

NEW
185
TEnergomeraCeDevice::TEnergomeraCeDevice(PDeviceConfig config, PProtocol protocol)
×
186
    : TSerialDevice(config, protocol),
UNCOV
187
      TUInt32SlaveId(config->SlaveId)
×
188
{}
189

NEW
190
TRegisterValue TEnergomeraCeDevice::ReadRegisterImpl(TPort& port, const TRegisterConfig& reg)
×
191
{
192
    auto request = MakePacket(SlaveId,
×
193
                              DeviceConfig()->Password,
×
194
                              MakeCommandFromRegisterAddress(GetUint32RegisterAddress(reg.GetAddress())));
×
NEW
195
    port.WriteBytes(request.data(), request.size());
×
196
    std::vector<uint8_t> response(MAX_RESPONSE_SIZE);
×
197
    auto res = port.ReadFrame(response.data(),
198
                              response.size(),
NEW
199
                              GetResponseTimeout(port),
×
NEW
200
                              GetFrameTimeout(port),
×
NEW
201
                              FrameComplete);
×
202
    response.resize(res.Count);
×
203
    DecodeByteStuffing(response, BYTE_STUFFING_RULES);
×
204
    CheckResponse(response);
×
205
    switch (reg.Type) {
×
206
        case TEnergomeraCeRegisterType::DEFAULT:
×
207
            return GetValue(response);
×
208
        case TEnergomeraCeRegisterType::DATE:
×
209
            return GetDate(response);
×
210
        case TEnergomeraCeRegisterType::ENERGY:
×
211
            return GetEnergy(response);
×
212
        default: {
×
213
            throw TSerialDevicePermanentRegisterException("Unknown register type " + std::to_string(reg.Type));
×
214
        }
215
    }
216
}
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