diff --git a/Interfaces/Interface.cs b/Interfaces/Interface.cs
index 09712cc..e87de5a 100644
--- a/Interfaces/Interface.cs
+++ b/Interfaces/Interface.cs
@@ -25,6 +25,9 @@
namespace RNS
{
+ ///
+ /// Initial Interface class
+ ///
public class Interface
{
public bool IN = false;
@@ -56,6 +59,9 @@ namespace RNS
public int ifac_size;
//List<>
+ ///
+ /// Basic initialization
+ ///
public Interface()
{
rxb = 0;
@@ -68,6 +74,9 @@ namespace RNS
Callbacks = new CallbackClass();
}
+ ///
+ /// Arguments for callback event handler
+ ///
public class CallbackArgs : EventArgs
{
public byte[] Message { get; private set; }
@@ -79,6 +88,9 @@ namespace RNS
}
}
+ ///
+ /// Class for callback event handler
+ ///
public class CallbackClass
{
//public delegate void CallbackEventHandler(object sender, CallbackArgs args);
diff --git a/Interfaces/RNodeInterface.cs b/Interfaces/RNodeInterface.cs
new file mode 100644
index 0000000..17f3fb2
--- /dev/null
+++ b/Interfaces/RNodeInterface.cs
@@ -0,0 +1,1512 @@
+// MIT License
+//
+// Copyright (c) 2016-2022 Mark Qvist / unsigned.io
+// C# Port (c) 2023 Michael Faragher / betweentheborders.com
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+// This is considered pre-alpha of a minimum viable product. No warranties are provided and compatibility is not assured.
+
+using System;
+using System.IO;
+using System.IO.Ports;
+
+namespace RNS.Interfaces
+{
+
+ public class RNodeInterface: RNS.Interface
+ {
+ SerialPort Port;
+
+ int Timeout = 1000;
+
+
+ //RNode Serial Specs
+ int SerialSpeed = 115200;
+ int SerialDataBits = 8;
+ StopBits SerialStopBits = StopBits.One;
+ Parity SerialParity = Parity.None;
+
+
+ //Stack Specs
+ int HW_MTU = 508;
+
+ //Network
+ List Packet_Queue = new List();
+ string ID_Callsign = "";
+ int ID_Interval = 0;
+ int Announce_Rate_Target;
+
+ //RNode Fixed Config
+ int RSSI_Offset = 157;
+ const int FB_Pixel_Width = 64;
+ const int FB_Bits_Per_Pixel = 1;
+ const int FB_Pixels_Per_Byte = 8 / FB_Bits_Per_Pixel;
+ const int FB_Bytes_Per_Line = FB_Pixel_Width / FB_Pixels_Per_Byte;
+ const int MAX_CHUNK = 32768;
+ const int FREQ_MIN = 137000000;
+ const int FREQ_MAX = 1020000000;
+ const int CALLSIGN_MAX_LEN = 32;
+ const int REQUIRED_FW_VER_MAJ = 1;
+ const int REQUIRED_FW_VER_MIN = 52;
+ const int RECONNECT_WAIT = 5000;
+
+ //RNode state
+ bool isOnline = false;
+ bool isDetected = false;
+ bool isDetached = false;
+ bool isReconnecting = false;
+ bool Firmware_OK = false;
+ bool Interface_Ready = false;
+ bool FlowControl = false;
+ DateTime? First_TX;
+ Thread ReceiveThread;
+ public string Name { get; private set; } = "Undefined Interface";
+ RNS.Transport Owner;
+ int Last_ID;
+ int Reconnect_W;
+ bool isValidConfig = false;
+ bool should_ID = false;
+
+ //Desired State
+ uint Frequency;
+ uint Bandwidth;
+ byte SF;
+ byte CR;
+ byte TXPower;
+ byte State;
+
+ //Reported State
+ int R_Frequency;
+ int R_Bandwidth;
+ int R_SF;
+ byte R_CR;
+ byte R_TXPower;
+ byte R_State;
+ byte R_Lock;
+ int R_Stat_RX;
+ int R_Stat_TX;
+ int R_Stat_RSSI;
+ float R_Stat_SNR;
+ byte R_Random;
+ int Bitrate;
+ float Bitrate_kbps;
+
+ int RXB;
+ int TXB;
+
+ bool DebugOutput = true;
+
+ //RNode Hard/firmware
+ int Maj_Version;
+ int Min_Version;
+ byte Platform;
+ byte MCU;
+ bool hasDisplay = false;
+
+ ///
+ /// RNode initialization routine.
+ ///
+ /// Reticulum specific. NYI.
+ /// Interface name. Example: RNode on Server 3
+ /// Port name. Example: COM6 or /dev/ttyAMC0
+ /// Frequency in Hz. Example: 915000000 for a 915MHz signal. Note: You are responsible for selecting a band legal in your nation and municipality.
+ /// Bandwidth in Hz: Example: 125000 for 125kHz bandwidth. Acceptable values from 7800 to 500000.
+ /// Transmission power in dBm. Acceptable values range from 0 - 17.
+ /// Spreading factor. Acceptable values range from 7 - 12.
+ /// Coding rate, 4 data bits per N transmitted bits. Acceptable values range from 5 - 8.
+ /// I'm actually unsure. Will consult with stack designer.
+ /// Time, in seconds, between callsign broadcasts. 0 disables.
+ /// Station callsign. When a packet is sent, begins a cooldown of _id_interval seconds, then broadcasts station ID. Meant for amateur radio compliance. string = "" disables.
+ /// Thrown if platform is unsupported.
+ /// Thrown when interface contains errors.
+ /// Thrown if serial port is unavailable.
+ public RNodeInterface(RNS.Transport _owner, string _name, string _port, uint _frequency = 0, uint _bandwidth = 0, byte _txpower = 0, byte _sf = 0, byte _cr = 0, bool _flow_control = false, int _id_interval = 0, string _id_callsign = "")
+ {
+ if(System.Environment.OSVersion.Platform == PlatformID.Other)
+ {
+ throw new Exception("Invalid platform. Library currently supports Windows and Linux. If you see this message on either of these platforms, please file a bug report");
+ }
+
+ RXB = 0;
+ TXB = 0;
+
+ Owner = _owner;
+ Name = _name;
+ Port = new SerialPort(_port);
+
+ isOnline = false;
+ isDetached = false;
+ isReconnecting = false;
+
+ Frequency = _frequency;
+ Bandwidth = _bandwidth;
+ TXPower = _txpower;
+ SF = _sf;
+ CR = _cr;
+ State = KISS.RADIO_STATE_OFF;
+ Bitrate = 0;
+ Platform = 0x00;
+ MCU = 0x00;
+ isDetected = false;
+ Firmware_OK = false;
+ Maj_Version = 0;
+ Min_Version = 0;
+
+ Last_ID = 0;
+ First_TX = null;
+ Reconnect_W = RECONNECT_WAIT;
+
+ R_Frequency = 0;
+ R_Bandwidth = 0;
+ R_TXPower = 0;
+ R_SF = 0;
+ R_CR = 0;
+ R_State = KISS.RADIO_STATE_OFF;
+ R_Lock = 0;
+ R_Stat_RX = 0;
+ R_Stat_TX = 0;
+ R_Stat_RSSI = 0;
+ R_Random = 0;
+
+ Packet_Queue.Clear();
+ FlowControl = _flow_control;
+ Interface_Ready = false;
+ Announce_Rate_Target = 0;
+
+ isValidConfig = true;
+ if(Frequency < FREQ_MIN || Frequency > FREQ_MAX)
+ {
+ isValidConfig = false;
+ Console.WriteLine("Invalid frequency configured for " + name);
+ }
+ if (TXPower<0 ||TXPower>17)
+ {
+ isValidConfig = false;
+ Console.WriteLine("Invalid TX power configured for " + name);
+ }
+ if (Bandwidth < 7800 || Bandwidth > 500000)
+ {
+ isValidConfig = false;
+ Console.WriteLine("Invalid bandwidth configured for " + name);
+ }
+ if (SF< 7 || SF>12)
+ {
+ isValidConfig = false;
+ Console.WriteLine("Invalid spreading factor configured for " + name);
+ }
+ if (CR<5 || CR>8)
+ {
+ isValidConfig = false;
+ Console.WriteLine("Invalid coding rate configured for " + name);
+ }
+ if (_id_interval > 0 && _id_callsign != "")
+ {
+ if (ID_Callsign.Length <= CALLSIGN_MAX_LEN)
+ {
+ ID_Callsign = _id_callsign;
+ ID_Interval = _id_interval;
+ should_ID = true;
+ }
+ else
+ {
+ Console.WriteLine("The ID callsign for " + Name + " exceeds the max length of " + CALLSIGN_MAX_LEN.ToString());
+ isValidConfig = false;
+ }
+ }
+ else
+ {
+ ID_Interval = 0;
+ ID_Callsign = "";
+ }
+
+ if (!isValidConfig)
+ {
+ throw new ArgumentException("The configuration for " + name + " contains errors; interface offline");
+ }
+
+ try
+ {
+ Port.Open();
+ if (Port.IsOpen)
+ {
+ Configure_Device();
+ }
+ else
+ {
+ throw new IOException("Could not open serial port " + Port.PortName);
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("Could not open serial port for interface " + Name);
+ Console.WriteLine("Contained exception was: "+e.ToString());
+ Console.WriteLine("Will attempt to bring this interface up periodically");
+ if(!isDetached && !isReconnecting)
+ {
+ Thread Reconnect = new Thread(Reconnect_Port);
+ Reconnect.Start();
+ }
+ }
+
+
+
+ }
+ ///
+ /// Built-In Test. Currently depreciated.
+ ///
+ /// True if BIT passed, false if failed.
+ //public bool BIT()
+ //{
+ // Console.WriteLine("Beginning Built-In Test");
+ // Console.WriteLine("Detecting serial ports:");
+ // DetectRadio();
+ // return true;
+ //}
+
+ //public bool InitializeSerial(string TargetPort)
+ //{
+ // CloseRadio();
+ // ConfigureSerialPort(ref Port, TargetPort);
+ // Port.Open();
+ // return true;
+ //}
+
+ //public void Open_Port()
+ //{
+ // Console.WriteLine("Opening serial port " + Port.PortName);
+ // Port.BaudRate = SerialSpeed;
+ // Port.DataBits = SerialDataBits;
+ // Port.Parity = SerialParity;
+ // Port.StopBits = SerialStopBits;
+ // Port.RtsEnable = false;
+ // Port.ReadTimeout = 250;
+ // Port.WriteTimeout = 250;
+ //}
+
+ //void ConfigureRadio()
+ //{
+
+ //}
+
+ ///
+ /// Sends frequency change request to radio. Uses Frequency variable.
+ ///
+ /// Thrown if transmission error.
+ void setFrequency()
+ {
+ byte c1 = (byte)(Frequency >> 24);
+ byte c2 = (byte)((byte)(Frequency >> 16) & 0xFF);
+ byte c3 = (byte)((byte)(Frequency >> 8) & 0xFF);
+ byte c4 = (byte)((byte)(Frequency) & 0xFF);
+ List data = new List() { c1, c2, c3, c4 };
+ data = KISS.Escape(data);
+ byte[] Command = new byte[data.Count + 3];
+ for (int i = 2; i < data.Count; i++)
+ {
+ Command[i] = (byte)data[i - 2];
+ }
+ Command[0] = KISS.FEND;
+ Command[Command.Length - 1] = KISS.FEND;
+ Command[1] = KISS.CMD_FREQUENCY;
+ try
+ {
+ Port.Write(Command, 0, Command.Length);
+ }
+ catch
+ {
+ throw new IOException("An IO error occurred while configuring frequency for " + Port.PortName);
+ }
+
+ }
+ ///
+ /// Sends bandwidth change request to radio. Uses Bandwidth variable.
+ ///
+ /// Thrown if transmission error.
+ void setBandwidth()
+ {
+
+ byte c1 = (byte)(Bandwidth >> 24);
+ byte c2 = (byte)((byte)(Bandwidth >> 16) & 0xFF);
+ byte c3 = (byte)((byte)(Bandwidth >> 8) & 0xFF);
+ byte c4 = (byte)((byte)(Bandwidth) & 0xFF);
+ List data = new List() { c1, c2, c3, c4 };
+ data = KISS.Escape(data);
+ byte[] Command = new byte[data.Count + 3];
+ for (int i = 2; i < data.Count; i++)
+ {
+ Command[i] = (byte)data[i - 2];
+ }
+ Command[0] = KISS.FEND;
+ Command[Command.Length - 1] = KISS.FEND;
+ Command[1] = KISS.CMD_BANDWIDTH;
+ try
+ {
+ Port.Write(Command, 0, Command.Length);
+ }
+ catch
+ {
+ throw new IOException("An IO error occurred while configuring bandwidth for " + Port.PortName);
+ }
+ }
+ ///
+ /// Sends transmission power change request to radio. Uses TXPower variable.
+ ///
+ /// Thrown if transmission error.
+ void setTXPower()
+ {
+ byte[] Command = new byte[] { KISS.FEND, KISS.CMD_TXPOWER, TXPower, KISS.FEND };
+ try
+ {
+ Port.Write(Command, 0, Command.Length);
+ }
+ catch
+ {
+ throw new IOException("An IO error occurred while configuring TX power for " + Port.PortName);
+ }
+ }
+
+ ///
+ /// Sends spreading factor change request to radio. Uses SF variable.
+ ///
+ /// Thrown if transmission error.
+ void setSpreadingFactor()
+ {
+ byte[] Command = new byte[] { KISS.FEND, KISS.CMD_SF, SF, KISS.FEND };
+ try
+ {
+ Port.Write(Command, 0, Command.Length);
+ }
+ catch
+ {
+ throw new IOException("An IO error occurred while configuring spreading factor for " + Port.PortName);
+ }
+ }
+
+ ///
+ /// Sends coding rate change request to radio. Uses CR variable.
+ ///
+ /// Thrown if transmission error.
+ void setCodingRate()
+ {
+ byte[] Command = new byte[] { KISS.FEND, KISS.CMD_CR, CR, KISS.FEND };
+ try
+ {
+ Port.Write(Command, 0, Command.Length);
+ }
+ catch
+ {
+ throw new IOException("An IO error occurred while configuring coding rate for " + Port.PortName);
+ }
+ }
+
+ ///
+ /// Sets State variable and send radio state change command.
+ ///
+ /// Target radio state
+ /// Thrown if a transmission error occurs
+ void setRadioState(byte TargetState)
+ {
+ State = TargetState;
+ byte[] Command = new byte[] { KISS.FEND, KISS.CMD_RADIO_STATE, TargetState, KISS.FEND };
+ try
+ {
+ Port.Write(Command, 0, Command.Length);
+ }
+ catch
+ {
+ throw new IOException("An IO error occurred while configuring radio state for " + Port.PortName);
+ }
+ }
+
+ ///
+ /// Sweeps serial ports looking for a radio. Non-API. Depreciated.
+ ///
+ /// Null string
+ //string DetectRadio()
+ //{
+ // CloseRadio();
+ // string[] DetectedPorts = SerialPort.GetPortNames();
+ // SerialPort TestPort = new SerialPort();
+ // if (DetectedPorts == null || DetectedPorts.Length == 0) {
+ // Console.WriteLine("No serial ports detected");
+ // return null;
+ // }
+ // foreach (string DetectedPort in DetectedPorts)
+ // {
+ // try
+ // {
+ // Console.WriteLine(DetectedPort);
+ // ConfigureSerialPort(ref TestPort, DetectedPort);
+ // TestPort.Open();
+ // Console.WriteLine("Port " + DetectedPort + " opened.");
+ // Console.WriteLine("Interrogating:");
+ // SendDetectCommand(TestPort);
+ // ReceiveTestResponse(TestPort);
+ // TestPort.Close();
+ // Console.WriteLine(DetectedPort + " closed.");
+ // }
+ // catch
+ // {
+ // Console.WriteLine(DetectedPort + " not an RNode or non-functional");
+ // }
+
+ // }
+ // return null;
+ //}
+
+ ///
+ /// Sends a detect request to the RNode. Non-API. Depreciated.
+ ///
+ ///
+ /// True if command sent successfully. False otherwise.
+ /// Thrown if serial communication fails.
+ bool SendDetectCommand(SerialPort Ser)
+ {
+ byte[] Command = { KISS.FEND, KISS.CMD_DETECT, KISS.DETECT_REQ, KISS.FEND, KISS.CMD_FW_VERSION, 0x00, KISS.FEND, KISS.CMD_PLATFORM, 0x00, KISS.FEND, KISS.CMD_MCU, 0x00, KISS.FEND };
+ try
+ {
+ Ser.Write(Command, 0, Command.Length);
+ return true;
+ }
+ catch
+ {
+ throw new IOException("An IO error occurred while detecting hardware for " + Port.PortName.ToString());
+ return false;
+ }
+ }
+
+ ///
+ /// Simple hook for manual packet transmission. Non-API.
+ ///
+ /// Raw message in an array of bytes.
+ public void Send(byte[] data)
+ {
+ Process_Outgoing(data);
+ }
+
+ //void SendFrequencyTest(SerialPort Ser)
+ //{
+ // byte[] Command = { KISS.FEND, KISS.CMD_FREQUENCY, 0x36, 0x89, 0xCA, 0xDB, 0xDC, KISS.FEND };
+ // Console.Write("Frequency: Sending ");
+ // foreach (byte b in Command)
+ // {
+ // Console.Write(b.ToString("X") + " ");
+ // }
+ // Console.WriteLine();
+
+ //}
+
+ ///
+ /// Leave radio
+ ///
+ /// Thrown on serial error
+ void Leave()
+ {
+ byte[] Command = new byte[] { KISS.FEND, KISS.CMD_LEAVE, 0xFF, KISS.FEND };
+ try
+ {
+ Port.Write(Command, 0, Command.Length);
+ }
+ catch
+ {
+ throw new IOException("An IO error occurred while sending host left command to device");
+
+ }
+ }
+
+ ///
+ /// Hard resets the RNode, then waits for 225ms
+ ///
+ /// Thrown on serial error
+ void Hard_Reset()
+ {
+ byte[] Command = new byte[] { KISS.FEND, KISS.CMD_RESET, 0xF8, KISS.FEND };
+ try
+ {
+ Port.Write(Command, 0, Command.Length);
+ }
+ catch
+ {
+ throw new IOException("An IO error occurred while restarting device");
+ }
+ Thread.Sleep(225);
+ }
+
+ ///
+ /// Disables RNode backlight. Non-API, but planned for next Python release. Will be brought into line after release.
+ ///
+ /// Thrown on serial error
+ public void DisableBacklight()
+ {
+
+ byte[] Command = { KISS.FEND, 0x45, 0x00, KISS.FEND };
+ if (!isOnline) return;
+ try
+ {
+ Port.Write(Command, 0, Command.Length);
+ }
+ catch
+ {
+ throw new IOException("Cannot disable backlight on " + Port.PortName.ToString());
+ }
+ }
+
+ ///
+ /// Enables RNode backlight. Non-API, but planned for next Python release. Will be brought into line after release.
+ ///
+ /// Thrown on serial error
+ public void EnableBacklight()
+ {
+
+ byte[] Command = { KISS.FEND, 0x45, 0x01, KISS.FEND };
+ if (!isOnline) return;
+ try
+ {
+ Port.Write(Command, 0, Command.Length);
+ }
+ catch
+ {
+ throw new IOException("Cannot enable backlight on " + Port.PortName.ToString());
+ }
+ }
+
+ //void ReceiveRawResponse(SerialPort Ser)
+ //{
+ // Console.WriteLine("Begin raw receive");
+ // bool isInFrame = false;
+ // List Buffer = new List();
+ // DateTime TimeToFail = DateTime.UtcNow;
+ // TimeToFail = TimeToFail.AddSeconds(2);
+ // while (Ser.BytesToRead == 0 && TimeToFail > DateTime.UtcNow)
+ // {
+ // System.Threading.Thread.Sleep(100);
+ // }
+ // while (Ser.BytesToRead > 0)
+ // {
+ // byte b = (byte)Ser.ReadByte();
+ // Console.Write(b.ToString("X") + " ");
+
+ // }
+ // Console.WriteLine();
+ // Console.WriteLine("End raw receive");
+ //}
+
+ //void ReceiveTestResponse(SerialPort Ser)
+ //{
+ // bool isInFrame = false;
+ // List Buffer = new List();
+ // DateTime TimeToFail = DateTime.UtcNow;
+ // TimeToFail = TimeToFail.AddSeconds(2);
+ // while (Ser.BytesToRead == 0 && TimeToFail > DateTime.UtcNow)
+ // {
+ // System.Threading.Thread.Sleep(100);
+ // }
+ // while (Ser.BytesToRead > 0)
+ // {
+ // byte b = (byte)Ser.ReadByte();
+ // if (!isInFrame && b == 0xc0)
+ // {
+ // isInFrame = true;
+ // }
+ // else if (isInFrame && b == 0xc0)
+ // {
+ // isInFrame = false;
+ // }
+ // else if (!isInFrame)
+ // {
+ // Console.WriteLine("Error: Byte received out of frame");
+ // }
+ // else if (isInFrame)
+ // {
+ // if (b == 0x08)
+ // {
+ // Console.Write("Detect sent: ");
+ // if (Ser.ReadByte() == 0x46)
+ // {
+ // Console.WriteLine("Proper response received.");
+ // }
+ // else
+ // {
+ // Console.WriteLine("Inappropriate response received.");
+ // }
+
+ // }
+
+ // if (b == 0x50)
+ // {
+ // try
+ // {
+ // Console.WriteLine("Firmware version: " + Ser.ReadByte().ToString() + "." + Ser.ReadByte().ToString());
+ // }
+ // catch
+ // {
+ // Console.WriteLine("Error reading firmware version");
+ // }
+ // }
+
+ // if (b == 0x48)
+ // {
+ // Console.Write("Platform: ");
+ // switch (Ser.ReadByte())
+ // {
+ // case 0x80:
+ // Console.WriteLine("ESP32");
+ // break;
+ // case 0x90:
+ // Console.WriteLine("AVR");
+ // break;
+ // default:
+ // Console.WriteLine("Undefined");
+ // break;
+ // }
+ // }
+
+ // if (b == 0x49)
+ // {
+ // Console.Write("MCU: ");
+ // switch (Ser.ReadByte())
+ // {
+ // case 0x81:
+ // Console.WriteLine("ESP32");
+ // break;
+ // case 0x91:
+ // Console.WriteLine("1284P");
+ // break;
+ // case 0x92:
+ // Console.WriteLine("2560");
+ // break;
+ // default:
+ // Console.WriteLine("Undefined");
+ // break;
+ // }
+ // }
+
+ // //Console.Write(b);
+ // //Console.Write(" ");
+ // }
+ // }
+ // Console.WriteLine();
+ //}
+
+ ///
+ /// Sends packet data to callback event
+ ///
+ /// Raw packet data, in a list of bytes
+ void ProcessIncoming(List Payload)
+ {
+ RXB += Payload.Count;
+ //if (DebugOutput)
+ //{
+ // Console.WriteLine("Data received:");
+ // foreach (byte b in Payload)
+ // {
+ // Console.Write((char)b);
+ // }
+ // Console.WriteLine("");
+ // Console.WriteLine("RSSI: " + R_Stat_RSSI.ToString());
+ // Console.WriteLine("SNR: " + R_Stat_SNR.ToString());
+ //}
+ Callbacks.Process_Inbound(Payload.ToArray(), this);
+ R_Stat_RSSI = 0;
+ R_Stat_SNR = 0;
+ }
+
+ //void ConfigureSerialPort(ref SerialPort PortOfInterest, string TargetPortName)
+ //{
+ // PortOfInterest = new SerialPort(TargetPortName, SerialSpeed, Parity.None, SerialDataBits, SerialStopBits);
+ //}
+
+ ///
+ /// Calculates bitrate based on radio configuration. Uses radio response data, not configuration data
+ ///
+ void UpdateBitrate()
+ {
+ try
+ {
+ Bitrate = (int)(R_SF * 1000 * (4.0f / R_CR) / (MathF.Pow(2, R_SF) / (R_Bandwidth / 1000)));
+ if (Bitrate <= 0)
+ {
+ Bitrate = 0;
+ return;
+ }
+
+ Bitrate_kbps = MathF.Round(Bitrate / 1000.0f, 2);
+ Console.WriteLine("On air bitrate is now " + Bitrate_kbps.ToString() + " kbps");
+ }
+ catch
+ {
+ Bitrate = 0;
+ }
+ }
+
+ ///
+ /// Starts and monitors a thread to attempt automatic reconnection of a disconnected RNode
+ ///
+ void Reconnect_Port()
+ {
+ isReconnecting = true;
+ while (!isOnline && !isDetached)
+ {
+ try
+ {
+ Thread.Sleep(5000);
+ Console.WriteLine("Attempting to reconnect serial port " + Port.PortName + " for " + Name);
+ Port.Open();
+ if (Port.IsOpen)
+ {
+ Configure_Device();
+ }
+ }
+
+ catch (Exception ex)
+ {
+ Console.WriteLine("Error while reconnecting port. The contained exception was: "+ex.Message);
+ }
+ }
+ isReconnecting = false;
+ if (isOnline)
+ {
+ Console.WriteLine("Reconnected port "+Port.PortName+" for "+Name);
+ }
+
+
+ }
+
+
+ ///
+ /// Closes the radio port. Non-API. Pending depreciation
+ ///
+ public void CloseRadio()
+ {
+ if (Port == null) { return; }
+ if (Port.IsOpen)
+ {
+ Port.Close();
+ }
+ }
+
+ ///
+ /// Add message to transmit queue
+ ///
+ /// Message data in an array of bytes
+ public void Queue(byte[] Payload)
+ {
+ Packet_Queue.Add(Payload);
+ }
+
+ //public void Force_Queue()
+ //{
+ // Process_Queue();
+ //}
+
+ ///
+ /// Attemps to send configuration data to the radio. Aborts configuration if radio does not return the configuration.
+ ///
+ public void Configure_Device()
+ {
+ R_Frequency = 0;
+ R_Bandwidth = 0;
+ R_TXPower = 0;
+ R_SF = 0;
+ R_CR = 0;
+ R_State = 0;
+ R_Lock = 0;
+
+ Port.BaudRate = SerialSpeed;
+ Port.DataBits = SerialDataBits;
+ Port.Parity = SerialParity;
+ Port.StopBits = SerialStopBits;
+ Port.RtsEnable = false;
+ Port.ReadTimeout = 250;
+ Port.WriteTimeout = 250;
+
+ Thread.Sleep(2000);
+
+ ReceiveThread = new Thread(ReceiveLoop);
+ ReceiveThread.Start();
+
+ Detect();
+ Thread.Sleep(200);
+
+ if (!isDetected)
+ {
+ Console.WriteLine("Could not detect device for " + Port.PortName);
+ Port.Close();
+ return;
+ }
+ else
+ {
+ if(Platform == KISS.PLATFORM_ESP32) { hasDisplay = true; }
+ }
+ Console.WriteLine("Serial port " + Port.PortName + " is now open");
+ Console.WriteLine("Configuring RNode interface...");
+ InitRadio();
+ if (ValidateRadioState())
+ {
+ Interface_Ready = true;
+ Console.WriteLine(Name+" is configured and powered up");
+ Thread.Sleep(300);
+ isOnline = true;
+ }
+ else
+ {
+ Console.WriteLine("After configuring " + Name + " the reported radio parameters did not match your configuration.");
+ Console.WriteLine("Make sure that your hardware actually supports the parameters specified in the configuration");
+ Console.WriteLine("Aborting RNode startup");
+ Port.Close();
+ return;
+ }
+ }
+
+ //public void Send_Plaintext(string Payload)
+ //{
+
+ // Queue(System.Text.Encoding.ASCII.GetBytes(Payload));
+
+ //}
+
+ ///
+ /// Pop a packey from the queue, send it to Process_Outgoing, and mark the interface ready.
+ ///
+ void Process_Queue()
+ {
+ if (Packet_Queue.Count > 0)
+ {
+ byte[] data = Packet_Queue[0];
+ Packet_Queue.RemoveAt(0);
+ Interface_Ready = true;
+ Process_Outgoing(data);
+ }
+ else if (Packet_Queue.Count == 0)
+ {
+ Interface_Ready = true;
+ }
+ }
+
+
+ ///
+ /// Applies FlowControl, processes ID_Callsign transmissions, escapes the data, and writes the packet to the serial port. If the interface is not ready, it re-queues it.
+ ///
+ ///
+ ///
+ void Process_Outgoing(byte[] Payload)
+ {
+ int DataLen = Payload.Length;
+ if (isOnline)
+ {
+ //Console.WriteLine("Online!");
+ if (Interface_Ready)
+ {
+ if (FlowControl)
+ {
+ Interface_Ready = false;
+ }
+ if (Payload.SequenceEqual(UTF8_to_Bytes(ID_Callsign)))
+ {
+ First_TX = null;
+ }
+ else
+ {
+ if (First_TX == null)
+ {
+ First_TX = DateTime.UtcNow;
+ }
+ }
+ int datalen = Payload.Length;
+ Payload = KISS.Escape(Payload.ToList()).ToArray();
+ byte[] Output = new byte[Payload.Length + 3];
+ Payload.CopyTo(Output, 2);
+ Output[0] = 0xC0;
+ Output[Output.Length - 1] = 0xC0;
+ try
+ {
+ Port.Write(Output, 0, Output.Length);
+ TXB += datalen;
+ }
+ catch
+ {
+ throw new IOException("Process_Outgoing has failed.");
+ }
+
+ }
+ else
+ {
+ Queue(Payload);
+ }
+ }
+ }
+
+ ///
+ /// Main receive loop. Processes incoming control and data packets, writing to registers or passing off to an event handler as appropriate
+ ///
+ /// Thrown on serial error
+ public void ReceiveLoop()
+ {
+ Console.WriteLine("ReceiveLoop running");
+ if (Port == null || !Port.IsOpen)
+ {
+ Console.WriteLine("Port is not properly initialized");
+ return;
+ }
+ bool isInFrame = false;
+ bool Escape = false;
+ byte Command = KISS.CMD_UNKNOWN;
+ byte Buffer;
+ List DataBuffer = new List();
+ List CommandBuffer = new List();
+ DateTime Last_Read = DateTime.UtcNow;
+ while (Port.IsOpen)
+ {
+ if (Port.BytesToRead > 0)
+ {
+ Buffer = (byte)Port.ReadByte();
+ if (isInFrame && Buffer == KISS.FEND && Command == KISS.CMD_DATA)
+ {
+ isInFrame = false;
+ ProcessIncoming(DataBuffer);
+ DataBuffer.Clear();
+ CommandBuffer.Clear();
+ }
+ else if (Buffer == KISS.FEND)
+ {
+ isInFrame = true;
+ Command = KISS.CMD_UNKNOWN;
+ DataBuffer.Clear();
+ CommandBuffer.Clear();
+ }
+ else if (isInFrame && DataBuffer.Count < HW_MTU)
+ {
+ if (DataBuffer.Count == 0 && Command == KISS.CMD_UNKNOWN) { Command = Buffer; }
+ else if (Command == KISS.CMD_DATA)
+ {
+ if (Buffer == KISS.FESC) { Escape = true; }
+ else
+ {
+ if (Escape)
+ {
+ if (Buffer == KISS.TFEND) { Buffer = KISS.FEND; }
+ if (Buffer == KISS.TFESC) { Buffer = KISS.FESC; }
+ Escape = false;
+ }
+ DataBuffer.Add(Buffer);
+ }
+ }
+ else if (Command == KISS.CMD_FREQUENCY)
+ {
+ if (Buffer == KISS.FESC) { Escape = true; }
+ else
+ {
+ if (Escape)
+ {
+ if (Buffer == KISS.TFEND) { Buffer = KISS.FEND; }
+ if (Buffer == KISS.TFESC) { Buffer = KISS.FESC; }
+ Escape = false;
+ }
+ CommandBuffer.Add(Buffer);
+ if (CommandBuffer.Count == 4)
+ {
+ R_Frequency = CommandBuffer[0] << 24 | CommandBuffer[1] << 16 | CommandBuffer[2] << 8 | CommandBuffer[3];
+ Console.WriteLine("Radio reporting frequency is " + (R_Frequency / 1000000.0).ToString() + "MHz");
+ UpdateBitrate();
+ }
+ }
+ }
+ else if (Command == KISS.CMD_BANDWIDTH)
+ {
+ if (Buffer == KISS.FESC) { Escape = true; }
+ else
+ {
+ if (Escape)
+ {
+ if (Buffer == KISS.TFEND) { Buffer = KISS.FEND; }
+ if (Buffer == KISS.TFESC) { Buffer = KISS.FESC; }
+ Escape = false;
+ }
+ CommandBuffer.Add(Buffer);
+ if (CommandBuffer.Count == 4)
+ {
+ R_Bandwidth = CommandBuffer[0] << 24 | CommandBuffer[1] << 16 | CommandBuffer[2] << 8 | CommandBuffer[3];
+ Console.WriteLine("Radio reporting bandwidth is " + (R_Bandwidth / 1000.0).ToString() + "KHz");
+ UpdateBitrate();
+ }
+ }
+ }
+ else if (Command == KISS.CMD_TXPOWER)
+ {
+ R_TXPower = Buffer;
+ Console.WriteLine("Radio reporting TX power is " + R_TXPower.ToString() + " dBm");
+ }
+ else if (Command == KISS.CMD_SF)
+ {
+ R_SF = Buffer;
+ Console.WriteLine("Radio reporting spreading factor is " + R_SF.ToString());
+ }
+ else if (Command == KISS.CMD_CR)
+ {
+ R_CR = Buffer;
+ Console.WriteLine("Radio reporting coding rate is " + R_CR.ToString());
+ }
+ else if (Command == KISS.CMD_RADIO_STATE)
+ {
+ R_State = Buffer;
+ if (R_State == 0) {
+ Console.WriteLine(Name + ": Radio reporting state is offline.");
+ }
+ }
+ else if (Command == KISS.CMD_RADIO_LOCK)
+ {
+ R_Lock = Buffer;
+ }
+ else if (Command == KISS.CMD_FW_VERSION)
+ {
+ if (Buffer == KISS.FESC) { Escape = true; }
+ else
+ {
+ if (Escape)
+ {
+ if (Buffer == KISS.TFEND) { Buffer = KISS.FEND; }
+ if (Buffer == KISS.TFESC) { Buffer = KISS.FESC; }
+ Escape = false;
+ }
+ CommandBuffer.Add(Buffer);
+ if (CommandBuffer.Count == 2)
+ {
+ Maj_Version = (int)CommandBuffer[0];
+ Min_Version = (int)CommandBuffer[1];
+ Validate_Firmware();
+ }
+
+ }
+ }
+ else if (Command == KISS.CMD_STAT_RX)
+ {
+ if (Buffer == KISS.FESC) { Escape = true; }
+ else
+ {
+ if (Escape)
+ {
+ if (Buffer == KISS.TFEND) { Buffer = KISS.FEND; }
+ if (Buffer == KISS.TFESC) { Buffer = KISS.FESC; }
+ Escape = false;
+ }
+ CommandBuffer.Add(Buffer);
+ if (CommandBuffer.Count == 4)
+ {
+ R_Stat_RX = CommandBuffer[0] << 24 | CommandBuffer[1] << 16 | CommandBuffer[2] << 8 | CommandBuffer[3];
+ }
+ }
+ }
+ else if (Command == KISS.CMD_STAT_TX)
+ {
+ if (Buffer == KISS.FESC) { Escape = true; }
+ else
+ {
+ if (Escape)
+ {
+ if (Buffer == KISS.TFEND) { Buffer = KISS.FEND; }
+ if (Buffer == KISS.TFESC) { Buffer = KISS.FESC; }
+ Escape = false;
+ }
+ CommandBuffer.Add(Buffer);
+ if (CommandBuffer.Count == 4)
+ {
+ R_Stat_TX = CommandBuffer[0] << 24 | CommandBuffer[1] << 16 | CommandBuffer[2] << 8 | CommandBuffer[3];
+ }
+ }
+ }
+ else if (Command == KISS.CMD_STAT_RSSI)
+ {
+ R_Stat_RSSI = Buffer - RSSI_Offset;
+ }
+ else if (Command == KISS.CMD_STAT_SNR)
+ {
+ R_Stat_SNR = (float)(sbyte)Buffer / 4.0f;
+ }
+ else if (Command == KISS.CMD_RANDOM)
+ {
+ R_Random = Buffer;
+ }
+ else if (Command == KISS.CMD_PLATFORM)
+ {
+ Platform = Buffer;
+ }
+ else if (Command == KISS.CMD_MCU)
+ {
+ MCU = Buffer;
+ }
+ else if (Command == KISS.CMD_ERROR)
+ {
+ if (Buffer == KISS.ERROR_INITRADIO)
+ {
+ throw new IOException("Radio initialization failure");
+ }
+ else if (Buffer == KISS.ERROR_TXFAILED)
+ {
+ throw new IOException("Hardware transmit failure");
+ }
+ else
+ {
+ throw new IOException("Unknown hardware failure");
+ }
+ }
+ else if (Command == KISS.CMD_RESET)
+ {
+ if (Buffer == 0xF8 && Platform == KISS.PLATFORM_ESP32 && isOnline)
+ {
+ throw new IOException("ESP32 reset while device online");
+ }
+ }
+ else if (Command == KISS.CMD_READY)
+ {
+ Console.WriteLine("Received CMD_READY");
+ Process_Queue();
+ }
+ else if (Command == KISS.CMD_DETECT)
+ {
+ if (Buffer == KISS.DETECT_RESP)
+ {
+ isDetected = true;
+ }
+ else
+ {
+ isDetected = false;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (DataBuffer.Count > 0 && DateTime.UtcNow > Last_Read.AddMilliseconds(Timeout))
+ {
+ Console.WriteLine("Serial Read Timeout");
+ DataBuffer.Clear();
+ isInFrame = false;
+ Command = KISS.CMD_UNKNOWN;
+ Escape = false;
+ }
+
+ if (ID_Interval > 0 && ID_Callsign != null)
+ {
+ if(First_TX != null)
+ {
+ if (DateTime.UtcNow > First_TX.Value.AddSeconds(ID_Interval))
+ {
+ Console.WriteLine("Interface " + Name + " is transmitting beacon data: " + ID_Callsign.ToString());
+ Process_Outgoing(UTF8_to_Bytes(ID_Callsign));
+ }
+ }
+ }
+
+ System.Threading.Thread.Sleep(80);
+ }
+ }
+ Console.WriteLine("Receive loop terminated.");
+
+ }
+
+ ///
+ /// Utility function. Non-API. Takes C# string in UTF-8 format and converts it to an array of bytes
+ ///
+ /// Data string in UTF-8
+ /// Byte array representation of data string
+
+ byte[] UTF8_to_Bytes(string Input)
+ {
+ return System.Text.Encoding.UTF8.GetBytes(Input);
+ }
+
+ ///
+ /// Detects the RNode and its firmware version
+ ///
+ /// Thrown on serial error
+ public void Detect()
+ {
+ //isDetected = SendDetectCommand(Port);
+ byte[] Command = { KISS.FEND, KISS.CMD_DETECT, KISS.DETECT_REQ, KISS.FEND, KISS.CMD_FW_VERSION, 0x00, KISS.FEND, KISS.CMD_PLATFORM, 0x00, KISS.FEND, KISS.CMD_MCU, 0x00, KISS.FEND };
+ try
+ {
+ Port.Write(Command, 0, Command.Length);
+ }
+ catch
+ {
+ throw new IOException("An IO error occurred while detecting hardware for " + Port.PortName.ToString());
+ }
+ }
+
+ ///
+ /// Detects if RNode firmware version is above listed requirements
+ ///
+ void Validate_Firmware()
+ {
+ if(Maj_Version>=REQUIRED_FW_VER_MAJ && Min_Version >= REQUIRED_FW_VER_MIN)
+ {
+ Firmware_OK = true;
+ return;
+ }
+
+ Console.WriteLine("The firmware version of the connected RNode is "+Maj_Version.ToString()+ "."+Min_Version.ToString());
+ Console.WriteLine("This library requires at least version " + Maj_Version.ToString() + "." + Min_Version.ToString());
+ Console.WriteLine("Please update your RNode firmware with rnodeconf from https://github.com/markqvist/rnodeconfigutil/");
+ //ToDo RNS.Panic();
+ }
+
+ ///
+ /// Detects if the radio's reported state matches, within tolerance, the desired configuration. Frequency is the only variable with a tolerance: +/- 500 Hz. Sets isValidConfig as well as returns a boolean
+ ///
+ /// True if state is valid, else false
+ public bool ValidateRadioState()
+ {
+ isValidConfig = true;
+ Console.WriteLine("Waiting for config validation");
+ System.Threading.Thread.Sleep(250);
+ if (R_Frequency > 0 && Math.Abs(R_Frequency - Frequency) > 500)
+ {
+ isValidConfig = false;
+ Console.WriteLine("Frequency mismatch");
+ Console.WriteLine("Expected " + Frequency.ToString() + " - got " + R_Frequency.ToString());
+ }
+ if (R_Bandwidth != Bandwidth)
+ {
+ isValidConfig = false;
+ Console.WriteLine("Bandwidth mismatch");
+ Console.WriteLine("Expected " + Bandwidth.ToString() + " - got " + R_Bandwidth.ToString());
+ }
+ if (R_TXPower != TXPower)
+ {
+ isValidConfig = false;
+ Console.WriteLine("TX power mismatch");
+ Console.WriteLine("Expected " + TXPower.ToString() + " - got " + R_TXPower.ToString());
+ }
+ if (R_SF != SF)
+ {
+ isValidConfig = false;
+ Console.WriteLine("Spreading factor mismatch");
+ Console.WriteLine("Expected " + SF.ToString() + " - got " + R_SF.ToString());
+ }
+ if (R_State != State)
+ {
+ isValidConfig = false;
+ Console.WriteLine("Radio state mismatch");
+ }
+ return isValidConfig;
+ }
+
+ ///
+ /// Sends radio initialization commands
+ ///
+ public void InitRadio()
+ {
+ setFrequency();
+ setBandwidth();
+ setTXPower();
+ setSpreadingFactor();
+ setCodingRate();
+ setRadioState(KISS.RADIO_STATE_ON);
+ }
+
+ //public void DefineRatio(uint CenterFrequency, uint BW, byte Code_Rade, byte SpreadFactor, byte TX_Power)
+ //{
+ // Frequency = CenterFrequency;
+ // Bandwidth = BW;
+ // SF = SpreadFactor;
+ // CR = Code_Rade;
+ // TXPower = TX_Power;
+
+ //}
+
+ ///
+ /// Detaches radio
+ ///
+ public void Detach()
+ {
+ isDetached = true;
+ Disable_External_Framebuffer();
+ setRadioState(KISS.RADIO_STATE_OFF);
+ Leave();
+ }
+
+ ///
+ /// Disables the external framebuffer, returnig the RNode display graphic to internal control
+ ///
+ /// Thrown on serial error
+
+ public void Disable_External_Framebuffer()
+ {
+ if (hasDisplay && isOnline)
+ {
+ byte[] Command = new byte[] { KISS.FEND, KISS.CMD_FB_EXT, 0x00, KISS.FEND };
+ try
+ {
+ Port.Write(Command, 0, Command.Length);
+ }
+ catch
+ {
+ throw new IOException("An IO error occurred while disabling external framebuffer on device");
+ }
+ }
+ }
+
+ ///
+ /// Enables external frame buffer, taking control of RNode display graphic
+ ///
+ /// Thrown on serial error
+ public void Enable_External_Framebuffer()
+ {
+ if (hasDisplay && isOnline)
+ {
+ byte[] Command = new byte[] { KISS.FEND, KISS.CMD_FB_EXT, 0x01, KISS.FEND };
+ try
+ {
+ Port.Write(Command, 0, Command.Length);
+ }
+ catch
+ {
+ throw new IOException("An IO error occurred while enabling external framebuffer on device");
+ }
+ }
+ }
+
+ ///
+ /// Sends image data to RNode display
+ ///
+ /// Array of bytes containing image information
+ public void Display_Image(byte[] ImageData)
+ {
+ if (hasDisplay)
+ {
+ int lines = ImageData.Length / 8;
+ for (int i = 0; i < lines; i++)
+ {
+
+ int LineStart = i * FB_Bytes_Per_Line;
+ int LineEnd = LineStart + FB_Bytes_Per_Line;
+ byte[] LineData = new byte[FB_Bytes_Per_Line];
+ Array.Copy(ImageData, LineStart, LineData, 0, FB_Bytes_Per_Line);
+ Write_Framebuffer(i, LineData);
+ }
+ }
+ }
+
+ ///
+ /// Parses image data and send to RNode for display
+ ///
+ /// Target line on RNode Display
+ /// Data for the line, in a byte array
+ ///
+ void Write_Framebuffer(int Line, byte[] LineData)
+ {
+ if (hasDisplay && isOnline)
+ {
+
+ LineData = KISS.Escape(LineData.ToList()).ToArray();
+ byte[] Command = new byte[LineData.Length + 4];
+ Array.Copy(LineData, 0, Command, 3, LineData.Length);
+ Command[2] = (byte)Line;
+ Command[0] = KISS.FEND;
+ Command[1] = KISS.CMD_FB_WRITE;
+ Command[Command.Length - 1] = KISS.FEND;
+ try
+ {
+ Port.Write(Command, 0, Command.Length);
+ }
+ catch
+ {
+ throw new IOException("An IO error occurred while writing framebuffer data");
+ }
+
+ }
+ }
+
+ class KISS
+ {
+ public const byte FEND = 0xC0;
+ public const byte FESC = 0xDB;
+ public const byte TFEND = 0xDC;
+ public const byte TFESC = 0xDD;
+ public const byte CMD_UNKNOWN = 0xFE;
+ public const byte CMD_DATA = 0x00;
+ public const byte CMD_FREQUENCY = 0x01;
+ public const byte CMD_BANDWIDTH = 0x02;
+ public const byte CMD_TXPOWER = 0x03;
+ public const byte CMD_SF = 0x04;
+ public const byte CMD_CR = 0x05;
+ public const byte CMD_RADIO_STATE = 0x06;
+ public const byte CMD_RADIO_LOCK = 0x07;
+ public const byte CMD_DETECT = 0x08;
+ public const byte CMD_LEAVE = 0x0A;
+ public const byte CMD_READY = 0x0F;
+ public const byte CMD_STAT_RX = 0x21;
+ public const byte CMD_STAT_TX = 0x22;
+ public const byte CMD_STAT_RSSI = 0x23;
+ public const byte CMD_STAT_SNR = 0x24;
+ public const byte CMD_BLINK = 0x30;
+ public const byte CMD_RANDOM = 0x40;
+ public const byte CMD_FB_EXT = 0x41;
+ public const byte CMD_FB_READ = 0x42;
+ public const byte CMD_FB_WRITE = 0x43;
+ public const byte CMD_PLATFORM = 0x48;
+ public const byte CMD_MCU = 0x49;
+ public const byte CMD_FW_VERSION = 0x50;
+ public const byte CMD_ROM_READ = 0x51;
+ public const byte CMD_RESET = 0x55;
+ public const byte DETECT_REQ = 0x73;
+ public const byte DETECT_RESP = 0x46;
+ public const byte RADIO_STATE_OFF = 0x00;
+ public const byte RADIO_STATE_ON = 0x01;
+ public const byte RADIO_STATE_ASK = 0xFF;
+ public const byte CMD_ERROR = 0x90;
+ public const byte ERROR_INITRADIO = 0x01;
+ public const byte ERROR_TXFAILED = 0x02;
+ public const byte ERROR_EEPROM_LOCKED = 0x03;
+ public const byte PLATFORM_AVR = 0x90;
+ public const byte PLATFORM_ESP32 = 0x80;
+
+
+
+
+
+
+ public static List Escape(List Input)
+ {
+ List Output = new List();
+ foreach (byte b in Input)
+ {
+ if (b == 0xDB)
+ {
+ Output.Add(0xDB);
+ Output.Add(0xDD);
+ }
+ else if (b == 0xC0)
+ {
+ Output.Add(0xDB);
+ Output.Add(0xDC);
+ }
+ else
+ {
+ Output.Add(b);
+ }
+ }
+ return Output;
+ }
+ }
+ }
+
+
+}
\ No newline at end of file