diff --git a/Makefile b/Makefile index 4bfbfdc..0639e4e 100644 --- a/Makefile +++ b/Makefile @@ -148,6 +148,7 @@ PROG_OBJS:=\ $(BUILD)/units.o \ $(BUILD)/wmbus.o \ $(BUILD)/wmbus_amb8465.o \ + $(BUILD)/wmbus_amb3665.o \ $(BUILD)/wmbus_im871a.o \ $(BUILD)/wmbus_cul.o \ $(BUILD)/wmbus_rtlwmbus.o \ diff --git a/src/admin.cc b/src/admin.cc index bd81796..8e3485a 100644 --- a/src/admin.cc +++ b/src/admin.cc @@ -55,6 +55,7 @@ LIST_OF_MAIN_MENU #define LIST_OF_WMBUS_RECEIVERS \ X(AMB8465, "amb8465") \ + X(AMB3665, "amb3665") \ X(CUL, "cul") \ X(IM871A, "im871a") \ X(RC1180, "rc1180") \ @@ -237,6 +238,9 @@ void detectWMBUSReceiver() int c = selectFromMenu("Select your wmbus radio device", receivers_menu); switch (static_cast(c)) { + case ReceiversType::AMB3665: + probeFor("amb3665", detectAMB3665); + break; case ReceiversType::AMB8465: probeFor("amb8465", detectAMB8465); break; @@ -260,6 +264,33 @@ void resetWMBUSReceiver() int c = selectFromMenu("Select your wmbus radio device", receivers_menu); switch (static_cast(c)) { + case ReceiversType::AMB3665: + { + vector devices = handler->listSerialTTYs(); + if (devices.size() == 0) + { + vector entries; + displayInformationAndWait("No serial ports!", entries); + return; + } + int c = selectFromMenu("Select device", devices); + string device = devices[c]; + int was_baud = 0; + AccessCheck ac = factoryResetAMB3665(device, handler, &was_baud); + if (ac == AccessCheck::AccessOK) + { + vector entries; + entries.push_back("amb3665 "+device+" using "+to_string(was_baud)); + displayInformationAndWait("Factory reset successful", entries); + } + else + { + vector entries; + entries.push_back(device); + displayInformationAndWait("No amb3665 response from", entries); + } + break; + } case ReceiversType::AMB8465: { vector devices = handler->listSerialTTYs(); diff --git a/src/bus.cc b/src/bus.cc index ade201a..ecb6c0d 100644 --- a/src/bus.cc +++ b/src/bus.cc @@ -205,6 +205,10 @@ shared_ptr BusManager::createWmbusObject(Detected *detected, Configuratio verbose("(amb8465) on %s\n", detected->found_file.c_str()); wmbus = openAMB8465(*detected, serial_manager_, serial_override); break; + case DEVICE_AMB3665: + verbose("(amb3665) on %s\n", detected->found_file.c_str()); + wmbus = openAMB3665(*detected, serial_manager_, serial_override); + break; case DEVICE_SIMULATION: verbose("(simulation) in %s\n", detected->found_file.c_str()); wmbus = openSimulator(*detected, serial_manager_, serial_override); diff --git a/src/testinternals.cc b/src/testinternals.cc index 1615234..d195c33 100644 --- a/src/testinternals.cc +++ b/src/testinternals.cc @@ -261,6 +261,7 @@ int test_linkmodes() auto serial2 = manager->createSerialDeviceSimulator(); auto serial3 = manager->createSerialDeviceSimulator(); auto serial4 = manager->createSerialDeviceSimulator(); + auto serial5 = manager->createSerialDeviceSimulator(); vector no_meter_shells, no_meter_jsons; Detected de; @@ -269,6 +270,7 @@ int test_linkmodes() shared_ptr wmbus_amb8465 = openAMB8465(de, manager, serial2); shared_ptr wmbus_rtlwmbus = openRTLWMBUS(de, "", false, manager, serial3); shared_ptr wmbus_rawtty = openRawTTY(de, manager, serial4); + shared_ptr wmbus_amb3665 = openAMB3665(de, manager, serial5); Configuration nometers_config; // Check that if no meters are supplied then you must set a link mode. diff --git a/src/wmbus.h b/src/wmbus.h index ef1fa26..a075048 100644 --- a/src/wmbus.h +++ b/src/wmbus.h @@ -39,6 +39,7 @@ bool trimCRCsFrameFormatB(std::vector &payload); X(MBUS,mbus,true,false,detectMBUS) \ X(AUTO,auto,false,false,detectAUTO) \ X(AMB8465,amb8465,true,false,detectAMB8465) \ + X(AMB3665,amb3665,true,false,detectAMB3665) \ X(CUL,cul,true,false,detectCUL) \ X(IM871A,im871a,true,false,detectIM871AIM170A) \ X(IM170A,im170a,true,false,detectSKIP) \ @@ -663,6 +664,9 @@ shared_ptr openIU880B(Detected detected, shared_ptr openAMB8465(Detected detected, shared_ptr manager, shared_ptr serial_override); +shared_ptr openAMB3665(Detected detected, + shared_ptr manager, + shared_ptr serial_override); shared_ptr openRawTTY(Detected detected, shared_ptr manager, shared_ptr serial_override); @@ -750,6 +754,7 @@ AccessCheck reDetectDevice(Detected *detected, shared_ptr handler); AccessCheck detectAMB8465(Detected *detected, shared_ptr handler); +AccessCheck detectAMB3665(Detected *detected, shared_ptr handler); AccessCheck detectCUL(Detected *detected, shared_ptr handler); AccessCheck detectD1TC(Detected *detected, shared_ptr manager); AccessCheck detectIM871AIM170A(Detected *detected, shared_ptr handler); @@ -761,9 +766,10 @@ AccessCheck detectRTL433(Detected *detected, shared_ptr handler); AccessCheck detectSKIP(Detected *detected, shared_ptr handler); -// Try to factory reset an AMB8465 by trying all possible serial speeds and +// Try to factory reset an AMB8465/AMB3665 by trying all possible serial speeds and // restore to factory settings. AccessCheck factoryResetAMB8465(string tty, shared_ptr handler, int *was_baud); +AccessCheck factoryResetAMB3665(string tty, shared_ptr handler, int *was_baud); Detected detectWMBusDeviceOnTTY(string tty, set probe_for, diff --git a/src/wmbus_amb3665.cc b/src/wmbus_amb3665.cc new file mode 100755 index 0000000..3164246 --- /dev/null +++ b/src/wmbus_amb3665.cc @@ -0,0 +1,833 @@ +/* + Copyright (C) 2018-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"wmbus_amb3665.h" +#include"serial.h" +#include"threads.h" + +#include +#include +#include +#include +#include +#include + +using namespace std; + +uchar xorChecksum3665(vector &msg, size_t offset, size_t len); + +struct ConfigAMB3665 +{ + uchar uart_ctl0 {}; + uchar uart_ctl1 {}; + uchar received_frames_as_cmd {}; + uchar c_field {}; + uint16_t mfct {}; + uint32_t id {}; + uchar version {}; + uchar media {}; + + uchar auto_rssi {}; + + string dongleId() + { + return tostrprintf("%08x", id); + } + + string str() + { + string ids = tostrprintf("id=%08x media=%02x version=%02x c_field=%02x auto_rssi=%02x", id, media, version, c_field, auto_rssi); + return ids; + } + + bool decodeNoFrame(vector &bytes, size_t o) + { + if (bytes.size() < o+69) return false; + + uart_ctl0 = bytes[0+o]; + uart_ctl1 = bytes[0+o]; + + received_frames_as_cmd = bytes[5+o]; + c_field = bytes[49+o]; + id = bytes[51+o]<<8|bytes[50+o]; + mfct = bytes[55+o]<<24|bytes[54+o]<<16|bytes[53+o]<<8|bytes[52+o]; + version = bytes[56+o]; + media = bytes[57+o]; + + auto_rssi = bytes[69+o]; + return true; + } + + bool decode(vector &bytes, size_t offset) + { + // The first 5 bytes are: + // 0xFF + // 0x8A + // serial, shared_ptr manager); + ~WMBusAmber3665() { + manager_->onDisappear(this->serial(), NULL); + } + +private: + vector read_buffer_; // Must be protected by LOCK_WMBUS_RECEIVING_BUFFER(where) + vector request_; + vector response_; + + LinkModeSet link_modes_ {}; + bool rssi_expected_ {}; + struct timeval timestamp_last_rx_ {}; + + ConfigAMB3665 device_config_; + + FrameStatus checkAMB3665Frame(vector &data, + size_t *frame_length, + int *msgid_out, + int *payload_len_out, + int *payload_offset, + int *rssi_dbm); + void handleMessage(int msgid, vector &frame, int rssi_dbm); +}; + +shared_ptr openAMB3665(Detected detected, shared_ptr manager, shared_ptr serial_override) +{ + string bus_alias = detected.specified_device.bus_alias; + string device = detected.found_file; + assert(device != ""); + + if (serial_override) + { + WMBusAmber3665 *imp = new WMBusAmber3665(bus_alias, serial_override, manager); + imp->markAsNoLongerSerial(); + return shared_ptr(imp); + } + + auto serial = manager->createSerialDeviceTTY(device.c_str(), 9600, PARITY::NONE, "amb3665"); + WMBusAmber3665 *imp = new WMBusAmber3665(bus_alias, serial, manager); + return shared_ptr(imp); +} + +WMBusAmber3665::WMBusAmber3665(string alias, shared_ptr serial, shared_ptr manager) : + WMBusCommonImplementation(alias, DEVICE_AMB3665, manager, serial, true) +{ + rssi_expected_ = true; + reset(); +} + +void WMBusAmber3665::deviceReset() +{ + timerclear(×tamp_last_rx_); +} + +uchar xorChecksum3665(vector &msg, size_t offset, size_t len) +{ + assert(msg.size() >= len+offset); + uchar c = 0; + for (size_t i=offset; ireadonly()) return true; // Feeding from stdin or file. + + return true; +} + +string WMBusAmber3665::getDeviceId() +{ + if (serial()->readonly()) { return "?"; } // Feeding from stdin or file. + if (cached_device_id_ != "") return cached_device_id_; + + bool ok = getConfiguration(); + if (!ok) return "ERR"; + + cached_device_id_ = device_config_.dongleId(); + + return cached_device_id_; +} + +string WMBusAmber3665::getDeviceUniqueId() +{ + if (serial()->readonly()) { return "?"; } // Feeding from stdin or file. + if (cached_device_unique_id_ != "") return cached_device_unique_id_; + + LOCK_WMBUS_EXECUTING_COMMAND(get_device_unique_id); + + request_.resize(4); + request_[0] = AMBER_SERIAL_SOF; + request_[1] = CMD_SERIALNO_REQ; + request_[2] = 0; // No payload + request_[3] = xorChecksum3665(request_, 0, 3); + + verbose("(amb3665) get device unique id\n"); + bool sent = serial()->send(request_); + if (!sent) return "?"; + + bool ok = waitForResponse(CMD_SERIALNO_REQ | 0x80); + if (!ok) return "?"; + + if (response_.size() < 5) return "ERR"; + + uint32_t idv = + response_[1] << 24 | + response_[2] << 16 | + response_[3] << 8 | + response_[4]; + + verbose("(amb3665) unique device id %08x\n", idv); + + cached_device_unique_id_ = tostrprintf("%08x", idv); + return cached_device_unique_id_; +} + +LinkModeSet WMBusAmber3665::getLinkModes() +{ + if (serial()->readonly()) { return Any_bit; } // Feeding from stdin or file. + + // It is not possible to read the volatile mode set using setLinkModeSet below. + // (It is possible to read the non-volatile settings, but this software + // does not change those.) So we remember the state for the device. + return link_modes_; +} + +bool WMBusAmber3665::getConfiguration() +{ + if (serial()->readonly()) { return true; } // Feeding from stdin or file. + + LOCK_WMBUS_EXECUTING_COMMAND(getConfiguration); + + request_.resize(6); + request_[0] = AMBER_SERIAL_SOF; + request_[1] = CMD_GET_REQ; + request_[2] = 0x02; + request_[3] = 0x00; + request_[4] = 0x80; + request_[5] = xorChecksum3665(request_, 0, 5); + + assert(request_[5] == 0x77); + + verbose("(amb3665) get config\n"); + bool sent = serial()->send(request_); + if (!sent) return false; + + bool ok = waitForResponse(CMD_GET_REQ | 0x80); + if (!ok) return false; + + return device_config_.decodeNoFrame(response_, 3); +} + +bool WMBusAmber3665::deviceSetLinkModes(LinkModeSet lms) +{ + bool rc = false; + + if (serial()->readonly()) return true; // Feeding from stdin or file. + + if (!canSetLinkModes(lms)) + { + string modes = lms.hr(); + error("(amb3665) setting link mode(s) %s is not supported for amb3665\n", modes.c_str()); + } + + { + // Empty the read buffer we do not want any partial data lying around + // because we expect a response to arrive. + LOCK_WMBUS_RECEIVING_BUFFER(deviceSetLinkMode_ClearBuffer); + read_buffer_.clear(); + } + + LOCK_WMBUS_EXECUTING_COMMAND(devicesSetLinkModes); + + request_.resize(8); + request_[0] = AMBER_SERIAL_SOF; + request_[1] = CMD_SET_MODE_REQ; + request_[2] = 1; // Len + if (lms.has(LinkMode::N1a)) + { + // Listening to only N2a (TX and RX). + request_[3] = 0x02; + } + else if (lms.has(LinkMode::N1b)) + { + // Listening to only N2b (TX and RX). + request_[3] = 0x04; + } + else if (lms.has(LinkMode::N1c)) + { + // Listening to only N2c (TX and RX). + request_[3] = 0x06; + } + else if (lms.has(LinkMode::N1d)) + { + // Listening only to N2d (TX and RX). + request_[3] = 0x08; + } + else if (lms.has(LinkMode::N1e)) + { + // Listening only to N2e (TX and RX). + request_[3] = 0x0A; + } + else if (lms.has(LinkMode::N1f)) + { + // Listening only to N2f (TX and RX). + request_[3] = 0x0C; + } + request_[4] = xorChecksum3665(request_, 0, 4); + + verbose("(amb3665) set link mode %02x\n", request_[3]); + bool sent = serial()->send(request_); + + if (sent) + { + bool ok = waitForResponse(CMD_SET_MODE_REQ | 0x80); + if (ok) + { + rc = true; + } + else + { + warning("Warning! Did not get confirmation on set link mode for amb3665\n"); + rc = false; + } + } + + link_modes_ = lms; + return rc; +} + +FrameStatus WMBusAmber3665::checkAMB3665Frame(vector &data, + size_t *frame_length, + int *msgid_out, + int *payload_len_out, + int *payload_offset, + int *rssi_dbm) +{ + if (data.size() < 2) return PartialFrame; + debugPayload("(amb3665) checkAMB3665Frame", data); + int payload_len = 0; + if (data[0] == 0xff) + { + if (data.size() < 3) + { + debug("(amb3665) not enough bytes yet for command.\n"); + return PartialFrame; + } + + // Only response from CMD_DATA_IND has rssi + int rssi_len = (rssi_expected_ && data[1] == (0x80|CMD_DATA_IND)) ? 1 : 0; + + // A command response begins with 0xff + *msgid_out = data[1]; + payload_len = data[2]; + *payload_len_out = payload_len; + *payload_offset = 3; + // FF CMD len payload [RSSI] CS + *frame_length = 4 + payload_len + rssi_len; + if (data.size() < *frame_length) + { + debug("(amb3665) not enough bytes yet, partial command response %d %d.\n", data.size(), *frame_length); + return PartialFrame; + } + + debug("(amb3665) received full command frame\n"); + + uchar cs = xorChecksum3665(data, 0, *frame_length-1); + if (data[*frame_length-1] != cs) { + verbose("(amb3665) checksum error %02x (should %02x)\n", data[*frame_length-1], cs); + } + + if (rssi_len) + { + int rssi = (int)data[*frame_length-2]; + *rssi_dbm = (rssi >= 128) ? (rssi - 256) / 2 - 74 : rssi / 2 - 74; + verbose("(amb3665) rssi %d (%d dBm)\n", rssi, *rssi_dbm); + } + + return FullFrame; + } + // If it is not a 0xff we assume it is a message beginning with a length. + // There might be a different mode where the data is wrapped in 0xff. But for the moment + // this is what I see. + size_t offset = 0; + + // The data[0] must be at least 10 bytes. C MM AAAA V T Ci + // And C must be a valid wmbus c field. + while ((payload_len = data[offset]) < 10 || !isValidWMBusCField(data[offset+1])) + { + offset++; + if (offset + 2 >= data.size()) + { + // No sensible telegram in the buffer. Flush it! + // But not the last char, because the next char could be a valid c field. + verbose("(amb3665) no sensible telegram found, clearing buffer.\n"); + uchar last = data[data.size()-1]; + data.clear(); + data.insert(data.end(), &last, &last+1); // Re-insert the last byte. + return PartialFrame; + } + } + *msgid_out = 0; // 0 is used to signal + *payload_len_out = payload_len; + *payload_offset = offset+1; + *frame_length = payload_len+offset+1; + if (data.size() < *frame_length) + { + debug("(amb3665) not enough bytes yet, partial frame %d %d.\n", data.size(), *frame_length); + return PartialFrame; + } + + if (offset > 0) + { + verbose("(amb3665) out of sync, skipping %d bytes.\n", offset); + } + debug("(amb3665) received full frame\n"); + + if (rssi_expected_) + { + int rssi = data[*frame_length-1]; + *rssi_dbm = (rssi >= 128) ? (rssi - 256) / 2 - 74 : rssi / 2 - 74; + verbose("(amb3665) rssi %d (%d dBm)\n", rssi, *rssi_dbm); + } + + return FullFrame; +} + +void WMBusAmber3665::processSerialData() +{ + vector data; + + // Receive and accumulated serial data until a full frame has been received. + serial()->receive(&data); + + struct timeval timestamp; + + // Check long delay beetween rx chunks + gettimeofday(×tamp, NULL); + + LOCK_WMBUS_RECEIVING_BUFFER(processSerialData); + + if (read_buffer_.size() > 0 && timerisset(×tamp_last_rx_)) + { + struct timeval chunk_time; + timersub(×tamp, ×tamp_last_rx_, &chunk_time); + + if (chunk_time.tv_sec >= 2) + { + verbose("(amb3665) rx long delay (%lds), drop incomplete telegram\n", chunk_time.tv_sec); + read_buffer_.clear(); + protocolErrorDetected(); + } + else + { + unsigned long chunk_time_ms = 1000 * chunk_time.tv_sec + chunk_time.tv_usec / 1000; + debug("(amb3665) chunk time %ld msec\n", chunk_time_ms); + } + } + + read_buffer_.insert(read_buffer_.end(), data.begin(), data.end()); + + size_t frame_length; + int msgid; + int payload_len, payload_offset; + int rssi_dbm; + + for (;;) + { + FrameStatus status = checkAMB3665Frame(read_buffer_, &frame_length, &msgid, &payload_len, &payload_offset, &rssi_dbm); + + if (status == PartialFrame) + { + if (read_buffer_.size() > 0) { + // Save timestamp of this chunk + timestamp_last_rx_ = timestamp; + } + else + { + // Clean and empty + timerclear(×tamp_last_rx_); + } + break; + } + if (status == ErrorInFrame) + { + verbose("(amb3665) protocol error in message received!\n"); + string msg = bin2hex(read_buffer_); + debug("(amb3665) protocol error \"%s\"\n", msg.c_str()); + read_buffer_.clear(); + protocolErrorDetected(); + break; + } + if (status == FullFrame) + { + vector payload; + if (payload_len > 0) + { + uchar l = payload_len; + payload.insert(payload.end(), &l, &l+1); // Re-insert the len byte. + payload.insert(payload.end(), read_buffer_.begin()+payload_offset, read_buffer_.begin()+payload_offset+payload_len); + } + + read_buffer_.erase(read_buffer_.begin(), read_buffer_.begin()+frame_length); + + handleMessage(msgid, payload, rssi_dbm); + } + } +} + +void WMBusAmber3665::handleMessage(int msgid, vector &frame, int rssi_dbm) +{ + switch (msgid) { + case (0): + { + AboutTelegram about("amb3665["+cached_device_id_+"]", rssi_dbm, FrameType::WMBUS); + handleTelegram(about, frame); + break; + } + case (0x80|CMD_SET_MODE_REQ): + { + verbose("(amb3665) set link mode completed\n"); + response_.clear(); + response_.insert(response_.end(), frame.begin(), frame.end()); + debugPayload("(amb3665) set link mode response", response_); + notifyResponseIsHere(0x80|CMD_SET_MODE_REQ); + break; + } + case (0x80|CMD_GET_REQ): + { + verbose("(amb3665) get config completed\n"); + response_.clear(); + response_.insert(response_.end(), frame.begin(), frame.end()); + debugPayload("(amb3665) get config response", response_); + notifyResponseIsHere(0x80|CMD_GET_REQ); + break; + } + case (0x80|CMD_SERIALNO_REQ): + { + verbose("(amb3665) get device id completed\n"); + response_.clear(); + response_.insert(response_.end(), frame.begin(), frame.end()); + debugPayload("(amb3665) get device id response", response_); + notifyResponseIsHere(0x80|CMD_SERIALNO_REQ); + break; + } + default: + verbose("(amb3665) unhandled device message %d\n", msgid); + response_.clear(); + response_.insert(response_.end(), frame.begin(), frame.end()); + debugPayload("(amb3665) unknown response", response_); + } +} + +AccessCheck detectAMB3665(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(), 9600, PARITY::NONE, "detect amb3665"); + serial->disableCallbacks(); + bool ok = serial->open(false); + if (!ok) + { + verbose("(amb3665) could not open tty %s for detection\n", detected->found_file.c_str()); + return AccessCheck::NoSuchDevice; + } + + vector response; + int count = 1; + // First clear out any data in the queue, this might require multiple reads. + for (;;) + { + size_t n = serial->receive(&response); + count++; + if (n == 0) break; + if (count > 10) + { + break; + } + usleep(1000*100); + continue; + } + + if (response.size() > 0) + { + if (count <= 10) + { + debug("(amb3665) cleared %zu bytes from serial buffer\n", response.size()); + } + else + { + debug("(amb3665) way too much data received %zu when trying to detect! cannot clear serial buffer!\n", + response.size()); + } + + response.clear(); + } + + // Query all of the non-volatile parameter memory. + vector request; + request.resize(6); + request[0] = AMBER_SERIAL_SOF; + request[1] = CMD_GET_REQ; + request[2] = 0x02; + request[3] = 0x00; // Start at byte 0 + request[4] = 0x80; // End at byte 127 + request[5] = xorChecksum3665(request, 0, 5); + + assert(request[5] == 0x77); + + bool sent = false; + count = 0; + do + { + debug("(amb3665) sending %zu bytes attempt %d\n", request.size(), count); + sent = serial->send(request); + debug("(amb3665) sent %zu bytes %s\n", request.size(), sent?"OK":"Failed"); + if (!sent) + { + // We failed to send! Why? We have successfully opened the tty.... + // Perhaps the dongle needs to wake up. Lets try again in 100 ms. + usleep(1000*100); + count ++; + if (count >= 4) + { + // Tried and failed 3 times. + debug("(amb3665) failed to sent query! Giving up!\n"); + verbose("(amb3665) are you there? no, nothing is there.\n"); + serial->close(); + return AccessCheck::NoProperResponse; + } + } + } while (sent == false && count < 4); + + // Wait for 100ms so that the USB stick have time to prepare a response. + usleep(1000*100); + + ConfigAMB3665 config; + vector data; + count = 1; + for (;;) + { + if (count > 3) + { + verbose("(amb3665) are you there? no.\n"); + serial->close(); + return AccessCheck::NoProperResponse; + } + debug("(amb3665) reading response... %d\n", count); + + size_t n = serial->receive(&data); + count++; + if (n == 0) + { + usleep(1000*100); + continue; + } + response.insert(response.end(), data.begin(), data.end()); + size_t offset = findBytes(response, 0xff, 0x8A, 0x82); + + if (offset == ((size_t)-1)) + { + // No response found yet, lets wait for more bytes. + usleep(1000*100); + continue; + } + + // We have the start of the response, but do we have enough bytes? + bool ok = config.decode(response, offset); + // Yes! + if (ok) { + debug("(amb3665) found response at offset %zu\n", offset); + break; + } + // No complete response found yet, lets wait for more bytes. + usleep(1000*100); + } + + serial->close(); + + // FF8A8200800080710200000000FFFFFA00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0C3200021400FFFFFFFFFF010004000000FFFFFF01440000000000000000FFFF0B060100FFFFFFFFFF00020000FFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF18 + + detected->setAsFound(config.dongleId(), WMBusDeviceType::DEVICE_AMB3665, 9600, false, + detected->specified_device.linkmodes); + + verbose("(amb3665) detect %s\n", config.str().c_str()); + verbose("(amb3665) are you there? yes %s\n", config.dongleId().c_str()); + + return AccessCheck::AccessOK; +} + +static AccessCheck tryFactoryResetAMB3665(string device, shared_ptr manager, int baud) +{ + // Talk to the device and expect a very specific answer. + auto serial = manager->createSerialDeviceTTY(device.c_str(), baud, PARITY::NONE, "reset amb3665"); + bool ok = serial->open(false); + if (!ok) + { + verbose("(amb3665) could not open device %s using baud %d for reset\n", device.c_str(), baud); + return AccessCheck::NoSuchDevice; + } + + vector data; + // First clear out any data in the queue. + serial->receive(&data); + data.clear(); + + vector request_; + request_.resize(4); + request_[0] = AMBER_SERIAL_SOF; + request_[1] = CMD_FACTORYRESET_REQ; + request_[2] = 0; // No payload + request_[3] = xorChecksum3665(request_, 0, 3); + + assert(request_[3] == 0xee); + + verbose("(amb3665) try factory reset %s using baud %d\n", device.c_str(), baud); + serial->send(request_); + // Wait for 100ms so that the USB stick have time to prepare a response. + usleep(1000*100); + serial->receive(&data); + int limit = 0; + while (data.size() > 8 && data[0] != 0xff) + { + // Eat bytes until a 0xff appears to get in sync with the proper response. + // Extraneous bytes might be due to a partially read telegram. + data.erase(data.begin()); + vector more; + serial->receive(&more); + if (more.size() > 0) { + data.insert(data.end(), more.begin(), more.end()); + } + if (limit++ > 100) break; // Do not wait too long. + } + + serial->close(); + + debugPayload("(amb3665) reset response", data); + + if (data.size() < 8 || + data[0] != 0xff || + data[1] != 0x90 || + data[2] != 0x01 || + data[3] != 0x00 || // Status should be 0. + data[4] != xorChecksum3665(data, 0, 4)) + { + verbose("(amb3665) no response to factory reset %s using baud %d\n", device.c_str(), baud); + return AccessCheck::NoProperResponse; + } + verbose("(amb3665) received proper factory reset response %s using baud %d\n", device.c_str(), baud); + return AccessCheck::AccessOK; +} + +int bauds3665[] = { 1200, 2400, 4800, 9600, 19200, 38400, 56000, 115200, 0 }; + +AccessCheck factoryResetAMB3665(string device, shared_ptr manager, int *was_baud) +{ + AccessCheck rc = AccessCheck::NoSuchDevice; + + for (int i=0; bauds3665[i] != 0; ++i) + { + rc = tryFactoryResetAMB3665(device, manager, bauds3665[i]); + if (rc == AccessCheck::AccessOK) + { + *was_baud = bauds3665[i]; + return AccessCheck::AccessOK; + } + } + *was_baud = 0; + return AccessCheck::NoSuchDevice; +} diff --git a/src/wmbus_amb3665.h b/src/wmbus_amb3665.h new file mode 100644 index 0000000..0130436 --- /dev/null +++ b/src/wmbus_amb3665.h @@ -0,0 +1,23 @@ +// Copyright (C) 2019 Fredrik Öhrström (CC0-1.0) +// Defines documented in the Manual for the AMBER wM-Bus Modules Version 2.7 + +#define AMBER_SERIAL_SOF 0xFF + +#define CMD_DATA_REQ 0x00 +#define CMD_DATARETRY_REQ 0x02 +#define CMD_DATA_IND 0x03 +#define CMD_SET_MODE_REQ 0x04 +#define CMD_RESET_REQ 0x05 +#define CMD_SET_CHANNEL_REQ 0x06 +#define CMD_SET_REQ 0x09 +#define CMD_GET_REQ 0x0A +#define CMD_SERIALNO_REQ 0x0B +#define CMD_FWV_REQ 0x0C +#define CMD_RSSI_REQ 0x0D +#define CMD_SETUARTSPEED_REQ 0x10 +#define CMD_FACTORYRESET_REQ 0x11 +#define CMD_DATA_PRELOAD_REQ 0x30 +#define CMD_DATA_CLR_PRELOAD_REQ 0x31 +#define CMD_SET_AES_KEY_REQ 0x50 +#define CMD_CLR_AES_KEY_REQ 0x51 +#define CMD_GET_AES_DEV_REQ 0x52