From d254ce5066248e15330d541f850be189d22e55b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20=C3=96hrstr=C3=B6m?= Date: Sat, 19 Feb 2022 08:13:58 +0100 Subject: [PATCH] Add foundation for iu880b dongle to receive lora. --- Makefile | 1 + src/admin.cc | 9 +- src/bus.cc | 6 + src/lora_iu880b.cc | 572 ++++++++++++++++++++++++++++++ src/lora_iu880b.h | 101 ++++++ src/main.cc | 8 + src/manufacturer_specificities.cc | 4 +- src/testinternals.cc | 66 ++++ src/util.cc | 77 ++++ src/util.h | 4 + src/wmbus.cc | 10 + src/wmbus.h | 6 + src/wmbus_im871a.cc | 6 +- 13 files changed, 865 insertions(+), 5 deletions(-) create mode 100644 src/lora_iu880b.cc create mode 100644 src/lora_iu880b.h diff --git a/Makefile b/Makefile index 38ca4bd..3b32993 100644 --- a/Makefile +++ b/Makefile @@ -134,6 +134,7 @@ PROG_OBJS:=\ $(BUILD)/wmbus_rawtty.o \ $(BUILD)/wmbus_rc1180.o \ $(BUILD)/wmbus_utils.o \ + $(BUILD)/lora_iu880b.o \ # If you run: "make DRIVER=minomess" then only driver_minomess.cc will be compiled into wmbusmeters. # The old style drivers meter_xyz.cc must always be compiled in, but eventually they will be gone. diff --git a/src/admin.cc b/src/admin.cc index 31e6914..bd81796 100644 --- a/src/admin.cc +++ b/src/admin.cc @@ -57,7 +57,8 @@ LIST_OF_MAIN_MENU X(AMB8465, "amb8465") \ X(CUL, "cul") \ X(IM871A, "im871a") \ - X(RC1180, "rc1180") + X(RC1180, "rc1180") \ + X(IU880B, "iu880b") enum class ReceiversType { #define X(name,description) name, @@ -245,6 +246,9 @@ void detectWMBUSReceiver() case ReceiversType::IM871A: probeFor("im871a/im170a", detectIM871AIM170A); break; + case ReceiversType::IU880B: + probeFor("iu880b", detectIU880B); + break; case ReceiversType::RC1180: probeFor("rc1180", detectRC1180); break; @@ -292,6 +296,9 @@ void resetWMBUSReceiver() case ReceiversType::RC1180: notImplementedYet("Resetting RC1180"); break; + case ReceiversType::IU880B: + notImplementedYet("Resetting IU880B"); + break; } } diff --git a/src/bus.cc b/src/bus.cc index a10e025..f33f9c0 100644 --- a/src/bus.cc +++ b/src/bus.cc @@ -235,6 +235,12 @@ shared_ptr BusManager::createWmbusObject(Detected *detected, Configuratio wmbus = openRC1180(*detected, serial_manager_, serial_override); break; } + case DEVICE_IU880B: + { + verbose("(iu880b) on %s\n", detected->found_file.c_str()); + wmbus = openIU880B(*detected, serial_manager_, serial_override); + break; + } case DEVICE_UNKNOWN: warning("(main) internal error! cannot create an unknown device! exiting!\n"); if (config->daemon) { diff --git a/src/lora_iu880b.cc b/src/lora_iu880b.cc new file mode 100644 index 0000000..3cc7281 --- /dev/null +++ b/src/lora_iu880b.cc @@ -0,0 +1,572 @@ +/* + Copyright (C) 2022 Fredrik Öhrström (gpl-3.0-or-later) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include"wmbus.h" +#include"wmbus_common_implementation.h" +#include"wmbus_utils.h" +#include"lora_iu880b.h" +#include"serial.h" +#include"threads.h" + +#include +#include +#include +#include +#include +#include + +using namespace std; + +static void buildRequest(int endpoint_id, int msg_id, vector& body, vector& out); + +struct IU880BDeviceInfo +{ + // 0x90 = iM880A (obsolete) 0x92 = iM880A-L (128k) 0x93 = iU880A (128k) 0x98 = iM880B 0x99 = iU880B 0xA0 = iM881A 0xA1 = iU881A + uchar module_type {}; + uint16_t device_address {}; + uchar group_address {}; + string uid; + + string str() + { + string s; + if (module_type == 0x90) s += "im880a "; + else if (module_type == 0x92) s += "im880al "; + else if (module_type == 0x93) s += "iu880a "; + else if (module_type == 0x98) s += "im880b "; + else if (module_type == 0x99) s += "iu880b "; + else if (module_type == 0xa0) s += "im881a "; + else if (module_type == 0xa1) s += "iu881a "; + else s += "unknown_type("+to_string(module_type)+") "; + + s += tostrprintf("address %04x/%02x uid %s", device_address, group_address, uid.c_str()); + + return s; + } + + bool decode(vector &bytes) + { + if (bytes.size() < 9) return false; + int i = 0; + module_type = bytes[i++]; + device_address = bytes[i] | bytes[i+1]; + i+=2; + group_address = bytes[i++]; + i++; // skip reserved + uid = tostrprintf("%02x%02x%02x%02x", bytes[i+3], bytes[i+2], bytes[i+1], bytes[i]); + i+=4; + return true; + } +}; + +struct IU880BConfig +{ + // 0x90 = iM880A (obsolete) 0x92 = iM880A-L (128k) 0x93 = iU880A (128k) 0x98 = iM880B 0x99 = iU880B 0xA0 = iM881A 0xA1 = iU881A + uchar module_type {}; + uint16_t device_address {}; + uchar group_address {}; + string uid; + + string str() + { + string s; + if (module_type == 0x90) s += "im880a "; + else if (module_type == 0x92) s += "im880al "; + else if (module_type == 0x93) s += "iu880a "; + else if (module_type == 0x98) s += "im880b "; + else if (module_type == 0x99) s += "iu880b "; + else if (module_type == 0xa0) s += "im881a "; + else if (module_type == 0xa1) s += "iu881a "; + else s += "unknown_type("+to_string(module_type)+") "; + + s += tostrprintf("address %04x/%02x uid %s", device_address, group_address, uid.c_str()); + + return s; + } + + bool decode(vector &bytes) + { + if (bytes.size() < 9) return false; + int i = 0; + module_type = bytes[i++]; + device_address = bytes[i] | bytes[i+1]; + i+=2; + group_address = bytes[i++]; + i++; // skip reserved + uid = tostrprintf("%02x%02x%02x%02x", bytes[i+3], bytes[i+2], bytes[i+1], bytes[i]); + i+=4; + return true; + } +}; + +struct LoRaIU880B : public virtual WMBusCommonImplementation +{ + bool ping(); + string getDeviceId(); + string getDeviceUniqueId(); + uchar getFirmwareVersion(); + LinkModeSet getLinkModes(); + void deviceReset(); + void deviceSetLinkModes(LinkModeSet lms); + LinkModeSet supportedLinkModes() + { + return LORA_bit; + } + int numConcurrentLinkModes() { + return 1; + } + bool canSetLinkModes(LinkModeSet lms) + { + if (lms.empty()) return false; + if (!supportedLinkModes().supports(lms)) return false; + // Otherwise its a single link mode. + return 1 == countSetBits(lms.asBits()); + } + bool sendTelegram(ContentStartsWith starts_with, vector &content); + void processSerialData(); + void simulate() { } + + LoRaIU880B(string alias, shared_ptr serial, shared_ptr manager); + ~LoRaIU880B() { + } + + static FrameStatus checkIU880BFrame(vector &data, + vector &out, + size_t *frame_length, + int *endpoint_id_out, + int *msg_id_out, + int *status_out, + int *rssi_dbm); + +private: + + vector read_buffer_; + vector request_; + vector response_; + + bool getDeviceInfo(); + bool loaded_device_info_ {}; + IU880BDeviceInfo device_info_; + + friend AccessCheck detectIU880B(Detected *detected, shared_ptr manager); + void handleDevMgmt(int msgid, vector &payload); + void handleRadioLink(int msgid, vector &payload, int rssi_dbm); + void handleRadioLinkTest(int msgid, vector &payload); + void handleHWTest(int msgid, vector &payload); + +}; + +shared_ptr openIU880B(Detected detected, shared_ptr manager, shared_ptr serial_override) +{ + string bus_alias = detected.specified_device.bus_alias; + string device_file = detected.found_file; + assert(device_file != ""); + if (serial_override) + { + LoRaIU880B *imp = new LoRaIU880B(bus_alias, serial_override, manager); + imp->markAsNoLongerSerial(); + return shared_ptr(imp); + } + + auto serial = manager->createSerialDeviceTTY(device_file.c_str(), 115200, PARITY::NONE, "iu880b"); + LoRaIU880B *imp = new LoRaIU880B(bus_alias, serial, manager); + return shared_ptr(imp); +} + +LoRaIU880B::LoRaIU880B(string alias, shared_ptr serial, shared_ptr manager) : + WMBusCommonImplementation(alias, WMBusDeviceType::DEVICE_IU880B, manager, serial, true) +{ + reset(); +} + +bool LoRaIU880B::ping() +{ + /* + if (serial()->readonly()) return true; // Feeding from stdin or file. + + LOCK_WMBUS_EXECUTING_COMMAND(ping); + + request_.resize(4); + request_[0] = IU880B_SERIAL_SOF; + request_[1] = DEVMGMT_ID; + request_[2] = DEVMGMT_MSG_PING_REQ; + request_[3] = 0; + + verbose("(iu880b) ping\n"); + bool sent = serial()->send(request_); + + if (sent) return waitForResponse(DEVMGMT_MSG_PING_RSP); + */ + return true; +} + +string LoRaIU880B::getDeviceId() +{ + if (serial()->readonly()) return "?"; // Feeding from stdin or file. + if (cached_device_id_ != "") return cached_device_id_; + + bool ok = getDeviceInfo(); + if (!ok) return "ER1R"; + + cached_device_id_ = device_info_.uid; + + verbose("(iu880b) got device id %s\n", cached_device_id_.c_str()); + + return cached_device_id_; +} + +string LoRaIU880B::getDeviceUniqueId() +{ + return getDeviceId(); +} + +uchar LoRaIU880B::getFirmwareVersion() +{ + return 0; +} + +LinkModeSet LoRaIU880B::getLinkModes() +{ + return LORA_bit; +} + +void LoRaIU880B::deviceReset() +{ + // No device specific settings needed right now. + // The common code in wmbus.cc reset() + // will open the serial device and potentially + // set the link modes properly. +} + +void LoRaIU880B::deviceSetLinkModes(LinkModeSet lms) +{ + if (serial()->readonly()) return; // Feeding from stdin or file. + + if (!canSetLinkModes(lms)) + { + string modes = lms.hr(); + error("(iu880b) setting link mode(s) %s is not supported for iu880b\n", modes.c_str()); + } + + LOCK_WMBUS_EXECUTING_COMMAND(set_link_modes); + + vector init; + init.resize(30); + for (int i=0; i<30; ++i) + { + init[i] = 0xc0; + } + // Wake-up the dongle. + serial()->send(init); + + vector body = { 0x02 }; // 2 means listen to all traffic + request_.clear(); + buildRequest(DEVMGMT_ID, DEVMGMT_MSG_SET_RADIO_MODE_REQ, body, request_); + + verbose("(iu880b) set link mode lora listen to all\n"); + + bool sent = serial()->send(request_); + if (!sent) return; // tty overridden with stdin/file + + bool ok = waitForResponse(DEVMGMT_MSG_SET_RADIO_MODE_RSP); + if (!ok) return; // timeout +} + +FrameStatus LoRaIU880B::checkIU880BFrame(vector &data, + vector &out, + size_t *frame_length_out, + int *endpoint_id_out, + int *msg_id_out, + int *status_byte_out, + int *rssi_dbm) +{ + vector msg; + + removeSlipFraming(data, frame_length_out, msg); + + if (msg.size() < 5) return PartialFrame; + + *endpoint_id_out = msg[0]; + *msg_id_out = msg[1]; + *status_byte_out = msg[2]; + + uint16_t crc = ~crc16_CCITT(&msg[0], msg.size()-2); + uchar crc_lo = crc & 0xff; + uchar crc_hi = crc >> 8; + + if (msg[msg.size()-2] != crc_lo || msg[msg.size()-1] != crc_hi) + { + debug("(iu880b) bad crc got %02x%02x expected %02x%02x\n", + msg[msg.size()-1], msg[msg.size()-2], crc_hi, crc_lo); + + return ErrorInFrame; + } + + int payload_offset = 3; + int payload_len = msg.size()-2; + out.clear(); + out.insert(out.end(), msg.begin()+payload_offset, msg.begin()+payload_len); + + return FullFrame; +} + +void LoRaIU880B::processSerialData() +{ + vector data; + + // Receive and accumulated serial data until a full frame has been received. + serial()->receive(&data); + + read_buffer_.insert(read_buffer_.end(), data.begin(), data.end()); + + size_t frame_length; + int endpoint_id; + int msg_id; + int status_byte; + int rssi_dbm = 0; + + vector payload; + + for (;;) + { + FrameStatus status = checkIU880BFrame(read_buffer_, + payload, + &frame_length, + &endpoint_id, + &msg_id, + &status_byte, + &rssi_dbm); + + if (status == PartialFrame) + { + if (read_buffer_.size() > 0) + { + debugPayload("(iu880b) partial frame, expecting more.", read_buffer_); + } + break; + } + if (status == ErrorInFrame) + { + debugPayload("(iu880b) bad frame, clearing.", read_buffer_); + read_buffer_.clear(); + break; + } + if (status == FullFrame) + { + read_buffer_.erase(read_buffer_.begin(), read_buffer_.begin()+frame_length); + + // We now have a proper message in payload. Let us trigger actions based on it. + // It can be wmbus receiver-dongle messages or wmbus remote meter messages received over the radio. + switch (endpoint_id) { + case DEVMGMT_ID: handleDevMgmt(msg_id, payload); break; + case RADIOLINK_ID: handleRadioLink(msg_id, payload, rssi_dbm); break; + case HWTEST_ID: handleHWTest(msg_id, payload); break; + } + } + } +} + +void LoRaIU880B::handleDevMgmt(int msgid, vector &payload) +{ + switch (msgid) { + case DEVMGMT_MSG_PING_RSP: + verbose("(iu880b) pong\n"); + notifyResponseIsHere(DEVMGMT_MSG_PING_RSP); + break; + case DEVMGMT_MSG_GET_DEVICE_INFO_RSP: + verbose("(iu880b) device info completed\n"); + response_.clear(); + response_.insert(response_.end(), payload.begin(), payload.end()); + notifyResponseIsHere(DEVMGMT_MSG_GET_DEVICE_INFO_RSP); + break; + case DEVMGMT_MSG_SET_RADIO_MODE_RSP: + verbose("(iu880b) device set radio mode completed\n"); + response_.clear(); + response_.insert(response_.end(), payload.begin(), payload.end()); + notifyResponseIsHere(DEVMGMT_MSG_SET_RADIO_MODE_RSP); + break; + case DEVMGMT_MSG_GET_FW_INFO_RSP: + verbose("(iu880b) device get firmware\n"); + response_.clear(); + response_.insert(response_.end(), payload.begin(), payload.end()); + notifyResponseIsHere(DEVMGMT_MSG_GET_FW_INFO_RSP); + break; + + default: + verbose("(iu880b) Unhandled device management message %d\n", msgid); + } +} + +void LoRaIU880B::handleRadioLink(int msgid, vector &frame, int rssi_dbm) +{ +} + +void LoRaIU880B::handleRadioLinkTest(int msgid, vector &payload) +{ +} + +void LoRaIU880B::handleHWTest(int msgid, vector &payload) +{ +} + + +bool LoRaIU880B::getDeviceInfo() +{ + if (loaded_device_info_) return true; + + LOCK_WMBUS_EXECUTING_COMMAND(get_device_info); + + vector empty_body; + request_.clear(); + buildRequest(DEVMGMT_ID, DEVMGMT_MSG_GET_DEVICE_INFO_REQ, empty_body, request_); + + verbose("(iu880b) get device info\n"); + + vector init; + init.resize(30); + for (int i=0; i<30; ++i) + { + init[i] = 0xc0; + } + // Wake-up the dongle. + serial()->send(init); + + bool sent = serial()->send(request_); + if (!sent) return false; // tty overridden with stdin/file + + bool ok = waitForResponse(DEVMGMT_MSG_GET_DEVICE_INFO_RSP); + if (!ok) return false; // timeout + + // Now device info response is in response_ vector. + device_info_.decode(response_); + + loaded_device_info_ = true; + verbose("(iu880b) device info: %s\n", device_info_.str().c_str()); + + request_.clear(); + buildRequest(DEVMGMT_ID, DEVMGMT_MSG_GET_FW_INFO_REQ, empty_body, request_); + + serial()->send(init); + sent = serial()->send(request_); + if (!sent) return false; // tty overridden with stdin/file + + ok = waitForResponse(DEVMGMT_MSG_GET_FW_INFO_RSP); + if (!ok) return false; // timeout + + verbose("(iu880b) get firmware\n"); + + return true; +} + +bool LoRaIU880B::sendTelegram(ContentStartsWith starts_with, vector &content) +{ + return false; +} + +static void buildRequest(int endpoint_id, int msg_id, vector& body, vector& out) +{ + vector request; + request.push_back(endpoint_id); + request.push_back(msg_id); + request.insert(request.end(), body.begin(), body.end()); + + uint16_t crc = ~crc16_CCITT(&request[0], request.size()); + request.push_back(crc & 0xff); + request.push_back(crc >> 8); + + addSlipFraming(request, out); +} + + +AccessCheck detectIU880B(Detected *detected, shared_ptr manager) +{ + assert(detected->found_file != ""); + + // Talk to the device and expect a very specific answer. + auto serial = manager->createSerialDeviceTTY(detected->found_file.c_str(), 115200, PARITY::NONE, "detect iu880b"); + serial->disableCallbacks(); + bool ok = serial->open(false); + if (!ok) + { + verbose("(iu880b) could not open tty %s for detection\n", detected->found_file.c_str()); + return AccessCheck::NoSuchDevice; + } + + vector response; + // First clear out any data in the queue. + serial->receive(&response); + response.clear(); + + vector init; + init.resize(30); + for (int i=0; i<30; ++i) + { + init[i] = 0xc0; + } + // Wake-up the dongle. + serial->send(init); + + vector body; + vector request; + buildRequest(DEVMGMT_ID, DEVMGMT_MSG_GET_DEVICE_INFO_REQ, body, request); + serial->send(request); + + // Wait for 100ms so that the USB stick have time to prepare a response. + usleep(100*1000); + serial->receive(&response); + + int endpoint_id = 0; + int msg_id = 0; + int status_byte = 0; + size_t frame_length = 0; + int rssi_dbm = 0; + vector payload; + + FrameStatus status = LoRaIU880B::checkIU880BFrame(response, + payload, + &frame_length, + &endpoint_id, + &msg_id, + &status_byte, + &rssi_dbm); + + if (status != FullFrame || + endpoint_id != DEVMGMT_ID || + msg_id != DEVMGMT_MSG_GET_DEVICE_INFO_RSP) + { + verbose("(iu880b) are you there? no.\n"); + serial->close(); + return AccessCheck::NoProperResponse; + } + + serial->close(); + + debugPayload("(iu880b) device info response", payload); + + debug("(iu880b) endpoint %02x msg %02x status %02x\n", endpoint_id, msg_id, status_byte); + + IU880BDeviceInfo di; + di.decode(payload); + + debug("(iu880b) info: %s\n", di.str().c_str()); + + detected->setAsFound(di.uid, WMBusDeviceType::DEVICE_IU880B, 115200, false, detected->specified_device.linkmodes); + + verbose("(iu880b) are you there? yes %s\n", di.uid.c_str()); + + return AccessCheck::AccessOK; +} diff --git a/src/lora_iu880b.h b/src/lora_iu880b.h new file mode 100644 index 0000000..48c7360 --- /dev/null +++ b/src/lora_iu880b.h @@ -0,0 +1,101 @@ +// Copyright (C) 2022 Fredrik Öhrström (CC0-1.0) +// Definitions from WiMOD_LR_Base_HCI_Spec_V1_10.pdf + +#define DEVMGMT_ID 0x01 +#define RTL_ID 0x02 +#define RADIOLINK_ID 0x03 +#define REMOTE_CTRL_ID 0x04 +#define HWTEST_ID 0xA1 + +#define DEVMGMT_STATUS_OK 0x00 +#define DEVMGMT_STATUS_ERROR 0x01 +#define DEVMGMT_STATUS_CMD_NOT_SUPPORTED 0x02 +#define DEVMGMT_STATUS_WRONG_PARAMETER 0x03 + +#define DEVMGMT_MSG_PING_REQ 0x01 +#define DEVMGMT_MSG_PING_RSP 0x02 +#define DEVMGMT_MSG_GET_DEVICE_INFO_REQ 0x03 +#define DEVMGMT_MSG_GET_DEVICE_INFO_RSP 0x04 +#define DEVMGMT_MSG_GET_FW_INFO_REQ 0x05 +#define DEVMGMT_MSG_GET_FW_INFO_RSP 0x06 +#define DEVMGMT_MSG_RESET_REQ 0x07 +#define DEVMGMT_MSG_RESET_RSP 0x08 +#define DEVMGMT_MSG_SET_OPMODE_REQ 0x09 +#define DEVMGMT_MSG_SET_OPMODE_RSP 0x0A +#define DEVMGMT_MSG_GET_OPMODE_REQ 0x0B +#define DEVMGMT_MSG_GET_OPMODE_RSP 0x0C +#define DEVMGMT_MSG_SET_RTC_REQ 0x0D +#define DEVMGMT_MSG_SET_RTC_RSP 0x0E +#define DEVMGMT_MSG_GET_RTC_REQ 0x0F +#define DEVMGMT_MSG_GET_RTC_RSP 0x10 +#define DEVMGMT_MSG_SET_RADIO_CONFIG_REQ 0x11 +#define DEVMGMT_MSG_SET_RADIO_CONFIG_RSP 0x12 +#define DEVMGMT_MSG_GET_RADIO_CONFIG_REQ 0x13 +#define DEVMGMT_MSG_GET_RADIO_CONFIG_RSP 0x14 +#define DEVMGMT_MSG_RESET_RADIO_CONFIG_REQ 0x15 + +#define DEVMGMT_MSG_RESET_RADIO_CONFIG_RSP 0x16 +#define DEVMGMT_MSG_GET_SYSTEM_STATUS_REQ 0x17 +#define DEVMGMT_MSG_GET_SYSTEM_STATUS_RSP 0x18 +#define DEVMGMT_MSG_SET_RADIO_MODE_REQ 0x19 +#define DEVMGMT_MSG_SET_RADIO_MODE_RSP 0x1A +// obsolete in V1.9 +#define DEVMGMT_MSG_ENTER_LPM_REQ 0x1B +// obsolete in V1.9 +#define DEVMGMT_MSG_ENTER_LPM_RSP 0x1C +// Firmware V1.6 +#define DEVMGMT_MSG_POWER_UP_IND 0x20 +// Firmware V1.10 +#define DEVMGMT_MSG_SET_AES_KEY_REQ 0x21 +#define DEVMGMT_MSG_SET_AES_KEY_RSP 0x22 +#define DEVMGMT_MSG_GET_AES_KEY_REQ 0x23 +#define DEVMGMT_MSG_GET_AES_KEY_RSP 0x24 + +#define RADIOLINK_MSG_SEND_U_DATA_REQ 0x01 +#define RADIOLINK_MSG_SEND_U_DATA_RSP 0x02 +#define RADIOLINK_MSG_U_DATA_RX_IND 0x04 +// Firmware V1.6 +#define RADIOLINK_MSG_U_DATA_TX_IND 0x06 +#define RADIOLINK_MSG_RAW_DATA_RX_IND 0x08 +#define RADIOLINK_MSG_SEND_C_DATA_REQ 0x09 +#define RADIOLINK_MSG_SEND_C_DATA_RSP 0x0A +#define RADIOLINK_MSG_C_DATA_RX_IND 0x0C +#define RADIOLINK_MSG_C_DATA_TX_IND 0x0E +#define RADIOLINK_MSG_ACK_RX_IND 0x10 +#define RADIOLINK_MSG_ACK_TIMEOUT_IND 0x12 +#define RADIOLINK_MSG_ACK_TX_IND 0x14 +#define RADIOLINK_MSG_SET_ACK_DATA_REQ 0x15 +#define RADIOLINK_MSG_SET_ACK_DATA_RSP 0x16 + +#define RADIOLINK_STATUS_OK 0x00 +#define RADIOLINK_STATUS_ERROR 0x01 +#define RADOLINK_STATUS_CMD_NOT_SUPPORTED 0x02 +#define RADIOLINK_STATUS_WRONG_PARAMETER 0x03 +#define RADIOLINK_STATUS_WRONG_RADIO_MODE 0x04 +// Firmware V1.0 +#define RADIOLINK_STATUS_MEDIA_BUSY 0x05 +#define RADIOLINK_STATUS_BUFFER_FULL 0x07 +#define RADIOLINK_STATUS_LENGTH_ERROR 0x08 + + +#define RLT_MSG_START_REQ 0x01 +#define RLT_MSG_START_RSP 0x02 +#define RLT_MSG_STOP_REQ 0x03 +#define RLT_MSG_STOP_RSP 0x04 +#define RLT_MSG_STATUS_IND 0x06 + +#define RLT_STATUS_OK 0x00 +#define RLT_STATUS_ERROR 0x01 +#define RLT_STATUS_CMD_NOT_SUPPORTED 0x02 +#define RLT_STATUS_WRONG_PARAMETER 0x03 +#define RLT_STATUS_WRONG_RADIO_MODE 0x04 + +#define HWTEST_MSG_RADIO_TEST_REQ 0x01 +#define HWTEST_MSG_RADIO_TEST_RSP 0x02 + +#define HWTEST_STATUS_OK 0x00 +#define HWTEST_STATUS_ERROR 0x01 +#define HWTEST_STATUS_CMD_NOT_SUPPORTED 0x02 +#define HWTEST_STATUS_WRONG_PARAMETER 0x03 + +#define REMOTE_CTRL_MSG_BUTTON_PRESSED_IND 0x02 diff --git a/src/main.cc b/src/main.cc index bebb887..e42a76e 100644 --- a/src/main.cc +++ b/src/main.cc @@ -489,6 +489,14 @@ bool start(Configuration *config) // to achive a nice shutdown. onExit(call(serial_manager_.get(),stop)); + /* + Detected d; + d.specified_device.file = "/dev/ttyUSB0"; + d.found_file = "/dev/ttyUSB0"; + d.specified_device.type = WMBusDeviceType::DEVICE_IU880B; + + detectIU880B(&d, serial_manager_); +*/ // Create the printer object that knows how to translate // telegrams into json, fields that are written into log files // or sent to shell invocations. diff --git a/src/manufacturer_specificities.cc b/src/manufacturer_specificities.cc index 654f058..4b56a87 100644 --- a/src/manufacturer_specificities.cc +++ b/src/manufacturer_specificities.cc @@ -247,9 +247,11 @@ void addDefaultManufacturerKeyIfAny(const vector &frame, TPLSecurityMode && tpl_sec_mode == TPLSecurityMode::AES_CBC_IV && detectDiehlFrameInterpretation(frame) == DiehlFrameInterpretation::OMS) { - vector half; hex2bin(PRIOS_DEFAULT_KEY2, &half); + vector half; + hex2bin(PRIOS_DEFAULT_KEY2, &half); meter_keys->confidentiality_key = vector(half.begin(), half.end()); meter_keys->confidentiality_key.insert(meter_keys->confidentiality_key.end(), half.begin(), half.end()); + debug("(mfct) added default key\n"); } } diff --git a/src/testinternals.cc b/src/testinternals.cc index 7e9d7e4..14d176c 100644 --- a/src/testinternals.cc +++ b/src/testinternals.cc @@ -45,6 +45,7 @@ void test_aes(); void test_sbc(); void test_hex(); void test_translate(); +void test_slip(); int main(int argc, char **argv) { @@ -76,6 +77,7 @@ int main(int argc, char **argv) test_sbc(); test_hex(); test_translate(); + test_slip(); return 0; } @@ -1085,3 +1087,67 @@ void test_translate() printf("ERROR expected \"%s\" but got \"%s\"\n", e.c_str(), s.c_str()); } } + +void test_slip() +{ + vector from = { 1, 0xc0, 3, 4, 5, 0xdb }; + vector expected_to = { 0xc0, 1, 0xdb, 0xdc, 3, 4, 5, 0xdb, 0xdd, 0xc0 }; + vector to; + vector back; + + addSlipFraming(from, to); + + if (expected_to != to) + { + printf("ERROR slip 1\n"); + } + + size_t frame_length = 0; + removeSlipFraming(to, &frame_length, back); + + if (back != from) + { + printf("ERROR slip 2\n"); + } + + if (to.size() != frame_length) + { + printf("ERROR slip 3\n"); + } + + vector more = { 0xc0, 0xc0, 0xc0, 1, 2, 3, 4, 5, 6, 7, 8 }; + addSlipFraming(more, to); + + frame_length = 0; + removeSlipFraming(to, &frame_length, back); + + if (back != from) + { + printf("ERROR slip 4\n"); + } + + to.erase(to.begin(), to.begin()+frame_length); + removeSlipFraming(to, &frame_length, back); + + if (back != more) + { + printf("ERROR slip 5\n"); + } + + vector again = { 0xc0 }; + removeSlipFraming(again, &frame_length, back); + + if (frame_length != 0) + { + printf("ERROR slip 6\n"); + } + + vector againn = { 0xc0, 1, 2, 3, 4, 5 }; + removeSlipFraming(againn, &frame_length, back); + + if (frame_length != 0) + { + printf("ERROR slip 7\n"); + } + +} diff --git a/src/util.cc b/src/util.cc index 64a8ae2..36347d4 100644 --- a/src/util.cc +++ b/src/util.cc @@ -2084,3 +2084,80 @@ string reverseBinaryAsciiSafeToString(string v) reverse(bytes.begin(), bytes.end()); return safeString(bytes); } + +#define SLIP_END 0xc0 /* indicates end of packet */ +#define SLIP_ESC 0xdb /* indicates byte stuffing */ +#define SLIP_ESC_END 0xdc /* ESC ESC_END means END data byte */ +#define SLIP_ESC_ESC 0xdd /* ESC ESC_ESC means ESC data byte */ + +void addSlipFraming(vector& from, vector &to) +{ + to.push_back(SLIP_END); + for (uchar c : from) + { + if (c == SLIP_END) + { + to.push_back(SLIP_ESC); + to.push_back(SLIP_ESC_END); + } + else if (c == SLIP_ESC) + { + to.push_back(SLIP_ESC); + to.push_back(SLIP_ESC_ESC); + } + else + { + to.push_back(c); + } + } + to.push_back(SLIP_END); +} + +void removeSlipFraming(vector& from, size_t *frame_length, vector &to) +{ + *frame_length = 0; + to.clear(); + to.reserve(from.size()); + bool esc = false; + size_t i; + bool found_end = false; + + for (i = 0; i < from.size(); ++i) + { + uchar c = from[i]; + if (c == SLIP_END) + { + if (to.size() > 0) + { + found_end = true; + i++; + break; + } + } + else if (c == SLIP_ESC) + { + esc = true; + } + else if (esc) + { + if (c == SLIP_ESC_END) to.push_back(SLIP_END); + else if (c == SLIP_ESC_ESC) to.push_back(SLIP_ESC); + else to.push_back(c); // This is an error...... + } + else + { + to.push_back(c); + } + } + + if (found_end) + { + *frame_length = i; + } + else + { + *frame_length = 0; + to.clear(); + } + +} diff --git a/src/util.h b/src/util.h index 7c55a90..f4397cf 100644 --- a/src/util.h +++ b/src/util.h @@ -171,6 +171,10 @@ uint16_t crc16_EN13757(uchar *data, size_t len); uint16_t crc16_CCITT(uchar *data, uint16_t length); bool crc16_CCITT_check(uchar *data, uint16_t length); +void addSlipFraming(std::vector& from, std::vector &to); +// Frame length is set to zero if no frame was found. +void removeSlipFraming(std::vector& from, size_t *frame_length, std::vector &to); + // Eat characters from the vector v, iterating using i, until the end char c is found. // If end char == -1, then do not expect any end char, get all until eof. // If the end char is not found, return error. diff --git a/src/wmbus.cc b/src/wmbus.cc index 58ccdb8..f246cd1 100644 --- a/src/wmbus.cc +++ b/src/wmbus.cc @@ -5172,6 +5172,16 @@ Detected detectWMBusDeviceOnTTY(string tty, } } + // Talk iu880b with it... + // assumes this device is configured for 115200 bps, which seems to be the default. + if (type == WMBusDeviceType::DEVICE_AUTO || type == WMBusDeviceType::DEVICE_IU880B) + { + if (detectIU880B(&detected, handler) == AccessCheck::AccessOK) + { + return detected; + } + } + // We could not auto-detect either. default is DEVICE_UNKNOWN. return detected; } diff --git a/src/wmbus.h b/src/wmbus.h index 270eec9..009268c 100644 --- a/src/wmbus.h +++ b/src/wmbus.h @@ -44,6 +44,7 @@ bool trimCRCsFrameFormatB(std::vector &payload); X(RC1180,rc1180,true,false,detectRC1180) \ X(RTL433,rtl433,false,true,detectRTL433) \ X(RTLWMBUS,rtlwmbus,false,true,detectRTLWMBUS) \ + X(IU880B,iu880b,true,false,detectIU880B) \ X(SIMULATION,simulation,false,false,detectSIMULATION) enum WMBusDeviceType { @@ -89,6 +90,7 @@ void setIgnoreDuplicateTelegrams(bool idt); X(N1e,n1e,--n1e,0x100) \ X(N1f,n1f,--n1f,0x200) \ X(MBUS,mbus,--mbus,0x400) \ + X(LORA,lora,--lora,0x800) \ X(UNKNOWN,unknown,----,0x0) enum class LinkMode { @@ -662,6 +664,9 @@ shared_ptr openIM871A(Detected detected, shared_ptr openIM170A(Detected detected, shared_ptr manager, shared_ptr serial_override); +shared_ptr openIU880B(Detected detected, + shared_ptr manager, + shared_ptr serial_override); shared_ptr openAMB8465(Detected detected, shared_ptr manager, shared_ptr serial_override); @@ -745,6 +750,7 @@ AccessCheck detectAMB8465(Detected *detected, shared_ptr handler); AccessCheck detectD1TC(Detected *detected, shared_ptr manager); AccessCheck detectIM871AIM170A(Detected *detected, shared_ptr handler); +AccessCheck detectIU880B(Detected *detected, shared_ptr handler); AccessCheck detectRAWTTY(Detected *detected, shared_ptr handler); AccessCheck detectMBUS(Detected *detected, shared_ptr handler); AccessCheck detectRC1180(Detected *detected, shared_ptr handler); diff --git a/src/wmbus_im871a.cc b/src/wmbus_im871a.cc index 1d9cdeb..ca829a3 100644 --- a/src/wmbus_im871a.cc +++ b/src/wmbus_im871a.cc @@ -37,7 +37,7 @@ using namespace std; #define FIRMWARE_14_C_AND_T 0x14 #define FIRMWARE_13_C_OR_T 0x13 -struct DeviceInfo +struct IM871ADeviceInfo { uchar module_type; // 0x33 = im871a 0x36 = im170a uchar device_mode; // 0 = other 1 = meter @@ -228,7 +228,7 @@ struct WMBusIM871aIM170A : public virtual WMBusCommonImplementation private: - DeviceInfo device_info_ {}; + IM871ADeviceInfo device_info_ {}; Config device_config_ {}; vector read_buffer_; @@ -1014,7 +1014,7 @@ AccessCheck detectIM871AIM170A(Detected *detected, shared_ptr