SSB_HighSpeed_Modem/oscardata/oscardata/udp.cs

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;
}
}