diff --git a/devices/CMakeLists.txt b/devices/CMakeLists.txt
index 04f2916f9..5232822f1 100644
--- a/devices/CMakeLists.txt
+++ b/devices/CMakeLists.txt
@@ -39,3 +39,5 @@ endif()
if(ENABLE_USRP AND UHD_FOUND)
add_subdirectory(usrp)
endif()
+
+add_subdirectory(metis)
diff --git a/devices/metis/CMakeLists.txt b/devices/metis/CMakeLists.txt
new file mode 100644
index 000000000..1a5f7009e
--- /dev/null
+++ b/devices/metis/CMakeLists.txt
@@ -0,0 +1,24 @@
+project(metisdevice)
+
+set(metisdevice_SOURCES
+ devicemetis.cpp
+ devicemetisscan.cpp
+)
+
+set(metisdevice_HEADERS
+ devicemetis.h
+ devicemetisscan.h
+)
+
+add_library(metisdevice SHARED
+ ${metisdevice_SOURCES}
+)
+
+set_target_properties(metisdevice
+ PROPERTIES DEFINE_SYMBOL "devices_EXPORTS")
+
+target_link_libraries(metisdevice
+ sdrbase
+)
+
+install(TARGETS metisdevice DESTINATION ${INSTALL_LIB_DIR})
diff --git a/devices/metis/devicemetis.cpp b/devices/metis/devicemetis.cpp
new file mode 100644
index 000000000..89fb50cdd
--- /dev/null
+++ b/devices/metis/devicemetis.cpp
@@ -0,0 +1,32 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2020 Edouard Griffiths, F4EXB //
+// //
+// 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 as 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 V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include "devicemetis.h"
+
+DeviceMetis::DeviceMetis()
+{
+}
+
+DeviceMetis::~DeviceMetis()
+{
+}
+
+DeviceMetis& DeviceMetis::instance()
+{
+ static DeviceMetis inst;
+ return inst;
+}
\ No newline at end of file
diff --git a/devices/metis/devicemetis.h b/devices/metis/devicemetis.h
new file mode 100644
index 000000000..edb356538
--- /dev/null
+++ b/devices/metis/devicemetis.h
@@ -0,0 +1,42 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2020 Edouard Griffiths, F4EXB //
+// //
+// 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 as 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 V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef DEVICES_METIS_DEVICEMETIS_H_
+#define DEVICES_METIS_DEVICEMETIS_H_
+
+#include "export.h"
+#include "devicemetisscan.h"
+
+class DEVICES_API DeviceMetis
+{
+public:
+ static DeviceMetis& instance();
+ void scan() { m_scan.scan(); }
+ void enumOriginDevices(const QString& hardwareId, PluginInterface::OriginDevices& originDevices) {
+ m_scan.enumOriginDevices(hardwareId, originDevices);
+ }
+ const DeviceMetisScan::DeviceScan* getDeviceScanAt(unsigned int index) const { return m_scan.getDeviceAt(index); }
+
+protected:
+ DeviceMetis();
+ ~DeviceMetis();
+
+private:
+ DeviceMetisScan m_scan;
+};
+
+#endif // DEVICES_METIS_DEVICEMETIS_H_
\ No newline at end of file
diff --git a/devices/metis/devicemetisscan.cpp b/devices/metis/devicemetisscan.cpp
new file mode 100644
index 000000000..5772206d8
--- /dev/null
+++ b/devices/metis/devicemetisscan.cpp
@@ -0,0 +1,146 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2020 Edouard Griffiths, F4EXB //
+// //
+// 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 as 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 V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+#include
+
+#include "devicemetisscan.h"
+
+void DeviceMetisScan::scan()
+{
+ m_scans.clear();
+
+ if (m_udpSocket.bind(QHostAddress::AnyIPv4, 10001, QUdpSocket::ShareAddress))
+ {
+ connect(&m_udpSocket, SIGNAL(readyRead()), this, SLOT(readyRead()));
+ }
+ else
+ {
+ qDebug() << "DeviceMetisScan::scan: cannot bind socket";
+ return;
+ }
+
+ unsigned char buffer[63];
+ buffer[0] = (unsigned char) 0xEF;
+ buffer[1] = (unsigned char) 0XFE;
+ buffer[2] = (unsigned char) 0x02;
+ std::fill(&buffer[3], &buffer[63], 0);
+
+ if (m_udpSocket.writeDatagram((const char*) buffer, sizeof(buffer), QHostAddress::Broadcast, 1024) < 0)
+ {
+ qDebug() << "DeviceMetisScan::scan: discovery writeDatagram failed " << m_udpSocket.errorString();
+ return;
+ }
+ else
+ {
+ qDebug() << "DeviceMetisScan::scan: discovery writeDatagram sent";
+ }
+
+ // wait for 1 second before returning
+ QEventLoop loop;
+ QTimer *timer = new QTimer(this);
+ connect(timer, SIGNAL(timeout()), &loop, SLOT(quit()));
+ timer->start(500);
+
+ qDebug() << "DeviceMetisScan::scan: start 0.5 second timeout loop";
+ // Execute the event loop here and wait for the timeout to trigger
+ // which in turn will trigger event loop quit.
+ loop.exec();
+
+ disconnect(&m_udpSocket, SIGNAL(readyRead()), this, SLOT(readyRead()));
+ m_udpSocket.close();
+}
+
+void DeviceMetisScan::enumOriginDevices(const QString& hardwareId, PluginInterface::OriginDevices& originDevices)
+{
+ scan();
+
+ for (int i = 0; i < m_scans.size(); i++)
+ {
+ const DeviceScan& deviceInfo = m_scans.at(i);
+ QString serial = QString("%1:%2_%3").arg(deviceInfo.m_address.toString()).arg(deviceInfo.m_port).arg(deviceInfo.m_serial);
+ QString displayableName(QString("Metis[%1] %2").arg(i).arg(serial));
+ originDevices.append(PluginInterface::OriginDevice(
+ displayableName,
+ hardwareId,
+ serial,
+ i, // sequence
+ 8, // Nb Rx
+ 1 // Nb Tx
+ ));
+ }
+}
+
+const DeviceMetisScan::DeviceScan* DeviceMetisScan::getDeviceAt(unsigned int index) const
+{
+ if (index < m_scans.size()) {
+ return &m_scans.at(index);
+ } else {
+ return nullptr;
+ }
+}
+
+void DeviceMetisScan::getSerials(QList& serials) const
+{
+ for (int i = 0; i < m_scans.size(); i++) {
+ serials.append(m_scans.at(i).m_serial);
+ }
+}
+
+void DeviceMetisScan::readyRead()
+{
+ QHostAddress metisAddress;
+ quint16 metisPort;
+ unsigned char buffer[1024];
+
+ if (m_udpSocket.readDatagram((char*) &buffer, (qint64) sizeof(buffer), &metisAddress, &metisPort) < 0)
+ {
+ qDebug() << "DeviceMetisScan::readyRead: readDatagram failed " << m_udpSocket.errorString();
+ return;
+ }
+
+ QString metisIP = QString("%1:%2").arg(metisAddress.toString()).arg(metisPort);
+
+ if (buffer[0] == 0xEF && buffer[1] == 0xFE)
+ {
+ switch(buffer[2])
+ {
+ case 3: // reply
+ // should not happen on this port
+ break;
+ case 2: // response to a discovery packet
+ {
+ QByteArray array((char *) &buffer[3], 6);
+ QString serial = QString(array.toHex());
+ m_scans.append(DeviceScan(
+ serial,
+ metisAddress,
+ metisPort
+ ));
+ m_serialMap.insert(serial, &m_scans.back());
+ qDebug() << "DeviceMetisScan::readyRead: found Metis at:" << metisIP << "MAC:" << serial;
+ }
+ break;
+ case 1: // a data packet
+ break;
+ }
+ }
+ else
+ {
+ qDebug() << "DeviceMetisScan::readyRead: received invalid response to discovery";
+ }
+}
\ No newline at end of file
diff --git a/devices/metis/devicemetisscan.h b/devices/metis/devicemetisscan.h
new file mode 100644
index 000000000..5d3b7366f
--- /dev/null
+++ b/devices/metis/devicemetisscan.h
@@ -0,0 +1,71 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2020 Edouard Griffiths, F4EXB //
+// //
+// 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 as 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 V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef DEVICES_METIS_DEVICEMETISSCAN_H_
+#define DEVICES_METIS_DEVICEMETISSCAN_H_
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include