Add initial support for QC2 sending

pull/1/head
firealarmss 1 year ago
parent 113e06087b
commit 1921e342cf

@ -18,6 +18,8 @@
<Button Content="PTT" Width="80" Margin="0,5,0,0" Background="Green" Foreground="White" Name="PttButton"
HorizontalAlignment="Left" Grid.Row="3" Click="PTTButton_Click"/>
<Button Content="Page Select" Width="80" Margin="80,5,0,0" Background="Green" Foreground="White" Name="PageSelectButton"
HorizontalAlignment="Left" Grid.Row="3" Click="PageSelectButton_Click"/>
</Grid>
</Border>
</UserControl>

@ -30,12 +30,15 @@ namespace WhackerLinkConsoleV2.Controls
{
private readonly SelectedChannelsManager _selectedChannelsManager;
private bool _pttState;
private bool _pageState;
private bool _emergency;
private string _lastSrcId = "0";
public FlashingBackgroundManager _flashingBackgroundManager;
public event EventHandler<ChannelBox> PTTButtonClicked;
public event EventHandler<ChannelBox> PageButtonClicked;
public event PropertyChangedEventHandler PropertyChanged;
public string ChannelName { get; set; }
@ -64,6 +67,16 @@ namespace WhackerLinkConsoleV2.Controls
}
}
public bool PageState
{
get => _pageState;
set
{
_pageState = value;
UpdatePageColor();
}
}
public bool Emergency
{
get => _emergency;
@ -136,6 +149,16 @@ namespace WhackerLinkConsoleV2.Controls
PttButton.Background = new SolidColorBrush(Colors.Green);
}
private void UpdatePageColor()
{
if (IsEditMode) return;
if (PageState)
PageSelectButton.Background = new SolidColorBrush(Colors.Orange);
else
PageSelectButton.Background = new SolidColorBrush(Colors.Green);
}
private void UpdateBackground()
{
Background = IsSelected ? Brushes.DodgerBlue : Brushes.DarkGray;
@ -147,9 +170,15 @@ namespace WhackerLinkConsoleV2.Controls
PTTButtonClicked.Invoke(sender, this);
}
private void PageSelectButton_Click(object sender, RoutedEventArgs e)
{
PageState = !PageState;
PageButtonClicked.Invoke(sender, this);
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

@ -1,16 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/*
* WhackerLink - WhackerLinkConsoleV2
*
* 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, either 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2024 Caleb, K4PHP
*
*/
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using WhackerLinkLib.Models.Radio;
namespace WhackerLinkConsoleV2

@ -24,6 +24,7 @@
</MenuItem>
<MenuItem Header="Page">
<MenuItem Header="P25 Page" Click="P25Page_Click" />
<MenuItem Header="Manual QC2" Click="ManualPage_Click" />
</MenuItem>
<MenuItem Header="Clear Emergency" Click="ClearEmergency_Click" />
</Menu>

@ -38,6 +38,8 @@ using NAudio.Wave;
using WhackerLinkLib.Interfaces;
using WhackerLinkLib.Models.IOSP;
using Nancy;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.ProgressBar;
namespace WhackerLinkConsoleV2
{
@ -251,6 +253,7 @@ namespace WhackerLinkConsoleV2
}
channelBox.PTTButtonClicked += ChannelBox_PTTButtonClicked;
channelBox.PageButtonClicked += ChannelBox_PageButtonClicked;
channelBox.MouseLeftButtonDown += ChannelBox_MouseLeftButtonDown;
channelBox.MouseMove += ChannelBox_MouseMove;
@ -347,8 +350,23 @@ namespace WhackerLinkConsoleV2
$"Selected Output Device Index: {outputDeviceIndex}",
"Selected Devices");
_waveIn.DeviceNumber = inputDeviceIndex.Value;
_waveOut.DeviceNumber = outputDeviceIndex.Value;
try
{
_waveIn.StopRecording();
_waveIn.DeviceNumber = inputDeviceIndex.Value;
_waveIn.StartRecording();
_waveOut.Stop();
_waveOut.DeviceNumber = outputDeviceIndex.Value;
_waveOut.Init(_waveProvider);
_waveOut.Play();
MessageBox.Show("Audio devices updated successfully.", "Success");
}
catch (Exception ex)
{
MessageBox.Show($"Failed to update audio devices: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
else
{
@ -368,6 +386,59 @@ namespace WhackerLinkConsoleV2
}
}
private void ManualPage_Click(object sender, RoutedEventArgs e)
{
QuickCallPage pageWindow = new QuickCallPage();
pageWindow.Owner = this;
if (pageWindow.ShowDialog() == true)
{
foreach (ChannelBox channel in _selectedChannelsManager.GetSelectedChannels())
{
Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
IWebSocketHandler handler = _webSocketManager.GetWebSocketHandler(system.Name);
if (channel.PageState)
{
ToneGenerator generator = new ToneGenerator();
byte[] toneA = generator.GenerateTone(Double.Parse(pageWindow.ToneA), 1.0);
byte[] toneB = generator.GenerateTone(Double.Parse(pageWindow.ToneB), 3.0);
byte[] combinedAudio = new byte[toneA.Length + toneB.Length];
Buffer.BlockCopy(toneA, 0, combinedAudio, 0, toneA.Length);
Buffer.BlockCopy(toneB, 0, combinedAudio, toneA.Length, toneB.Length);
int chunkSize = 1600;
int totalChunks = (combinedAudio.Length + chunkSize - 1) / chunkSize;
Task.Factory.StartNew(() =>
{
_waveProvider.ClearBuffer();
_waveProvider.AddSamples(combinedAudio, 0, combinedAudio.Length);
});
for (int i = 0; i < totalChunks; i++)
{
int offset = i * chunkSize;
int size = Math.Min(chunkSize, combinedAudio.Length - offset);
byte[] chunk = new byte[size];
Buffer.BlockCopy(combinedAudio, offset, chunk, 0, size);
handler.SendMessage(PacketFactory.CreateVoicePacket(system.Rid, cpgChannel.Tgid, channel.VoiceChannel, chunk, system.Site));
}
handler.SendMessage(PacketFactory.CreateVoiceChannelRelease(system.Rid, cpgChannel.Tgid, channel.VoiceChannel, system.Site));
Dispatcher.Invoke(() =>
{
channel.PageSelectButton.Background = Brushes.Green;
});
}
}
}
}
private void SelectWidgets_Click(object sender, RoutedEventArgs e)
{
WidgetSelectionWindow widgetSelectionWindow = new WidgetSelectionWindow();
@ -468,6 +539,9 @@ namespace WhackerLinkConsoleV2
{
channel.Background = new SolidColorBrush(Colors.DarkCyan);
});
} else if (channel.PageState && response.Status == (int)ResponseType.GRANT && response.Channel != null && response.SrcId == system.Rid && response.DstId == cpgChannel.Tgid)
{
channel.VoiceChannel = response.Channel;
}
else
{
@ -485,6 +559,22 @@ namespace WhackerLinkConsoleV2
}
}
private void ChannelBox_PageButtonClicked(object sender, ChannelBox e)
{
Codeplug.System system = Codeplug.GetSystemForChannel(e.ChannelName);
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(e.ChannelName);
IWebSocketHandler handler = _webSocketManager.GetWebSocketHandler(system.Name);
if (e.PageState)
handler.SendMessage(PacketFactory.CreateVoiceChannelRequest(system.Rid, cpgChannel.Tgid, system.Site));
else
{
//_stopSending = true;
handler.SendMessage(PacketFactory.CreateVoiceChannelRelease(system.Rid, cpgChannel.Tgid, e.VoiceChannel, system.Site));
e.VoiceChannel = null;
}
}
private void ChannelBox_PTTButtonClicked(object sender, ChannelBox e)
{
Codeplug.System system = Codeplug.GetSystemForChannel(e.ChannelName);

@ -0,0 +1,17 @@
<Window x:Class="WhackerLinkConsoleV2.QuickCallPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WhackerLinkConsoleV2"
mc:Ignorable="d"
Title="Manual QC2 Page" Height="300" Width="300">
<Grid>
<Button Content="Send" HorizontalAlignment="Center" Margin="0,157,0,0" Name="SendButton" VerticalAlignment="Top" Click="SendButton_Click"/>
<TextBox HorizontalAlignment="Left" Margin="115,96,0,0" TextWrapping="Wrap" Text="" Name="ToneAText" VerticalAlignment="Top" Width="120"/>
<TextBox HorizontalAlignment="Left" Margin="115,123,0,0" TextWrapping="Wrap" Text="" Name="ToneBText" VerticalAlignment="Top" Width="120"/>
<Label Content="A Tone:" HorizontalAlignment="Left" Margin="66,89,0,0" VerticalAlignment="Top"/>
<Label Content="B Tone:" HorizontalAlignment="Left" Margin="67,116,0,0" VerticalAlignment="Top"/>
</Grid>
</Window>

@ -0,0 +1,47 @@
/*
* WhackerLink - WhackerLinkConsoleV2
*
* 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, either 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2024 Caleb, K4PHP
*
*/
using System.Windows;
namespace WhackerLinkConsoleV2
{
/// <summary>
/// Interaction logic for QuickCallPage.xaml
/// </summary>
public partial class QuickCallPage : Window
{
public string ToneA;
public string ToneB;
public QuickCallPage()
{
InitializeComponent();
}
private void SendButton_Click(object sender, RoutedEventArgs e)
{
ToneA = ToneAText.Text;
ToneB = ToneBText.Text;
DialogResult = true;
Close();
}
}
}

@ -0,0 +1,100 @@
/*
* WhackerLink - WhackerLinkConsoleV2
*
* 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, either 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2024 Caleb, K4PHP
*
*/
using NAudio.Wave;
namespace WhackerLinkConsoleV2
{
/// <summary>
///
/// </summary>
public class ToneGenerator
{
private readonly int _sampleRate = 8000;
private readonly int _bitsPerSample = 16;
private readonly int _channels = 1;
private WaveOutEvent _waveOut;
private BufferedWaveProvider _waveProvider;
/// <summary>
/// Creates an instance of <see cref="ToneGenerator"/>
/// </summary>
public ToneGenerator()
{
_waveOut = new WaveOutEvent();
_waveProvider = new BufferedWaveProvider(new WaveFormat(_sampleRate, _bitsPerSample, _channels));
_waveOut.Init(_waveProvider);
}
/// <summary>
/// Generate a sine wave tone at the specified frequency and duration.
/// </summary>
/// <param name="frequency">Frequency in Hz</param>
/// <param name="durationSeconds">Duration in seconds</param>
/// <returns>PCM data as a byte array</returns>
public byte[] GenerateTone(double frequency, double durationSeconds)
{
int sampleCount = (int)(_sampleRate * durationSeconds);
byte[] buffer = new byte[sampleCount * (_bitsPerSample / 8)];
for (int i = 0; i < sampleCount; i++)
{
double time = (double)i / _sampleRate;
short sampleValue = (short)(Math.Sin(2 * Math.PI * frequency * time) * short.MaxValue);
buffer[i * 2] = (byte)(sampleValue & 0xFF);
buffer[i * 2 + 1] = (byte)((sampleValue >> 8) & 0xFF);
}
return buffer;
}
/// <summary>
/// Play the generated tone through the speakers.
/// </summary>
/// <param name="frequency">Frequency in Hz</param>
/// <param name="durationSeconds">Duration in seconds</param>
public void PlayTone(double frequency, double durationSeconds)
{
byte[] toneData = GenerateTone(frequency, durationSeconds);
_waveProvider.ClearBuffer();
_waveProvider.AddSamples(toneData, 0, toneData.Length);
_waveOut.Play();
}
/// <summary>
/// Stop playback.
/// </summary>
public void StopTone()
{
_waveOut.Stop();
}
/// <summary>
/// Dispose of resources.
/// </summary>
public void Dispose()
{
_waveOut.Dispose();
}
}
}
Loading…
Cancel
Save

Powered by TurnKey Linux.