591 lines
20 KiB
C#
Executable File
591 lines
20 KiB
C#
Executable File
/*
|
|
* 9/2020 (c) DJ0ABR, Kurt Moraw
|
|
* License: GPL 3.0
|
|
*
|
|
* udp.cs
|
|
* ------
|
|
*
|
|
* Creates a new thread which handles all incoming and outgoing UDP traffic.
|
|
* Communication to other threads is done via a thread-safe pipe.
|
|
* The UDP transmitter handles the correct datarate according to the modem speed.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Drawing;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Threading;
|
|
|
|
namespace oscardata
|
|
{
|
|
public static class Udp
|
|
{
|
|
// this thread handles udp RX
|
|
static Thread udprx_thread;
|
|
static Thread udptx_thread;
|
|
|
|
// Pipes for data transferred via UDP ports
|
|
static UdpQueue uq_rx = new UdpQueue();
|
|
static UdpQueue uq_tx = new UdpQueue();
|
|
static UdpQueue uq_ctrl = new UdpQueue();
|
|
static UdpQueue uq_fft = new UdpQueue();
|
|
static UdpQueue uq_iq = new UdpQueue();
|
|
static UdpQueue uq_rtty_rx = new UdpQueue();
|
|
|
|
public static int searchtimeout = 0;
|
|
static String last_audiodevstring = "";
|
|
|
|
// Constructor
|
|
// called when Udp is created by the main program
|
|
public static void InitUdp()
|
|
{
|
|
// create thread for UDP RX
|
|
udprx_thread = new Thread(new ThreadStart(Udprxloop));
|
|
udprx_thread.Name = "Thread: oscardata UDP-RX";
|
|
udprx_thread.Start();
|
|
|
|
// create thread for UDP TX
|
|
udptx_thread = new Thread(new ThreadStart(Udptxloop));
|
|
udptx_thread.Name = "Thread: oscardata UDP-TX";
|
|
udptx_thread.Start();
|
|
}
|
|
|
|
public static void Close()
|
|
{
|
|
try
|
|
{
|
|
udprx_thread.Abort();
|
|
udptx_thread.Abort();
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
// Udp RX Loop runs in its own thread
|
|
|
|
static void Udprxloop()
|
|
{
|
|
int extIPcnt = 0;
|
|
|
|
// define UDP port
|
|
UdpClient udpc = new UdpClient(statics.UdpRXport);
|
|
udpc.Client.ReceiveTimeout = 100;
|
|
|
|
while (statics.running)
|
|
{
|
|
try
|
|
{
|
|
// receive data from UDP port
|
|
IPEndPoint RemoteEndpoint = new IPEndPoint(IPAddress.Any, 0);
|
|
Byte[] rxarr = udpc.Receive(ref RemoteEndpoint);
|
|
if (rxarr != null)
|
|
{
|
|
// Data received:
|
|
// RemoteEndpoint.Address ... IP address of the sender
|
|
// RemoteEndpoint.Port ... port
|
|
// b[0] ... Type of data
|
|
// b+1 ... Byte array containing the data
|
|
int rxtype = rxarr[0];
|
|
Byte[] b = new byte[rxarr.Length - 1];
|
|
Array.Copy(rxarr, 1, b, 0, b.Length);
|
|
|
|
// payload
|
|
if (rxtype == statics.udp_payload)
|
|
{
|
|
//Console.WriteLine("payload");
|
|
uq_rx.Add(b);
|
|
}
|
|
|
|
// Broadcast response
|
|
if (rxtype == statics.udp_bc)
|
|
{
|
|
String ModIP = RemoteEndpoint.Address.ToString();
|
|
if (ModIP != statics.MyIP)
|
|
{
|
|
// this is not the local IP
|
|
// wait for 3 Receptions before accepting it
|
|
if (extIPcnt < 3)
|
|
{
|
|
//Console.WriteLine("myIP:"+statics.MyIP+" modem is ext IP:"+ModIP+", waiting:" + extIPcnt);
|
|
if (extIPcnt < 4) extIPcnt++;
|
|
if (extIPcnt < 3)
|
|
continue;
|
|
}
|
|
//Console.WriteLine("modem is ext IP, accepted");
|
|
}
|
|
else
|
|
{
|
|
//Console.WriteLine("modem is local IP");
|
|
extIPcnt = 0;
|
|
}
|
|
|
|
statics.ModemIP = ModIP;
|
|
searchtimeout = 0;
|
|
// message b contains audio devices and init status
|
|
|
|
statics.initAudioStatus = (b[0] == '1') ? 2 : 0;
|
|
statics.initAudioStatus |= (b[1] == '1') ? 1 : 0;
|
|
statics.initVoiceStatus = (b[2] == '1') ? 2 : 0;
|
|
statics.initVoiceStatus |= (b[3] == '1') ? 1 : 0;
|
|
|
|
//String s = statics.ByteArrayToString(b,4);
|
|
String s = statics.ByteArrayToStringUtf8(b, 4);
|
|
String[] sa1 = s.Split(new char[] { '^' });
|
|
statics.AudioPBdevs = sa1[0].Split(new char[] { '~' });
|
|
statics.AudioCAPdevs = sa1[1].Split(new char[] { '~' });
|
|
|
|
// has the device list changed ?
|
|
if(s != last_audiodevstring)
|
|
{
|
|
statics.GotAudioDevices = 1;
|
|
last_audiodevstring = s;
|
|
}
|
|
|
|
if(statics.GotAudioDevices == 0)
|
|
statics.GotAudioDevices = 1;
|
|
}
|
|
|
|
// FFT data
|
|
if (rxtype == statics.udp_fft)
|
|
{
|
|
int idx = 0;
|
|
statics.PBfifousage = b[idx++];
|
|
statics.CAPfifousage = b[idx++];
|
|
statics.RXlevelDetected = b[idx++];
|
|
statics.RXinSync = b[idx++];
|
|
statics.maxRXlevel = b[idx++];
|
|
statics.maxTXlevel = b[idx++];
|
|
statics.tune_frequency = b[idx++];
|
|
statics.tune_frequency <<= 8;
|
|
statics.tune_frequency += b[idx++];
|
|
//Console.WriteLine("f:" + statics.tune_frequency);
|
|
Byte[] b1 = new byte[b.Length - idx];
|
|
Array.Copy(b, idx, b1, 0, b1.Length);
|
|
drawFftBitmap(b1);
|
|
}
|
|
|
|
// IQ data
|
|
if (rxtype == statics.udp_iq)
|
|
{
|
|
Int16[] re = new Int16[b.Length / 2];
|
|
Int16[] im = new Int16[b.Length / 2];
|
|
int idx = 0;
|
|
|
|
for (int i = 0; i < b.Length; i+=4)
|
|
{
|
|
re[idx] = b[i+0];
|
|
re[idx] <<= 8;
|
|
re[idx] += b[i+1];
|
|
|
|
im[idx] = b[i+2];
|
|
im[idx] <<= 8;
|
|
im[idx] += b[i+3];
|
|
idx++;
|
|
}
|
|
drawBitmap(re, im);
|
|
}
|
|
|
|
if (rxtype == statics.udp_rtty_rx)
|
|
{
|
|
uq_rtty_rx.Add(b);
|
|
}
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
|
|
static int panelw = 75, panelh = 75;
|
|
static Bitmap bm;
|
|
const int maxsum = 5000;
|
|
static Int16[] resum = new Int16[maxsum];
|
|
static Int16[] imsum = new Int16[maxsum];
|
|
static int sumidx = 0;
|
|
static SolidBrush bgcol = new SolidBrush(Color.Silver);//FromArgb(255, (byte) 0x40, (byte) 0x00, (byte) 0x00));
|
|
|
|
static double scaleiq(int v)
|
|
{
|
|
double f = v;
|
|
f /= 15000.0;
|
|
// f goes from -1 to +1
|
|
// scale it to the graphics
|
|
const int sz = 45;
|
|
f += 1;
|
|
f /= 2;
|
|
f *= sz;
|
|
f += (panelw-sz)/2;
|
|
return f;
|
|
}
|
|
|
|
static void drawBitmap(Int16[] re, Int16[] im)
|
|
{
|
|
// collect IQ data
|
|
for (int i = 0; i < re.Length; i++)
|
|
{
|
|
if (sumidx < maxsum)
|
|
{
|
|
resum[sumidx] = re[i];
|
|
imsum[sumidx] = im[i];
|
|
sumidx++;
|
|
}
|
|
}
|
|
|
|
// check if there is space in bitmap fifo
|
|
// if the GUI does not process the bitmaps fast enough, just cancel it
|
|
if (uq_iq.Count() > 2)
|
|
return;
|
|
|
|
// bitmap for drawing the complete picture
|
|
bm = new Bitmap(panelw, panelh);
|
|
|
|
using (Graphics gr = Graphics.FromImage(bm))
|
|
{
|
|
// background
|
|
gr.FillRectangle(bgcol, 0,0, panelw, panelh);
|
|
// oscilloscope screen
|
|
gr.DrawImage(new Bitmap(Properties.Resources.screen), 2, 1);
|
|
/*
|
|
// screws at the 4 corners
|
|
Bitmap screw = new Bitmap(Properties.Resources.schraube);
|
|
gr.DrawImage(screw, 2, 2);
|
|
gr.DrawImage(screw, panelw - 2-screw.Width, 2);
|
|
gr.DrawImage(screw, 2, panelh - 2 - screw.Height);
|
|
gr.DrawImage(screw, panelw - 2 - screw.Width, panelh - 2 - screw.Height);
|
|
*/
|
|
// draw constellation points
|
|
for (int i = 0; i < sumidx; i++)
|
|
{
|
|
if (resum[i] == 0 || imsum[i] == 0) continue;
|
|
double dist = Math.Sqrt((resum[i] * resum[i]) + (imsum[i] * imsum[i]));
|
|
if (dist > 22000) continue; // do not draw outside scope
|
|
|
|
double x = scaleiq(resum[i]);
|
|
double y = scaleiq(imsum[i]);
|
|
|
|
double et = 1.6;
|
|
x -= et;
|
|
y -= et;
|
|
double w = et * 2;
|
|
double h = et * 2;
|
|
gr.FillEllipse(Brushes.Yellow, (int)x, (int)y, (int)w, (int)h);
|
|
}
|
|
}
|
|
|
|
uq_iq.Add(bm);
|
|
sumidx = 0;
|
|
}
|
|
|
|
static int fftw = 410, ffth = 72;
|
|
static Bitmap bmskala = new Bitmap(fftw,ffth);
|
|
static bool bmf = false;
|
|
static Font fnt = new Font("Verdana", 9.0f);
|
|
static Font smallfnt = new Font("Verdana", 7.0f);
|
|
static Pen penyl = new Pen(Brushes.Yellow, 1);
|
|
static Pen pengn = new Pen(Brushes.LightGreen, 3);
|
|
|
|
static void drawFftBitmap(Byte[] b1)
|
|
{
|
|
int yl = ffth - 20;
|
|
int yh = 20;
|
|
|
|
if (!bmf)
|
|
{
|
|
// pre-draw background
|
|
bmf = true;
|
|
Pen pen = new Pen(Brushes.Navy, 1);
|
|
Pen pensolid = new Pen(Brushes.Navy, 1);
|
|
pen.DashPattern = new float[] { 1.0F, 2.0F, 1.0F, 2.0F };
|
|
Pen penred = new Pen(Brushes.Red, 1);
|
|
Pen penred2 = new Pen(Brushes.Red, 2);
|
|
|
|
using (Graphics gr = Graphics.FromImage(bmskala))
|
|
{
|
|
gr.FillRectangle(bgcol, 0, 0, fftw, ffth);
|
|
gr.DrawImage(new Bitmap(Properties.Resources.osci), 0, 0);
|
|
|
|
for (int x = 10; x <= 390; x += 10)
|
|
gr.DrawLine(pen, x, yl, x, yh);
|
|
|
|
gr.DrawLine(penred2, 11, yl, 10, yh);
|
|
gr.DrawLine(penred, 150, yl, 150, yh);
|
|
gr.DrawLine(pensolid, 20, yl, 20, yh);
|
|
gr.DrawLine(pensolid, 280, yl, 280, yh);
|
|
gr.DrawLine(pensolid, 360, yl, 360, yh);
|
|
|
|
gr.DrawRectangle(penred, 15, yh, 270, yl-yh);
|
|
|
|
gr.DrawString("200", smallfnt, Brushes.Black, 8, yl);
|
|
gr.DrawString("1500", smallfnt, Brushes.Black, 135, yl);
|
|
gr.DrawString("2800", smallfnt, Brushes.Black, 265, yl);
|
|
gr.DrawString("3600", smallfnt, Brushes.Black, 345, yl);
|
|
|
|
gr.DrawString(statics.langstr[8], fnt, Brushes.Black, 100, 0);
|
|
}
|
|
|
|
bmskala.MakeTransparent(Color.White);
|
|
}
|
|
|
|
// check if there is space in bitmap fifo
|
|
// if the GUI does not process the bitmaps fast enough, just cancel it
|
|
if (uq_fft.Count() > 2)
|
|
return;
|
|
|
|
// bitmap for drawing the complete picture
|
|
bm = new Bitmap(442, 76);
|
|
|
|
int rshift = 14;
|
|
using (Graphics gr = Graphics.FromImage(bm))
|
|
{
|
|
// background
|
|
gr.FillRectangle(bgcol, 0, 0, bm.Width, bm.Height);
|
|
// scala
|
|
gr.DrawImage(bmskala,16,2);
|
|
|
|
if(statics.real_datarate == 45)
|
|
{
|
|
// RTTY Markers
|
|
int low = (statics.tune_frequency - 170 / 2)/10;
|
|
int high = (statics.tune_frequency + 170 / 2)/10;
|
|
gr.DrawLine(pengn, low + rshift, yl, low + rshift, yh + 3);
|
|
gr.DrawLine(pengn, high + rshift, yl, high + rshift, yh + 3);
|
|
}
|
|
/*
|
|
// screws at the 4 corners
|
|
Bitmap screw = new Bitmap(Properties.Resources.schraube);
|
|
gr.DrawImage(screw, 2, 2);
|
|
gr.DrawImage(screw, 442 - 2 - screw.Width, 2);
|
|
gr.DrawImage(screw, 2, 76 - 2 - screw.Height);
|
|
gr.DrawImage(screw, 442 - 2 - screw.Width, 76 - 2 - screw.Height);
|
|
*/
|
|
// spectrum
|
|
int lastus = -1;
|
|
// values
|
|
for (int i = 0; i < b1.Length-1; i+=2)
|
|
{
|
|
int us = b1[i];
|
|
us <<= 8;
|
|
us += b1[i + 1];
|
|
double fus = 0;
|
|
if (us > 0)
|
|
fus = 35 * Math.Log10((double)us / 10);
|
|
|
|
us = (int)(fus - 5.0);
|
|
if(lastus != -1 && i>0)
|
|
gr.DrawLine(penyl, i/2+ rshift, 76-lastus, i/2+1+ rshift, 76-us); // 15 istead of 16 to get it in exact position
|
|
lastus = us;
|
|
}
|
|
}
|
|
uq_fft.Add(bm);
|
|
}
|
|
|
|
// Udp TX Loop runs in its own thread
|
|
static void Udptxloop()
|
|
{
|
|
DateTime dt = DateTime.UtcNow;
|
|
UdpClient udpc = new UdpClient();
|
|
|
|
while (statics.running)
|
|
{
|
|
bool wait = true;
|
|
if(uq_ctrl.Count() > 0)
|
|
{
|
|
// Control Message: send immediately
|
|
Byte[] b = uq_ctrl.Getarr();
|
|
udpc.Send(b, b.Length, statics.ModemIP, statics.UdpTXport);
|
|
wait = false;
|
|
}
|
|
|
|
if(statics.PBfifousage < 3)
|
|
{
|
|
// we need to send more payload data
|
|
// but never send faster than 1000 Byte/s
|
|
// because statics.PBfifousage may be updated too slow
|
|
//DateTime dtact = DateTime.UtcNow;
|
|
//TimeSpan ts = dtact - dt;
|
|
//if (ts.TotalMilliseconds > statics.UdpBlocklen)
|
|
{
|
|
if (uq_tx.Count() > 0)
|
|
{
|
|
Byte[] b = uq_tx.Getarr();
|
|
udpc.Send(b, b.Length, statics.ModemIP, statics.UdpTXport);
|
|
wait = false;
|
|
//dt = dtact;
|
|
}
|
|
}
|
|
}
|
|
if (wait) Thread.Sleep(1);
|
|
}
|
|
}
|
|
|
|
public static void UdpBCsend(Byte[] b, String ip, int port)
|
|
{
|
|
UdpClient udpc = new UdpClient();
|
|
udpc.EnableBroadcast = true;
|
|
udpc.Send(b, b.Length, ip, port);
|
|
}
|
|
|
|
// send a Byte array via UDP
|
|
// this function can be called from anywhere in the program
|
|
// it transfers the data to the udp-tx thread via a thread-safe pipe
|
|
public static void UdpSendData(Byte[] b)
|
|
{
|
|
uq_tx.Add(b);
|
|
}
|
|
|
|
public static void UdpSendCtrl(Byte[] b)
|
|
{
|
|
uq_ctrl.Add(b);
|
|
}
|
|
|
|
public static int GetBufferCount()
|
|
{
|
|
return uq_tx.Count();
|
|
}
|
|
|
|
public static int GetBufferCountCtrl()
|
|
{
|
|
return uq_ctrl.Count();
|
|
}
|
|
|
|
public static Byte[] UdpReceive()
|
|
{
|
|
if (uq_rx.Count() == 0) return null;
|
|
|
|
return uq_rx.Getarr();
|
|
}
|
|
|
|
public static qpskitem UdpGetIQ()
|
|
{
|
|
if (uq_iq.Count() == 0) return null;
|
|
|
|
return uq_iq.GetQPSKitem();
|
|
}
|
|
|
|
public static Bitmap UdpBitmap()
|
|
{
|
|
if (uq_iq.Count() == 0) return null;
|
|
|
|
return uq_iq.GetBitmap();
|
|
}
|
|
|
|
public static Bitmap UdpFftBitmap()
|
|
{
|
|
if (uq_fft.Count() == 0) return null;
|
|
|
|
return uq_fft.GetBitmap();
|
|
}
|
|
|
|
public static bool IQavail()
|
|
{
|
|
if (uq_iq.Count() == 0) return false;
|
|
return true;
|
|
}
|
|
|
|
public static Byte[] getRTTYrx()
|
|
{
|
|
if (uq_rtty_rx.Count() == 0) return null;
|
|
return uq_rtty_rx.Getarr();
|
|
}
|
|
}
|
|
|
|
// this class is a thread safe queue wich is used
|
|
// to exchange data with the UDP RX/TX threads
|
|
public class UdpQueue
|
|
{
|
|
Queue myQ = new Queue();
|
|
|
|
public void Add(Byte [] b)
|
|
{
|
|
lock (myQ.SyncRoot)
|
|
{
|
|
myQ.Enqueue(b);
|
|
}
|
|
}
|
|
|
|
public void Add(qpskitem b)
|
|
{
|
|
lock (myQ.SyncRoot)
|
|
{
|
|
myQ.Enqueue(b);
|
|
}
|
|
}
|
|
|
|
public void Add(Bitmap bm)
|
|
{
|
|
lock (myQ.SyncRoot)
|
|
{
|
|
myQ.Enqueue(bm);
|
|
}
|
|
}
|
|
|
|
public Bitmap GetBitmap()
|
|
{
|
|
Bitmap b;
|
|
|
|
lock (myQ.SyncRoot)
|
|
{
|
|
b = (Bitmap)myQ.Dequeue();
|
|
}
|
|
return b;
|
|
}
|
|
|
|
public Byte[] Getarr()
|
|
{
|
|
Byte[] b;
|
|
|
|
lock (myQ.SyncRoot)
|
|
{
|
|
b = (Byte[])myQ.Dequeue();
|
|
}
|
|
return b;
|
|
}
|
|
|
|
public qpskitem GetQPSKitem()
|
|
{
|
|
qpskitem b;
|
|
|
|
lock (myQ.SyncRoot)
|
|
{
|
|
b = (qpskitem)myQ.Dequeue();
|
|
}
|
|
return b;
|
|
}
|
|
|
|
public qpskitem GetItem()
|
|
{
|
|
qpskitem b;
|
|
|
|
lock (myQ.SyncRoot)
|
|
{
|
|
b = (qpskitem)myQ.Dequeue();
|
|
}
|
|
return b;
|
|
}
|
|
|
|
public int Count()
|
|
{
|
|
int result;
|
|
|
|
lock (myQ.SyncRoot)
|
|
{
|
|
result = myQ.Count;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
lock (myQ.SyncRoot)
|
|
{
|
|
myQ.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
public class qpskitem
|
|
{
|
|
public int re;
|
|
public int im;
|
|
}
|
|
}
|