Skip to content
This repository has been archived by the owner on Dec 19, 2017. It is now read-only.

Commit

Permalink
Merge branch 'feature/pulsar'
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikita webconn Maslov committed Jul 4, 2016
2 parents 0c33eed + 817bd47 commit 906a1c8
Show file tree
Hide file tree
Showing 11 changed files with 481 additions and 1 deletion.
4 changes: 3 additions & 1 deletion wb-mqtt-serial/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ SERIAL_SRCS=register.cpp \
modbus_device.cpp \
em_device.cpp \
milur_device.cpp \
mercury230_device.cpp
mercury230_device.cpp \
pulsar_device.cpp
SERIAL_OBJS=$(SERIAL_SRCS:.cpp=.o)
TEST_SRCS= \
$(TEST_DIR)/testlog.o \
Expand All @@ -59,6 +60,7 @@ TEST_SRCS= \
$(TEST_DIR)/em_test.o \
$(TEST_DIR)/em_integration.o \
$(TEST_DIR)/ivtm_test.o \
$(TEST_DIR)/pulsar_test.o \
$(TEST_DIR)/fake_mqtt.o \
$(TEST_DIR)/fake_serial_port.o \
$(TEST_DIR)/pty_based_fake_serial.o \
Expand Down
22 changes: 22 additions & 0 deletions wb-mqtt-serial/config-pulsar.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"debug": true,
"ports": [
{
"path" : "/tmp/ttyNSC1",
"protocol": "pulsar",
"baud_rate": 9600,
"parity": "N",
"data_bits": 8,
"stop_bits": 1,
"enabled": true,
"response_timeout_ms": 500,
"poll_interval" : 100,
"devices": [
{
"slave_id": 1,
"device_type": "Pulsar"
}
]
}
]
}
7 changes: 7 additions & 0 deletions wb-mqtt-serial/debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
wb-mqtt-serial (1.16) stable; urgency=medium

* adds support for Pulsar and Pulsar-M water meters
* adds support for Pulsar compact heat meter

-- Nikita Maslov <[email protected]> Mon, 04 Jul 2016 15:08:58 +0300

wb-mqtt-serial (1.15.2) stable; urgency=medium

* adds schema for WB-MSGR
Expand Down
232 changes: 232 additions & 0 deletions wb-mqtt-serial/pulsar_device.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
/* vim: set ts=4 sw=4: */

#include <cstring>
#include <cstdlib>

#include "pulsar_device.h"

/* FIXME: move this to configuration file! */
namespace {
const int FrameTimeout = 300;
}

REGISTER_PROTOCOL("pulsar", TPulsarDevice, TRegisterTypes({
{ TPulsarDevice::REG_DEFAULT, "default", "value", Double, true },
{ TPulsarDevice::REG_SYSTIME, "systime", "value", U64, true }
}));

TPulsarDevice::TPulsarDevice(PDeviceConfig device_config, PAbstractSerialPort port)
: TSerialDevice(device_config, port)
, RequestID(0) {}

uint16_t TPulsarDevice::CalculateCRC16(const uint8_t *buffer, size_t size)
{
uint16_t w;
uint16_t shift_cnt, f;
const uint8_t *ptrByte;

uint16_t byte_cnt = size;
ptrByte = buffer;
w = (uint16_t) 0xFFFF;

for (; byte_cnt > 0; byte_cnt--) {
w = (uint16_t) (w ^ (uint16_t) (*ptrByte++));
for (shift_cnt = 0; shift_cnt < 8; shift_cnt++) {
f = (uint8_t) ((w) & 0x01);
w >>= 1;
if (f)
w = (uint16_t) (w ^ 0xa001);
}
}

return w;
}

void TPulsarDevice::WriteBCD(uint64_t value, uint8_t *buffer, size_t size, bool big_endian)
{
for (size_t i = 0; i < size; i++) {
// form byte from the end of value
uint8_t byte = value % 10;
value /= 10;
byte |= (value % 10) << 4;
value /= 10;

buffer[big_endian ? size - i - 1 : i] = byte;
}
}

void TPulsarDevice::WriteHex(uint64_t value, uint8_t *buffer, size_t size, bool big_endian)
{
for (size_t i = 0; i < size; i++) {
buffer[big_endian ? size - i - 1 : i] = value & 0xFF;
value >>= 8;
}
}

uint64_t TPulsarDevice::ReadBCD(const uint8_t *buffer, size_t size, bool big_endian)
{
uint64_t result = 0;

for (size_t i = 0; i < size; i++) {
result *= 100;

uint8_t bcd_byte = buffer[big_endian ? i : size - i - 1];
uint8_t dec_byte = (bcd_byte & 0x0F) + 10 * ((bcd_byte >> 4) & 0x0F);

result += dec_byte;
}

return result;
}

uint64_t TPulsarDevice::ReadHex(const uint8_t *buffer, size_t size, bool big_endian)
{
uint64_t result = 0;

for (size_t i = 0; i < size; i++) {
result <<= 8;
result |= buffer[big_endian ? i : size - i - 1];
}

return result;
}

void TPulsarDevice::WriteDataRequest(uint32_t addr, uint32_t mask, uint16_t id)
{
Port()->CheckPortOpen();

uint8_t buf[14];

/* header = device address in big-endian BCD */
WriteBCD(addr, buf, sizeof (uint32_t), true);

/* data request => F == 1, L == 14 */
buf[4] = 1;
buf[5] = 14;

/* data mask in little-endian */
WriteHex(mask, &buf[6], sizeof (uint32_t), false);

/* request ID */
WriteHex(id, &buf[10], sizeof (uint16_t), false);

/* CRC16 */
uint16_t crc = CalculateCRC16(buf, 12);
WriteHex(crc, &buf[12], sizeof (uint16_t), false);

Port()->WriteBytes(buf, 14);
}

void TPulsarDevice::WriteSysTimeRequest(uint32_t addr, uint16_t id)
{
Port()->CheckPortOpen();

uint8_t buf[10];

/* header = device address in big-endian BCD */
WriteBCD(addr, buf, sizeof (uint32_t), true);

/* sys time request => F == 4, L == 10 */
buf[4] = 1;
buf[5] = 10;

/* request ID */
WriteHex(id, &buf[6], sizeof (uint16_t), false);

/* CRC16 */
uint16_t crc = CalculateCRC16(buf, 8);
WriteHex(crc, &buf[8], sizeof (uint16_t), false);

Port()->WriteBytes(buf, 10);
}

void TPulsarDevice::ReadResponse(uint32_t addr, uint8_t *payload, size_t size, uint16_t id)
{
const int exp_size = size + 10; /* payload size + service bytes */
uint8_t response[exp_size];

int nread = Port()->ReadFrame(response, exp_size, std::chrono::milliseconds(FrameTimeout),
[] (uint8_t *buf, int size) {
return size >= 6 && size == buf[5];
});

/* check size */
if (nread < 6)
throw TSerialDeviceTransientErrorException("frame is too short");

if (nread != exp_size)
throw TSerialDeviceTransientErrorException("unexpected end of frame");

if (exp_size != response[5])
throw TSerialDeviceTransientErrorException("unexpected frame length");

/* check CRC16 */
uint16_t crc_recv = ReadHex(&response[nread - 2], sizeof (uint16_t), false);
if (crc_recv != CalculateCRC16(response, nread - 2))
throw TSerialDeviceTransientErrorException("CRC mismatch");

/* check address */
uint32_t addr_recv = ReadBCD(response, sizeof (uint32_t), true);
if (addr_recv != addr)
throw TSerialDeviceTransientErrorException("slave address mismatch");

/* check request ID */
uint16_t id_recv = ReadHex(&response[nread - 4], sizeof (uint16_t), false);
if (id_recv != id)
throw TSerialDeviceTransientErrorException("request ID mismatch");

/* copy payload data to external buffer */
memcpy(payload, response + 6, size);
}

uint64_t TPulsarDevice::ReadDataRegister(PRegister reg)
{
// raw payload data
uint8_t payload[sizeof (uint64_t)];

// form register mask from address
uint32_t mask = 1 << reg->Address; // TODO: register range or something like this

// send data request and receive response
WriteDataRequest(reg->Slave->Id, mask, RequestID);
ReadResponse(reg->Slave->Id, payload, reg->ByteWidth(), RequestID);

++RequestID;

// decode little-endian double64_t value
return ReadHex(payload, reg->ByteWidth(), false);
}

uint64_t TPulsarDevice::ReadSysTimeRegister(PRegister reg)
{
// raw payload data
uint8_t payload[6];

// send system time request and receive response
WriteSysTimeRequest(reg->Slave->Id, RequestID);
ReadResponse(reg->Slave->Id, payload, sizeof (payload), RequestID);

++RequestID;

// decode little-endian double64_t value
return ReadHex(payload, sizeof (payload), false);
}

uint64_t TPulsarDevice::ReadRegister(PRegister reg)
{
Port()->SkipNoise();

switch (reg->Type) {
case REG_DEFAULT:
return ReadDataRegister(reg);
case REG_SYSTIME: // TODO: think about return value
return ReadSysTimeRegister(reg);
default:
throw TSerialDeviceException("Pulsar protocol: wrong register type");
}
}

void TPulsarDevice::WriteRegister(PRegister reg, uint64_t value)
{
throw TSerialDeviceException("Pulsar protocol: writing to registers is not supported");
}
42 changes: 42 additions & 0 deletions wb-mqtt-serial/pulsar_device.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* vim: set ts=4 sw=4: */

#pragma once

#include <memory>
#include "serial_device.h"
#include <stdint.h>


class TPulsarDevice: public TSerialDevice {
public:

enum RegisterType {
REG_DEFAULT,
REG_SYSTIME
};

TPulsarDevice(PDeviceConfig device_config, PAbstractSerialPort port);
uint64_t ReadRegister(PRegister reg);
void WriteRegister(PRegister reg, uint64_t value);

private:
void WriteBCD(uint64_t data, uint8_t *buffer, size_t size, bool big_endian = true);
void WriteHex(uint64_t data, uint8_t *buffer, size_t size, bool big_endian = true);

uint64_t ReadBCD(const uint8_t *data, size_t size, bool big_endian = true);
uint64_t ReadHex(const uint8_t *data, size_t size, bool big_endian = true);

uint16_t CalculateCRC16(const uint8_t *buffer, size_t size);

void WriteDataRequest(uint32_t addr, uint32_t mask, uint16_t id);
void WriteSysTimeRequest(uint32_t addr, uint16_t id);

void ReadResponse(uint32_t addr, uint8_t *payload, size_t size, uint16_t id);

uint64_t ReadDataRegister(PRegister reg);
uint64_t ReadSysTimeRegister(PRegister reg);

uint16_t RequestID;
};

typedef std::shared_ptr<TPulsarDevice> PPulsarDevice;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Open()
SkipNoise()
>> 00 10 70 80 01 0E 04 00 00 00 00 00 7C A7
<< 00 10 70 80 01 0E 5A B3 C5 41 00 00 18 DB
Close()
48 changes: 48 additions & 0 deletions wb-mqtt-serial/test/pulsar_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "testlog.h"
#include "fake_serial_port.h"
#include "pulsar_device.h"

namespace {
PSlaveEntry HeatMeter = TSlaveEntry::Intern("pulsar", 107080);

PRegister Heat_TempIn = TRegister::Intern(HeatMeter, 0, 2, Float);
PRegister Heat_TempOut = TRegister::Intern(HeatMeter, 0, 3, Float);
// TODO: time register
};

class TPulsarDeviceTest: public TSerialDeviceTest
{
protected:
void SetUp();
PPulsarDevice Dev;
};

void TPulsarDeviceTest::SetUp()
{
TSerialDeviceTest::SetUp();

// Create device with fixed Slave ID
Dev = std::make_shared<TPulsarDevice>(
std::make_shared<TDeviceConfig>("pulsar-heat", 102030, "pulsar"),
SerialPort);
SerialPort->Open();
}

TEST_F(TPulsarDeviceTest, PulsarHeatMeterFloatQuery)
{
// >> 00 10 70 80 01 0e 04 00 00 00 00 00 7C A7
// << 00 10 70 80 01 0E 5A B3 C5 41 00 00 18 DB
// temperature == 24.71257

SerialPort->Expect(
{
0x00, 0x10, 0x70, 0x80, 0x01, 0x0e, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xa7
},
{
0x00, 0x10, 0x70, 0x80, 0x01, 0x0e, 0x5a, 0xb3, 0xc5, 0x41, 0x00, 0x00, 0x18, 0xdb
});

ASSERT_EQ(0x41C5B35A, Dev->ReadRegister(Heat_TempIn));

SerialPort->Close();
}
Loading

0 comments on commit 906a1c8

Please sign in to comment.