Featured

Remote SDR v5

Web based Transceiver

“Remote SDR” or “SDR Distant” is a web application allowing to remotely control an amateur radio transceiver between 1 MHz and 6 GHZ. Its first application was the duplex control of a station allowing links to the geostationary satellite QO-100 / Es’Hail 2.

Remote SDR version 5.0 is available on Github.
New features as :

  • New SDRs processed:
    • SDR Play RSP1, RSP1A,
    • MSI.SDR
  • Sharing the receiver between multiple users
  • https protocol (port 443) grouping all communications

Listen to QO-100 live with Remote SDR

Characteristics

Receiver
  • SDR in reception:
    • RTL-SDR (example: NESDR SMArt from Nooelec) or,
    • HackRF One or,
    • Adalm-Pluto
    • RSP1, RSP1A
    • MSI SDR
  • Frequency: 1 MHz to 6 GHz (depending on the chosen SDR)
  • Spectral band processed: 2 MHz on 2048 points (depending on the chosen SDR)
  • Audio: 1 channel
  • Demodulation: NBFM, WBFM, AM, SSB or CW
  • RTTY decoder
  • Automatic band scan
  • Equalizer on the audio channel
  • Notch filter
  • Noise filter
Transmitter
  • Hardware:
    • HackRF One or,
    • Adalm-Pluto (common with the receiver) or,
    • NBFM VHF / UHF SA818 module from G-NiceRF
  • Frequency: 1 MHz to 6 GHz (depending on the chosen SDR)
  • Power: 1 dBm to 30 dBm (depending on the chosen SDR)
  • Audio: 1 channel
  • Modulation: NBFM, SSB or CW
  • RTTY encoder
  • Transmitter modulation compressor
  • Audio equalizer
  • CTCSS encoder
  • DTMF encoder
  • 1750 Hz encoder
  • Programmable frequency offset for relays
  • Automatic CW Manipulator (Iambic A and Iambic B)
Radio processing
  • Hardware:
    • Orange Pi Zero 2 , or
    • Raspberry Pi 4B (2 GB)
  • Software:
    • Operating System: Armbian / Debian Bullseye
    • Web server: Node JS /Express
    • Signal processing: GNU Radio 3.9
    • Remote SDR (version v3 minimum)
      • Html
      • Javascript
      • Python 3
    • Chrome, Edge or Firefox web browser
  • Network interface: wired Ethernet or WIFI
  • Interfacing with Gpredict to compensate the Doppler of low orbit satellites
  • Interfacing with GS-232 type rotator
  • Display and Audio: WEB page on PC, tablet or smartphone

Configurations

COMPACT CONFIGURATION with an ADALM-PLUTO – Rasperry Pi 4 – ETHERNET
Remote SDR – Adalm-Pluto – Raspberry 4
AvantagesDisadvantages
– Well-known RPI4
– Wifi or Ethernet
– 12 bits of Pluto dynamic
– poor stability in frequency of the Adalm-Pluto

May require the addition of an external oscillator and the extension of the Adalm-Pluto band.

COMPACT CONFIGURATION with an ADALM-PLUTO – Opi Zero 2 – Wifi
Remote SDR – Adalm Pluto – Opi Zero 2 – Wifi
Avantages Disadvantages
– optimized for cost
– Wifi or Ethernet
– 12 bits of Pluto dynamic
– poor stability in frequency of the Adalm-Pluto

May require the addition of an external oscillator and the extension of the Adalm-Pluto band.

Mixed Configuration HackRF – RTL-SDR – Orange Pi Zero 2
RTL-SDR – HackRF One – Orange Pi zero 2

In degraded mode, it is possible to extend reception in the 0.5 MHz – 30 MHz band with an RTL-SDR V3.

Avantages Disadvantages
– optimized for cost
– good frequency stability of the TX if a TCXO mounted on the HackRF One
– different frequency coverage of RX and TX
– RX frequency stability depends on the chosen RTL-SDR model
– 8 bits of SDR dynamic
Mixed Configuration HackRF – RTL-SDR – Raspberry Pi 4
Remote SDR – HackRF One and RTL-SDR – Raspberry Pi 4

In degraded mode, it is possible to extend reception in the 0.5 MHz – 30 MHz band with an RTL-SDR V3.

Avantages Disadvantages
– Well-known RPI4
– good frequency stability of the TX if a TCXO mounted on the HackRF One
– different frequency coverage of RX and TX
– RX frequency stability depends on the chosen RTL-SDR model
– 8 bits of SDR dynamic
Configuration 2 Hack RF One
Remote SDR – 2 HackRF One – Raspberry Pi 4B
Avantages Disadvantages
– Well-known RPI4
– good frequency stability of the TX and RX if a TCXO mounted on the HackRF One or shared between them
– large frequency coverage
– 8 bits of SDR dynamic
Configurations RTL-SDR and SA818
VHF or UHF NBFM Transceiver
VHF and UHF NBFM Transceiver
Avantages Disadvantages
– cost around 100 €
– power 1w HF
– VHF 2m and/or UHF 70cm only
– only NBFM transmission, no SSB

Details on the one band transceiver here.

Details on the two bands transceiver here.

These configurations make it possible to locate the HF part near the antennas, which is essential for links above GHz. In the transmission chain, amplifiers must be added to bring the HF signal to the desired level as well as filtering to ensure that unwanted lines are not emitted. The SDR of the reception chain can be either an HackRF One, an RTL-SDR or a Pluto depending on the frequency band you want to cover. Not all RTL-SDR models cover the same band. The transmission reception is carried out in full-duplex which is essential during satellite connection to hear the return of its own signal.

As of today (October 2021), the Raspberry Pi 4B (2 GB) is a good solution, but there are supply difficulties. The “Orange Pi” are processors similar to the Raspberry Pi running under the Armbian or Debian Operating System. In 2020 I used the Orange Pi One Plus, now in 2021 the Orange Pi Zero 2 also offers a 64-bit / 4-core processor, but also an ethernet or wifi connection. They serve as a web server and perform radio signal processing.

Example Transceiver QO-100

F1ATB QO-100 Transceiver in June 2022

Example UHF Transceiver – Wifi – Orange PI Zero 2

New configuration with the Orange Pi Zero 2 which allows communication via WIFI. No more wired Ethernet link, only 220v near the transmitter / receiver.

Experimental 432 MHz (70 cm) transceiver

Note that you need a USB Hub between the Pluto and the Orange PI One Plus (not for the Orange Pi Zero 2). This corresponds to a system bug.

Code Source et Image

The source code and the image for Orange Pi and Raspberry Pi 4B are available on Github https://github.com/F1ATB/Remote-SDR .

Key points of Remote SDR

In addition to being able to locate the HF treatment near the antennas, other points should be noted such as:

Data rate reduction

An SDR like the Pluto requests 1.4 M samples / s (minimum) * 2 Bytes (16 bits) * 2 channels (I and Q) = 5.6 M Bytes / s for reception. It is the same for the emission. Which gives us more than 10M bytes / second.
With Remote SDR, output on Ethernet or WiFi requires:

  • 10 k samples / s * 2 bytes for the audio in reception
  • 10.24 k sample / s * 2 bytes for the received spectrum
  • 10 k sample / s * 2 bytes for transmit audio
    We are at less than 100 k bytes / s by adding the control data.

There is therefore a reduction of approximately 100 in the communication speed required, which facilitates remote control via internet / ethernet without loss of quality through data compression.

The mini remote computer

Indeed, we have a remote computer which has a GPIO to which it is possible to add functions. For example, controlling an antenna rotor, measuring electrical voltages, temperatures, etc., … It is possible to access the system via the web (Apache server), in SSH to launch an application in terminal mode, or in graphical mode by the desktop and VNC.

Posts Remote-SDR

Power measurement with an ESP32

Measuring in real time the electrical power consumed or even produced by a solar installation allows for better management. For example, in the event of a surplus, in a self-consumption installation, hot water production can be started.
The assembly below:

  • measures power
  • provided the instantaneous voltage and current curve on a web page
  • activates a relay in case of production above consumption
  • provided a report to the central home automation system (Domoticz) for registration.

The ESP32 is a microcontroller adapted to our needs. It includes :

  • analog inputs for measuring voltages,
  • digital inputs/outputs to activate a relay if necessary,
  • a WIFI connection for remote reporting on a web page or a home automation system.

Current Sensor

Current transformer 100A/50mA

To measure the current, a current sensor is used through which the mains phase wire is passed. At the output, acting like a transformer, it provides an identical current, but 2000 times lower. This current is sent across a resistor and we will measure the voltage generated.

There are different models depending on the Max current that you want to measure. The 100A version is suitable for a home with a maximum power delivered of 12kVA. It is found in China at Aliexpress.

Voltage sensor

Transformer 220V/6V

To measure the voltage, we use a classic wire-wound step-down transformer that isolates us from the mains. For example a 220v/6v. We need a model as small as possible, we do not take any power. It’s not very easy to find anymore. A so-called bell transformer can do the trick.

Current and Voltage Measurement

The measurement of the 2 voltages representing the current and the mains voltage is done by the analog inputs of the ESP32. These inputs accept a voltage between 0 and 3.3V and digitize the value on 12 bits, values between 0 and 4095. To adapt to the input dynamics, a voltage reference is created in the middle of the range at 1.65V = 3.3V/2.

We take the 3.3V from the ESP32 which, passing through a bridge of 2 resistors (R6 and R7) of 4700 ohm connected to ground, provides us with a reference of 1.65V in the middle. To avoid measurement noise, a 470uF capacitor (C2) filters the 3.3V and another 10uF (C1) filters the midpoint at 1.65V.

In order not to exceed 3.3V peak to peak of the signals to be measured, or 1.65V peak, we set a limit of +-1V effective maximum.

For the current probe with 80A and a resistance of 24 ohm, we arrive at approximately 1V.

24*80A/2000=0.96V

At home, with a 12KVA subscription, I should not exceed 60A..

For the voltage measurement, you have to put a bridge of resistors (R4 and R5) to lower the 6V around 1V effective.

Connection to ESP32

ESP32 WROOM AZ-DELIVERY

The treasure hunt with these cards that integrate an ESP32, is to find the GPIOs available and not used for Flash programming etc.
In our case, we measure the following voltages:

  • GPIO 35: the reference voltage at 1.65V in theory.
  • GPIO 33: the voltage representing the current to be measured
  • GPIO 32: the output voltage of the transformer

2 LEDs on GPIOs 18 and 19 flash every 2s. The yellow if we consume current, the green if we supply current, because we are in overproduction.

2 solid relays on GPIO 22 and 23 allow:

  • to turn on a light in the room controlled by the switch on the GPIO 05,
  • energize the water heater start-up power relay in the event of significant electricity production.

Measure

Measuring the 2 values representing voltage and current takes about 120uS. In practice, it is planned over a period of 20ms (1/50Hz) to sample 100 pairs of values, which will give a good description of the a priori sinusoidal voltage and of the current often disrupted by switching power supplies.

Formulas for calculating powers

In order not to block the ESP32 for 20ms, it may need to transmit messages, we will take about ten measurements of the current/voltage pair for a little over 1 ms. A series of 10 measurements will be restarted 21 ms later on the next period of the sector. Thus the ESP32 can devote itself to other tasks. To properly time each measurement, we use the microsecond clock of the ESP32.

Every 10 s, we perform:

  • the calculation of the effective current
  • the calculation of the effective voltage
  • the calculation of the power in kVA
  • the calculation of the power in kW

A prior calibration was made to define the multiplicative constant to convert the voltage measured in binary to the real voltage or current.

Code

All code is written using the Arduino IDE. It is initially injected by the serial link, then once in place, it can be modified if necessary by WIFI as described here.

#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ArduinoOTA.h>
#include <RemoteDebug.h>

const char* ssid = "******";
const char* password = "*********";
RemoteDebug Debug;
unsigned long previousMeasureMillis;
unsigned long previousComputeMillis;
unsigned long previousClignoteMillis;
unsigned long previousLightMillis;
bool Clignote = false;
bool LightOn =false;

// Set your Static IP address
IPAddress local_IP(192, 168, 0, 208);
// Set your Gateway IP address
IPAddress gateway(192, 168, 0, 254);

IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(8, 8, 8, 8);   //optional
IPAddress secondaryDNS(8, 8, 4, 4); //optional

WebServer server(80);

//PINS
const int AnalogIn0 = 35;  // GPIO 35
const int AnalogIn1 = 32;  // GPIO 32
const int AnalogIn2 = 33;  // GPIO 33
const int RelayLight = 22;
const int RelayWaterHeater = 23;
const int LedYellow = 18;
const int LedGreen = 19;
const int Inter = 5;


int value0;
int value1[100];
int value2[100];

float Uef;
float Ief;
float PVA;
float PW;
float PowerFactor;
float kV = 0.2083;
float kI = 0.0642;
float Wh = 0;


//Client of Domoticz
const char* host = "192.168.0.99";
const int httpPort = 8080;
const int idxPower = 1011;
int Nloop = 0;
int day = -1;

//  SERVER
//***********
void handleRoot() {
  String S;
  Debug.println(F("Client Web"));
  S = "<h3 style='text-align:center'>Total Power Consumption</h3>";
  S += "<div style='display:flex;justify-content:space-around'>";
  S += "<div><table align='center' style='border:3px inset grey;padding:4px;background-color:#ddd;'>";
  S += "<tr><td>U :</td><td>" + String(Uef) + "</td></tr>";
  S += "<tr><td>I :</td><td>" + String(Ief) + "</td></tr>";
  S += "<tr><td>P Watt :</td><td>" + String(PW) + "</td></tr>";
  S += "<tr><td>P VA :</td><td>" + String(PVA) + "</td></tr>";
  S += "<tr><td>Power Factor :</td><td>" + String(PowerFactor) + "</td></tr>";
  S += "<tr><td>kWh :</td><td>" + String(Wh / 1000) + "</td></tr>";
  S += "</table></div>";
  S += "<div style='text-align:center;'><strong><span style='color:red;'>U_</span><span style='color:lightgreen;'> I_</span></strong>";
  S += "<p><svg height='200' width='500' style='border:3px inset grey;background-color:#ddd;'>";
  S += "<line x1='0' y1='200' x2='0' y2='0' style='stroke:rgb(0,0,0);stroke-width:2' />";
  S += "<line x1='0' y1='100' x2='500' y2='100' style='stroke:rgb(0,0,0);stroke-width:2' />";
  int Vmax = 1;
  int Imax = 1;
  for (int i = 0; i < 100; i++) {
    Vmax = max(abs(value1[i]), Vmax);
    Imax = max(abs(value2[i]), Imax);
    Debug.println(value1[i]);
  }
  S += "<polyline points='";
  for (int i = 0; i < 100; i++) {
    int Y = 100 - 100 * value1[i] / Vmax;
    int X = 5 * i;
    S += String(X) + "," + String(Y) + " ";
  }
  S += "' style='fill:none;stroke:red;stroke-width:2' />";
  S += "<polyline points='";
  for (int i = 0; i < 100; i++) {
    int Y = 100 - 100 * value2[i] / Imax;
    int X = 5 * i;
    S += String(X) + "," + String(Y) + " ";
  }
  S += "' style='fill:none;stroke:green;stroke-width:2' />";
  S += "</svg></p></div></div>";
  S += "<div style='width:96%;margin:auto;border:3px inset grey;background-color:#666;height:20px;position:relative;'>";
  int X = 0;
  int Y = 99 - 99 * PW / 12000;
  if (PW < 0) {
    X = 198 - Y;
    Y = 0;
  }
  S += "<div style='position:absolute;top:3px;height:14px;background-color:yellow;left:" + String(X) + "%;right:" + String(Y) + "%;'>";
  S += "</div></div>";
  server.send(200, "text/html", S );
}

void handleNotFound() {
  Debug.println(F("File Not Found"));
  String message = "File Not Foundnn";
  message += "URI: ";
  message += server.uri();
  message += "nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "nArguments: ";
  message += server.args();
  message += "n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "n";
  }
  server.send(404, "text/plain", message);

}
// DOMOTICZ client
//****************
void SendToDomoticz() {
  String url;
  Nloop = (Nloop + 1) % 4;

  if (Nloop == 0 || Nloop == 2) {
    // Use WiFiClient class to create TCP connections
    WiFiClient client;
    if (!client.connect(host, httpPort)) {
      Serial.println("connection failed");
      return;
    }
    if (Nloop == 2) {
      // We now create a URI for the request
      url = "/json.htm?type=command&param=getServerTime";  //Obtain time from Domoticz server

    } else {
      // We now create a URI for the request
      url = "/json.htm?type=command&param=udevice&idx=";
      url += String(idxPower);
      url += "&nvalue=0&svalue=";
      url += String(PW);
      url += ";";
      url += String(Wh);


    }

    // This will send the request to the server
    client.print(String("GET ") + url + " HTTP/1.1rn" +
                 "Host: " + host + "rn" +
                 "Connection: closernrn");
    unsigned long timeout = millis();
    while (client.available() == 0) {
      if (millis() - timeout > 5000) {
        Serial.println(">>> Client Timeout !");
        client.stop();
        return;
      }
    }

    // Read all the lines of the reply from server and print them to Serial
    while (client.available()) {
      String line = client.readStringUntil('r');
      // Serial.print(line);
      int p = line.indexOf("ServerTime");
      if (p > 0) {
        String subline = line.substring(p + 14, p + 32);
        p = subline.lastIndexOf("-");
        subline = subline.substring(p + 1, p + 3); //day after month
        // Serial.print(subline);
        int theday = subline.toInt();
        if (day != theday) {
          day = theday;
          Wh = 0; // Rest Watt Heure of the day
          //   Serial.println("Reset Watt Hour");
        }
      }

    }

    //  Serial.println();
    //  Serial.println("closing connection");
  }

}

// POWER
//********
void MeasurePower() {
  int i;
  value0 = analogRead(AnalogIn0);
  for (int k = 0; k < 11; k++) { //Read values during 1.2ms
    i = (micros() % 20000) / 200;
    value1[i] = analogRead(AnalogIn1) - value0;
    value2[i] = analogRead(AnalogIn2) - value0;
  }
}
void ComputePower() {
  float V;
  float I;
  Uef = 0;
  Ief = 0;
  PW = 0;
  for (int i = 0; i < 100; i++) {
    V = kV * float(value1[i]);
    Uef += sq(V);
    I = kI * float(value2[i]);
    Ief += sq(I );
    PW += V * I;
  }
  Uef = sqrt(Uef / 100) ;
  Ief = sqrt(Ief / 100) ;
  PW = floor(PW / 100);
  PVA = floor(Uef * Ief);
  PowerFactor = floor(100 * PW / PVA) / 100;
  Wh += PW / 360; //Every 10s
}

void Overproduction(){
  if (PW <-500){ //switch On water heater    
    digitalWrite(RelayWaterHeater,HIGH);
  }
  if (PW>2000){ //Switch Off with hyteresis
     digitalWrite(RelayWaterHeater,LOW) ; 
  }
}


// SETUP
//*******
void setup() {
  Serial.begin(115200);
  Serial.println("Booting");
  // Configures static IP address
  if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
    Serial.println("STA Failed to configure");
  }
  //WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Debug.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }
  // init remote debug
  Debug.begin("ESP32");

  initOTA();
  Debug.println("Ready");
  Debug.print("IP address: ");
  Debug.println(WiFi.localIP());
  Serial.print("IP address: ");
  server.on("/", handleRoot);

  server.on("/inline", []() {
    server.send(200, "text/plain", "this works as well");
  });

  server.onNotFound(handleNotFound);

  server.begin();
  Debug.println("HTTP server started");
  previousMeasureMillis = millis();
  previousComputeMillis = millis();
  previousClignoteMillis = millis();

  pinMode( RelayLight , OUTPUT);
  pinMode( RelayWaterHeater , OUTPUT);
  pinMode( LedYellow , OUTPUT);
  pinMode( LedGreen , OUTPUT);
  pinMode( Inter , INPUT_PULLUP);
  digitalWrite(RelayLight, LOW);
  digitalWrite(RelayWaterHeater, LOW);
  digitalWrite(LedYellow, LOW);
  digitalWrite(LedGreen, LOW);
}

// LOOP
//******
void loop() {
  ArduinoOTA.handle();
  Debug.handle();
  server.handleClient();
  if (millis() - previousMeasureMillis >= 21) {
    previousMeasureMillis = millis();
    MeasurePower();
  }
  if (millis() - previousComputeMillis >= 10000) {
    previousComputeMillis = millis();
    ComputePower();
    Overproduction();
    SendToDomoticz();
  }
  if (millis() - previousClignoteMillis >= 2000) {
    Clignote = !Clignote;
    if (Clignote) {
      previousClignoteMillis = millis() - 1950;

    } else {
      previousClignoteMillis = millis();
    }
    if (PW >= 0) {
      digitalWrite(LedYellow, Clignote);
      digitalWrite(LedGreen, LOW);
    } else {
      digitalWrite(LedYellow, LOW);
      digitalWrite(LedGreen, Clignote);
    }
  }
  //Light
  if (digitalRead(Inter)==LOW  &&  (millis()-previousLightMillis) >500) {
    LightOn=!LightOn;
    digitalWrite(RelayLight,LightOn);
    previousLightMillis=millis();
  }
  if (millis() - previousLightMillis >= 120000) {
    LightOn=false;
    digitalWrite(RelayLight,LightOn);
    previousLightMillis=millis();
  }


}

// OTA
//******
void initOTA() {
  // Port defaults to 3232
  // ArduinoOTA.setPort(3232);
  // Hostname defaults to esp3232-[MAC]
  ArduinoOTA.setHostname("ESP32");
  // No authentication by default
  // ArduinoOTA.setPassword("admin");
  // Password can be set with it's md5 value as well
  // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");
  ArduinoOTA
  .onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH)
      type = "sketch";
    else // U_SPIFFS
      type = "filesystem";
    // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
    Debug.println("Start updating " + type);
  })
  .onEnd([]() {
    Debug.println("nEnd");
  })
  .onProgress([](unsigned int progress, unsigned int total) {
    Debug.printf("Progress: %u%%r", (progress / (total / 100)));
  })
  .onError([](ota_error_t error) {
    Debug.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Debug.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Debug.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Debug.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Debug.println("Receive Failed");
    else if (error == OTA_END_ERROR) Debug.println("End Failed");
  });
  ArduinoOTA.begin();
}

Web Page

The code includes a small web server that displays on one page the various measurements as well as the voltage and current over a period of 20 ms.

Hardware

Hardware

In an electrician’s box we install :

  • The ESP32 card (AZ-Delivery)
  • A 5V 1A power supply for the ESP32
  • A low voltage transformer to measure the voltage
  • 2 solid state relays connected to 3.3V, controlled by the ESP32
  • On the front panel as an option, a push button and 2 LEDs

Connected to this box, we have the current probe to be placed around the phase wire of the mains to be measured.