SmartSDR v3.8.20 and the SmartSDR v3.8.20 Release Notes
SmartSDR v2.12.1 and the SmartSDR v2.12.1 Release Notes
Power Genius XL Utility v3.8.9 and the Power Genius XL Release Notes v3.8.9
Tuner Genius XL Utility v1.2.11 and the Tuner Genius XL Release Notes v1.2.11
Antenna Genius Utility v4.1.8
Need technical support from FlexRadio? It's as simple as Creating a HelpDesk ticket.
.NET API "Hello World?"
Is there a "Hello World" for using the .NET API?
I've combed thru the forums and googled and found enough to cause me some trouble... I can compile but not run. I won't bore you with the details... specific issues are:
I'm building for .NET 7 with C# - ok?
What architecture should I target? I see warnings about the DLL's being built for x86 and I'm targeting "Any CPU" and the errors I get at run-time indicate that it can't find the DLL's to load them.
I am sure there are individual answers to these questions, but I'm sure I'll run into the next problem (i.e. I learned to find a radio via the API static class - who knew?)
A very brief sample project that imports the correct DLL's, builds, and, maybe, finds a radio would be very helpful!
Thanks, and 73!
-Brian n8wrl
Answers
-
Hi Brian, I put a sample out on GitHub. I think you can find it by searching GitHub for my call.
It discovers a Flex radio on the network and connects as a non-GUI client. It binds to the first GUI client it finds but allows you to bind to the other station if one is connected.
It is just a sampler of things that I tried to help me understand Flexlib and C#.
Let me know if you have any problems finding it or getting it to work.
1 -
I am getting closer, many thanks to Len and access to the source.
I can Get a "Radio" but I may be confused about what that radio represents.
I am running SmartSDR at the same time, and I want my code to manipulate what SmartSDR is doing. That is, I want to mute/unmute audio, but nothing happens, I want to get the "active slice" and do things, but the ActiveSlice property is always null.
I suspect the API is giving me my own private connection to the radio to do as I please, but what I really want is to manipulate the radio as SmartSDR sees it.
What am I missing?
73 and TIA
-Brian n8wrl
0 -
Hi Brian, I think that I had problems with the Active Slice construct as well. Was a while ago, so I kind of forget...
In all of this, I did not use Active Slice, just slice0 and slice1 (if I had a 6700, I would have coded for all eight possible slices). The concept of an active slice is used by SmartSDR so that it knows which slice to apply mouse wheel frequency changes to. So for what I do, and what I think that you will be doing, the active slice is not really important.
Did you get the program that I wrote working? It might provide some insight. You should get a screen something like this:
Note that the controls on the left always correspond to slice0 and on the right, slice1. In this case they are named A and B respectively. The A and B designators came from the API, not my code. Since I only have one GUI client connected, "KD0RC Big Laptop" shows up as the station connected and that is reflected above the frequency boxes. If I had another GUI client connected, then both would be labelled "Slice A", but would have the corresponding station name displayed. The first one connected is slice0 and the second is slice1.
In the main form code, you need to set up the API:
public frmMain() { InitializeComponent(); API.RadioAdded += API_RadioAdded; API.RadioRemoved += API_RadioRemoved; API.ProgramName = "KD0RC mini utility"; API.Init(); mainSpeechSynthesizer.SelectVoiceByHints(System.Speech.Synthesis.VoiceGender.Female); TimeIt.Start(); }
In the radio added routine, you set up the vectors to the various API events:
private void API_RadioAdded(Radio radio) { _thisRadio = radio; SerNum = radio.Serial; // The properties, such as Nickname and Callsign, can't be read // successfully until you've received a PropertyChanged event // for that property... this is done after connect. // radio.PropertyChanged += radio_PropertyChanged; radio.SliceAdded += new Radio.SliceAddedEventHandler(radio_SliceAdded); radio.SliceRemoved += new Radio.SliceRemovedEventHandler(radio_SliceRemoved); radio.VoltsDataReady += new Radio.MeterDataReadyEventHandler(_thisRadio_VoltsDataReady); radio.PATempDataReady += new Radio.MeterDataReadyEventHandler(_thisRadio_PATempDataReady); radio.GUIClientAdded += new Radio.GUIClientAddedEventHandler(_thisRadio_GUIClientAdded); radio.GUIClientUpdated += new Radio.GUIClientUpdatedEventHandler(_thisRadio_GUIClientUpdated); radio.GUIClientRemoved += new Radio.GUIClientRemovedEventHandler(_thisRadio_GUIClientRemoved); radio.Connect(); form1.txtDebug.Text += "Status: " + radio.Status + NL; form1.txtDebug.Text += _thisRadio.GuiClientHosts + NL; ParseConfig(); }
In the radio_SliceAdded routine, I add an event handler for each of the two slices if and when they are added.
private void radio_SliceAdded(Slice slice) { //txtDebug.Text = "Slice: " + slice.Index.ToString() + " " + slice.ClientHandle.ToString() + NL; if (slice.Index == 0) { slice0 = slice; slice0.PropertyChanged += new PropertyChangedEventHandler(slice0_PropertyChanged); //txtDebug.Text += slice0.ClientHandle.ToString() + NL; lblSlice0.Text = "Slice " + slice0.Letter + " "; txtFreq0.Text = slice0.Freq.ToString("0.000 000"); txtFreq0.BackColor = Color.White; <snip>
So, after all that... I now can control slices 0 and 1 independently of which is "active". I also get the same slice designator (A, B, etc.) that SmartSDR is using, even if there are two different SmartSDR instances controlling the radio (MultiFlex).
Hopefully that helps...
0 -
Thanks Len, I figured it out.
First, I have to tell the radio to Connect()
Then, I have to wait for slices to be added - monitor the event.
THEN everything is there for the mucking!!
0 -
Exactly! I'm glad that you got it sorted out.
0 -
Yup. Even after a connect(), the slices aren't there. You get around that with your call to TimeIt.Start(). After at least one slice is "added", ActiveSlice works.
It was similar with the API waiting for radios to be discovered. After Init() I had to monitor the radio-added event.
I did this in a loop, sleeping between checks, with an overall timeout to throw an exception with the bad news if radios or slices never appear.
Thanks again for the help.
73
-Brian n8wrl
0 -
All
The slice subscription, status message gives the slice VFO frequency and a flag, for that slice, for both "active" slice and "tx" slice. The flag is 0/1, for each slice: active/tx combination. You need to use some logic to know what type of slice frequency you see in the slice status message, for each open slice.
Alan. WA9WUD
0 -
Hi Brian, TimeIt is only there for the speech synthesizer. A blind ham in the Boulder Amateur Radio Club asked me if any of my programming was blind-friendly, so I added a way to speak the frequency. I had to time things so that I did not get queued up calls to the synthesizer (crude, but effective...). If this is fixing your code, then it is just luck of the draw that the timing worked out OK.
I think that the actual thing going on is that you can't do any slice things until the radio has communicated that it has a slice, and then that the specific data that you want has been presented (Freq, Mute, etc.). I don't try to do a main loop waiting for data. It is just events firing and being serviced based on returned data.
In the case of slices, first you need the slice added event. Within that event handler, you create another event handler for the slice that was just added (e.g. slice0_PropertyChanged). Now within slice0_PropertyChanged, you can query e.PropertyName to see if the data that you want is there. Depending on the event returned parameters (e.PropertyName), I am populating the frequency text box for slice 0 or changing the slice 0 mute status on the display. There are many others, I just chose these two to provide an example.
private void slice0_PropertyChanged(object sender, PropertyChangedEventArgs e) { CheckOOB(0, slice0.Freq, slice0.DemodMode, slice0.IsTransmitSlice); if (e.PropertyName.Equals("Freq")) { txtFreq0.Text = slice0.Freq.ToString("0.000 000"); txtFreq0.BackColor = Color.White; <snip> } if (e.PropertyName.Equals("Mute")) { if(slice0 == null) { return; } if (slice0.Mute) { btnMute0.Text = "Un-Mute"; } else if (!slice0.Mute) { btnMute0.Text = "Mute"; } } <snip>
The event handlers do the waiting for you. When one fires off, you can act on the data received.
It is a different way to look at things, but once you see how the data arrives asynchronously via the event handler, you will have a good grip on it.
I hope that explanation made sense.
0 -
Hi Alan, Brian is using FlexLib with C#, not the raw text-based API. He is not dealing with subscriptions directly. FlexLib is doing all the parsing of status, response and message data and returning that data in a different way than what you are used to with the plain API.
0 -
Len, thanks. As you can guess, I am not familiar with the .NET.
Alan. WA9WUD
0 -
Thank you both!
Len, yes I am very used to event-based programming, much prefer it. I think your timer "fixes it" for you. In effect, I am doing the same thing with my short sleeps to allow events to percolate.
I'm off and running now. Thanks all!
73
-Brian n8wrl
1 -
Hi Brian, I'm glad you have it working! One thing that you will want to do as you get more into it is to be sure to bind your non-GUI client to the GUI Client (typically SmartSDR or a Maestro). This way, things like CW speed and RF power will work properly. Otherwise, you will always get 30 WPM for CW Speed and some value for RF power that does not follow SmartSDR. Check out the _thisRadio_GUIClientUpdated routine in my code where I grab the client handle and use it to get the ClientID and from there do the bind:
_thisRadio.BindGUIClient(ClientID);
This is a very misunderstood requirement, as quite a few things will work fine without it. A soon as you try to display or change one of the items (like CW Speed or RF Power), you will get the wrong value unless you have done the bind. Note that this only holds true if you are operating as a non-GUI Client (i.e. no panadapter via UDP). If you wanted to be a GUI client, you would need to execute the command to do that instead of the bind to GUI command (haven't researched that yet...).
As far as the TimeIt timer goes, it was only added recently for the speech. The rest of the code has been working very well without it. I have no main loop since it is a C# forms-based program. It is purely event-driven. In your case, the short sleeps in your main loop just let the event queue drain so that you can poll the data.
Be sure to share your Flex Control interface progress. It is always interesting and informative to see how people are interfacing additional gear to their Flex radios.
0 -
Len this was very helpful! Thank you.
As a proof-of-concept, I can toggle code in and out to control overall radio volume or active slice frequency by turning the FlexControl.
And now I have a new problem I have to research.
As you turn the FlexControl that SmartSDR knows about, the "VFO box" moves across the Panadapter display as the frequency changes up or down. As the "VFO box" gets close to the edges, the whole Panadapter scrolls horizontally as frequency continues to change.
My code is simply adjusting the frequency of the slice. As a result, the "VFO box" moves across the Panadapter, but when it gets to the edge it "snaps" back to the middle. The frequency changed correctly, and the frequency legend along the bottom of the Panadapter has changed too, so the display is correct. It just doesn't smoothly scroll left or right.
Gotta figure that out...
73!
-Brian n8wrl
0 -
Hi Brian, yes, I have that same behavior. If I tune using the mouse wheel, the panadapter scrolls left or right when I get close to the edge. If I tune using the knob on my TeensyMaestro, it snaps to center when approaching the edge of the panadapter.
I am OK with that behavior, so I never tried to change it, but now you have me curious as to what to do to make it a smooth scroll instead of a re-center.
If I figure it out, I will post back here.
0 -
Slice.AutoPan isn't the fix!
While it seems like a display-preference the user of SmartSDR might have set, it doesn't make sense that the two devices would behave differently manipulating the frequency of the same radio. I much prefer the smooth scrolling, and since SmartSDR knows what to do with a FlexControl, there must be a way to do it!
73
-Brian n8wrl
0 -
Well, I found this, but so far I can't make it work:
slice0.Panadapter.AutoCenter = false;
It compiles fine, but I am missing something...
0 -
Looking at the source for Panadapter I see that AutoCenter defaults to false, just like slice.AutoPan defaults to true, so setting either one to what "seems sane" won't change anything.
This feels like a GUI preference thing - something that the user of the GUI should be in control of, and if that's the case then we shouldn't be able to alter it via the API.
Except that it behaves differently depending on the source of the frequency change!
Still digging...
0 -
If you change slice.AutoPan to false, you get the expected behavior; the flag scrolls off the page and you get the slice indicator that shows you an arrow pointing the direction of the flag.
Changing slice.panadapter.AutoCenter has no effect. I think the issue is the fact that AutoPan is a slice parameter and AutoCenter is a panadapter parameter.
I don't know how to use that info yet (or if it is even valid...).
Maybe the thing to do is to do a WireShark capture of what happens when the mouse wheel moves the frequency near the pan edge. Perhaps SmartSDR is setting the panadapter center frequency by the step size when it is near the pan edge.
0 -
Good sleuthing.
I have shelved this for now. I am working on a generic way to configure the buttons and knob for various function-assignments. Right now I have a working hard-coded config that, turning the knob adjusts the volume, if button1 was pressed (and so the LED is on) turning the knob adjusts the volume.
Yeah, SmartSDR and other tools do this and much more now. The big payoff is going to be supporting multiple FlexControls, which I'm doing.
:)
The behavior of the GUI for frequency changes surely feels user-configurable to me, not something we should be adjusting via the API. What if I don't want it to auto-center? Etc.
73
-Brian n8wrl
0 -
Well in all this playing around, I finally figured out what I was doing wrong with the PropertyChangedEventHandler...
//radio.PropertyChanged += radio_PropertyChanged; radio.PropertyChanged += new PropertyChangedEventHandler(radio_PropertyChanged);
I originally just appended the routine name (commented out, above). It worked, but I wondered why that one statement looked different from the rest.
I found the actual event handler and used it along with the new keyword, just like the rest of them. Works great. I wonder what bad things were going on in the background that this fixes...
0 -
Over the years c# added features and more concise ways of doing things. Your new statement is an example of the old school delegate syntax which will still work... Your first, commented out statement is the simpler way to do the same thing.
1 -
Ahhh... Thanks for that Brian. My PL/I, Fortran, COBOL accent is showing through...
0 -
Hi Brian, I think figured out how SmartSDR scrolls the panadapter when you approach the edge of the screen while tuning with the mouse wheel.
When you get within a certain distance from the edge of the panadapter display, it scrolls the display by the amount of the step size. That prevents the flag from going past the edge of the screen which either triggers a re-center event, or simply moves off screen (depending on slice.AutoPan).
These are the messages returned by SmartSDR in the case of going past the threshold distance from the edge. This shows that SmartSDR resets the pan center frequency to avoid the re-centering.
S6BDA2526|display pan 0x40000000 center=7.019190 S6BDA2526|display waterfall 0x42000000 center=7.019190
So here is the code that I tested it out on, and it works better than I expected! This routine moves the frequency down one step when the frequency down button (on my form) for slice 0 is clicked:
private void btnFreq0Dn_Click(object sender, EventArgs e) { double Freq = 0; if (ConvertFreqToDouble(txtFreq0.Text, ref Freq)) { if(slice0.Freq <= (slice0.Panadapter.CenterFreq - (slice0.Panadapter.Bandwidth / 2)) + ((slice0.Panadapter.Bandwidth / 2) * .21)) { slice0.Panadapter.CenterFreq -= slice0.TuneStep / 1_000_000.0; } Freq -= (TuneStep0 / 1_000_000.0); slice0.Freq = Freq; Freq = 0; txtFreq0.BackColor = Color.White; //form1.txtDebug.Text += slice0.Panadapter.CenterFreq + NL; //form1.txtDebug.Text += slice0.TuneStep / 1_000_000.0 + NL; //form1.txtDebug.Text += slice0.Panadapter.Bandwidth + NL; }
The complicated if statement finds the left edge of the panadapter, then finds the threshold point (21%) to the right of that, then compares to see if the threshold has been passed and scrolls the center freq by the amount of the step. I should probably add step size to the calc in the if statement, as big steps could probably scoot you past the edge of the screen.
The test for the right panadapter edge would be similar, but with addition and subtraction reversed for some of the variables.
I think this little proof of concept will help you to obtain what you are trying to achieve, but let me know if it does not.
Thanks for asking about this Brian, I learned a few new things! I may even apply this to my TeensyMaestro with a menu item that allows choosing the desired action - scroll or re-center when tuning past panadapter edges.
1 -
OK, here is the final version with TuneStep taken into account. This allowed me to change the percentage to 20% of the distance from the edge plus StepSize as the threshold point for scrolling the panadapter.
I also removed the global variable TuneStep0 and just use slice0.TuneStep. The divide by a million converts TuneStep in Hz to MHz so that it can be added to or subtracted from Freq which is in MHz.
private void btnFreq0Dn_Click(object sender, EventArgs e) { double Freq = 0; if (ConvertFreqToDouble(txtFreq0.Text, ref Freq)) { if(slice0.Freq <= (slice0.Panadapter.CenterFreq - (slice0.Panadapter.Bandwidth / 2)) + ((slice0.Panadapter.Bandwidth / 2) * .20) + (slice0.TuneStep / 1_000_000.0)) { slice0.Panadapter.CenterFreq -= slice0.TuneStep / 1_000_000.0; } Freq -= (slice0.TuneStep / 1_000_000.0); slice0.Freq = Freq; Freq = 0; txtFreq0.BackColor = Color.White; //form1.txtDebug.Text += slice0.Panadapter.CenterFreq + NL; //form1.txtDebug.Text += slice0.TuneStep / 1_000_000.0 + NL; //form1.txtDebug.Text += slice0.Panadapter.Bandwidth + NL; } else { txtFreq0.BackColor = Color.Red; } }
Thanks again for posting here Brian, this was a fun exercise and definitely improved my understanding of the Flex API, FlexLib and C#.
0 -
This is good stuff Ken! Your logic makes perfect sense and the code goes a long way towards helping us all understand it.
Please understand that I am very grateful by the effort you put into this, and I in no way mean to criticize your work. There is quite a bit of "code smell" here to me. In this case, a magic number - 21. While your 21% logic makes perfect sense, it is based on knowledge of the UI and of the width of the display. Someone else may have a wider display and/or Flex may come out with a different VFO-"flag" UI for SmartSDR in the future. Heck, your teensy-maestro probably has different proportions. All of which makes 21% a guess this time.
I think there is something more fundamental going on here. "Separation of concerns" means decisions about scrolling and such is made by the UI (and the users of that UI), not by something like my code sending the radio messages. I am going to send a note to Flex support to see if they have any ideas.
Thanks again Len! Nice sleuthing!
73
-Brian n8wrl
0 -
Yep, 20% was determined empirically based on approximately where I saw the API switch over to centering the display. The width of the display does not factor in. It is solely based on the displayed bandwidth of the panadapter. That way, the behavior will be the same for any sized display.
This code does indeed depend on knowledge of the SmartSDR UI. It is specifically being used to drive that UI. If this were a GUI client (like SmartSDR), then that would be different.
Flex may tell you what their formula is for calculating the change-over point, but I would be surprised. It feels like slice.Panadapter.AutoCenter was created for this purpose, so maybe Flex can enlighten us on this construct and how to use it.
The TeensyMaestro is a non-GUI client and does not display panadapter data, so it is more like what you are doing with your FlexControl.
1 -
So here's the story -- if we drive these changes through the radio, the user experience is less than smooth due to the way the commands and status updates interact and the inherent delays getting messages to the radio and back to the client.
This flow just isn't suitable for smooth GUI operation:
Slice Tune -> Radio
Radio adjusts Pan -> Status back to client
Client reacts to status
So we end up handling as much of this as we can in the client directly while still supporting some mechanisms in the radio to ensure that tuning from CAT or other 3rd party applications keep the Slice visible in the Panadapter. The AutoPan and AutoCenter properties are the implementation for 3rd party apps.
I'm happy to share more about how we improved the performance in SmartSDR as needed, but it is similar to what KD0RC showed above -- just adjusting the Panadapter CenterFreq locally as needed to keep the Slice on screen.
2 -
Thanks for the explanation Eric, I really appreciate it.
0 -
Erik - Question
First, thanks for explaining the Flex philosophy on using status messages for client updates.
Question. You say that Flex uses this technique, so the client updates itself rather than waiting for a status message from Flex Radio.
For my applications, I have been using the Flex response message (hex zero or OK) as confirmation before I update the local status of the commanded parameter.
If I understand your explanation, SmartSDR, when making such commands, is doing so "in the blind," not waiting for a return confirmation of the command before updating the local parameter.
Alan. WA9WUD
0 -
Okaaay, with Eric's assistance and pure-plagiarism of Len's code, I've put something together that works pretty well. Before I post the whole method, some notes:
- This is a class library I'm working on, and the class this method lives in provides simplified access to a Flex Slice object. If you provide that (m_theSlice, in my case) you should be able to use this anywhere.
- I like to do math with decimals. I've done a lot of work with catalogs and other financial systems where rounding oddness from the floating point formats is unacceptable. The more manipulation you do, the worse it gets, so I convert to decimal and back to double after all the fiddly stuff.
- I'd like this to be a bit "smarter" about bandwidth, the side of the VFO the passband is on, etc. So I may post more later.
For now, here it is!
/// <summary> /// Set the slice frequency and adjust UI if necessary /// </summary> private void SetFrequency(decimal newFreqMhz) { // Very unlikely... decimal curFreqMHz = GetFrequencyMHz(); if (newFreqMhz == curFreqMHz) return; // Get our Panadapter var pan = m_theSlice.Panadapter; if (null != pan) { // With the Panadapter that this Slice is rendered on, decide if it needs to scroll with // the frequency change. decimal panCenterFreqMhz = (decimal)pan.CenterFreq; decimal panBandwidthMhz = (decimal)pan.Bandwidth; decimal halfPanBandwidthMHz = panBandwidthMhz / 2; // Is the slice is visible within the panadapter? if ( (curFreqMHz >= panCenterFreqMhz - halfPanBandwidthMHz) && (curFreqMHz <= panCenterFreqMhz + halfPanBandwidthMHz)) { // Are we about to push the slice off either end of the panadapter? // Percentage to apply to panadapter bandwidth to decide if we need to scroll decimal edgePercentage = 0.15m /* % */; // Scroll if we're within 15% of the edge decimal edgeDeltaMhz = halfPanBandwidthMHz * (1 - edgePercentage * 2); bool scrollPan = false; // Which direction are we going? if (newFreqMhz < curFreqMHz) { // Down, or to the left scrollPan = (newFreqMhz < (panCenterFreqMhz - edgeDeltaMhz)); } else { // Up, or to the right scrollPan = (newFreqMhz > (panCenterFreqMhz + edgeDeltaMhz)); } // Need to scroll? if (scrollPan) { // TODO: Should we restore these after setting slice-freq? pan.AutoCenter = false; m_theSlice.AutoPan = false; // We want to move the panadapter in the *same* direction the slice frequency // is moving so that the "VFO line" stays on the pan decimal deltaMHz = newFreqMhz - curFreqMHz; pan.CenterFreq += (double)deltaMHz; } } } // Now send the new frequency to the slice m_theSlice.Freq = (double)newFreqMhz; }
73!
-Brian n8wrl
1
Leave a Comment
Categories
- All Categories
- 260 Community Topics
- 2.1K New Ideas
- 538 The Flea Market
- 7.6K Software
- 6K SmartSDR for Windows
- 139 SmartSDR for Maestro and M models
- 368 SmartSDR for Mac
- 242 SmartSDR for iOS
- 226 SmartSDR CAT
- 175 DAX
- 345 SmartSDR API
- 8.8K Radios and Accessories
- 6.9K FLEX-6000 Signature Series
- 44 FLEX-8000 Signature Series
- 859 Maestro
- 45 FlexControl
- 849 FLEX Series (Legacy) Radios
- 807 Genius Products
- 424 Power Genius XL Amplifier
- 280 Tuner Genius XL
- 87 Antenna Genius
- 227 Shack Infrastructure
- 153 Networking
- 409 Remote Operation (SmartLink)
- 130 Contesting
- 640 Peripherals & Station Integration
- 116 Amateur Radio Interests
- 878 Third-Party Software