 Welcome to this lecture on digital communication using GNU Radio. So far you have been seeing the use of blocks in GNU Radio in order to achieve several functionalities. We have actually not really peaked into these blocks. Of course, we have some intuition as to what happens inside the blocks. For example, if you add a block that does let us say a filtering, it essentially does convolution inside. If you add an amplification block, it just multiplies the samples and so on. On many occasions, there are situations where you may want to add your own functionality because it may be cumbersome to implement it using the GNU Radio inbuilt blocks and maybe it can be expressed in a much easier fashion using some bit of Python code. In this lecture, we are going to introduce the concept of extending GNU Radio using Python blocks and in particular, how you can use Python blocks in GNU Radio to achieve several functionality extensions that can be done using just a few lines of code. In general, the blocks in GNU Radio are written in C++ or Python C++ for efficiency. But for simple calculations, you may find that using a Python block achieves a lot of extension in functionality without much effort. So you can follow along this lecture to understand how you can make some simple embedded Python blocks that extend the functionality of GNU Radio using Python. In this lecture, we are going to discover among the most powerful features of GNU Radio using which you can extend the features of GNU Radio by writing Python code. In particular, we are going to look at the Python block, embedded Python block over here, which allows you to create your own block by writing just a few lines of Python. But there are several things you can achieve in a very compact way directly using Python without having to use any of the inbuilt blocks in GNU Radio. This is particularly useful if there is some operation that is a little difficult to implement using just the built-in blocks. So you can write some Python to achieve some more powerful transformations with much ease. So let us begin. So we say control f for command f and if we type block, we get the Python block which is in core miscellaneous. We drag it here. We get this block called embedded Python block. Let us actually just connect it out and see what happens. So let's actually just get a throttle, control f or command f, we'll get a throttle. We'll get, let's say, a signal source, control f or command f, we say signal source and we get a time sink, control f or command f, we get time sink, okay. We connect this over here, connect this over here, connect this over here and we double click this embedded Python block and set the example parameter, let's say 1.0. And let's say we try to run this, it says block example.toA must begin with a letter and may contain letters, numbers and so on, okay. So I think this is not going to work. So let's double click this and actually start making some edits. So we say open in editor. So that it will open in a Python editor, your default Python editor. And as you can see, you get this particular block, okay, embedded Python block. There's a boilerplate code. And you have at the beginning a string. So actually Python uses strings for documentation. So they've written embedded blocks. Each time this file is saved, new radio companion within will instantiate the first class it finds to get ports and parameters of your block. The arguments to in it will be the parameters. All of them are required to have default values. So let's remove the string. What they're saying is that this is a Python class, the first class is essentially taken by no radio companion. This class is basically a Python object definition. It is a subclass of gr.sync block, which means that this block is going to take in samples and give out samples at the same rate. They have mentioned that there are other base classes, which are basic block, December block, interblock. So basic block is more general decimation interpolation block will provide less or more samples than the inputs as you can get from their names. So let us get rid of this also. Again, there's a string embedded Python block example, a simple multiply const, okay. So now if we look inside, what happens is that there's a init. This init is also called constructor. So this def init is like a method of the object. It's the constructor. So you don't need to know the full details. But this is what gets called when Python when glue radio first creates the object. You need to just say gr.sync block.init and self. So basically you're going to call the sync blocks initialization name. Let us call it let's say instead of embedded Python block, we'll call it my amplifier. Okay, the in sync and out sync are very important. They determine what type the input is and what type the output is. In this case, the in sync out sync both are NP dot complex 64. We will leave it like it is. And the self param equal to example param basically this example param which is 1.0 above. This is a parameter passed by GNU radio companion to store it within the object within the instantiated object. You have to say self dot example param equal to example param only then the value is stored. Now let us actually change this a little bit. Let's call this instead of example param, let's call this amplification. And then here also we will call it amplification. Now the next thing is that there is a function or method called work. This is the main function or method that does all the jobs. You get an input items, you get an output item. These are actually not NMPI arrays, but they are like lists of NMPI arrays. They contain that many elements as the dimension. For example, in my case, in sync NP dot complex 64 implies that NMPI array is NMPI array 64 implies that input items contains one element, which is an NMPI array out sync is NP dot complex 64, which means my output items contains one element with the NMPI array. So this particular notation allows you to get that first element and write into it. So this essentially replaces those elements with whatever you write here. So what does this line do? Output items 0, that is the first output item, colon allows you to write into it equal to input item 0 multiplied by self dot example param. This we will change to amplification return len output item 0. This tells crew radio how many items you are actually going to return. In general, because it's a sync block, it is expected that the number of output items is equal to the number of input items in each of those streams. So now let's save and exit. Now let's say okay. Now let's execute it. So as you can see, you know, it did some, you know, it has this complex signal and you know, you have these red and blue, all those, all that is fine. But let's check, let's make the amplification two. Now if you execute it, you can see that it actually amplified to two. But the thing is, I don't want the sampler to work on complex samples because I get these, you know, cause sign both because of the C par J omega naught T and everything. Let us actually make it work for a real. So how do you make it work it work for real? I'm going to double click it, click open an editor. And I'm going to now change the data types from complex 64 to float 32. Float 32 over here, oops. And float 32 over here. And I don't need to worry about the rest because GNU radio will make sure that these NumPy arrays are now float arrays as opposed to complex arrays. So this code doesn't change, it will work as is. If I now save and say, okay, notice that because I've changed the types, GNU radio has disconnected them and notice that the color is now orange indicating that this is a float. So let's change all our blocks to float starting with a signal source. Then the throttle, our time sink. Let's also make our time sink have two inputs. So let's first add the, let's say two inputs. And now let's connect this over here. Let's connect the original input to the first one and let's connect the amplified one to the second one. And now if I run my code, you can clearly see that the signal to which is the second signal is clearly amplified by a factor of two. Okay, so this is not still not, you know, very useful. Can I actually do some more things with this? Can I make this amplification a variable amount? So what we do is we say control F for command F, we say range, and we get a QT GUI range. And we double click this range, and we call it ampl because I'm too lazy to type out amplification. Let its default value be one, let it start at zero, go up to 10. And we'll say step 0.1, okay, and we'll change this to ampl, okay. So let's execute our flow graph. Now if I increase the amplification, you can clearly see that the red one gets amplified or, you know, if I, if I make the application less than one, it gets smaller and smaller, and that's good. But what if I want to have more parameters? It's very easy. You can just double click this particular my amplifier can open in editor. Now let us also add a feature to introduce an offset into the signal. So to do that, we will first change this init function to accept another parameter called offset by default, it should be zero. We will store it self dot offset is equal to offset. And finally, over here, you just add the offset. So what does this do? This essentially amplifies your signal by amplification and adds the offset so that you can boost the signal by that much, you can boost meaning you can raise it by that much. So let us save and exit. Now we're going to call this a variable offset and say, okay, obviously offset is not defined since I'm too lazy to get another range. I'm going to just click this hit control C or command C control your command V, I get a copy I double click this and I'm going to now just call this offset. Now if I execute this flow graph, as you can see, offset is one, okay, fine, let me make the offset zero. You can see that the offset is zero, they match. If I start increasing the offset goes up and down, of course, I can make the offset negative also. If I let's make this less amplified and let's also introduce an offset. So you can see that I am now able to take multiple parameters also. Let me now just make this amplifier work for two signals just, you know, just for curiosity. So let's just take my amplifier and let's say that you have two signals. So for two signals input and two signals output, you don't need the inputs and output to be the same. Let's say that here you do have them to be the same. You say you take a float 32 and you give out another float 32. Then in this case, you can see what you'll have is that the number of signals input is true, the number of signals output is true. So you can actually amplify them in parallel. So by just adding this line and putting the one over here. So this is going to amplify two signals concurrently. So let's save this and now let's say, okay, as you can see, you have two signals now. So in the meanwhile, let's make this have four inputs or let's actually create a copy of this. Okay. Now let us create copy the signal source by doing Ctrl C Ctrl B and let's make this into a let's say square wave, okay, square wave and let's connect the square wave original over here and the square wave amplification offset through here. Now if I execute this, both of these are going to experience the same kind of offset. For example, if I increase the offset, it goes up and down together. If I increase the amplification, both of them amplify together, right? So if I increase the offset, you can see both the sign and square go up, increase the amplification, both of them go up. So what you have seen is that you can make Python blocks rather easily without having to put in too much effort. And the key idea that you're going to exploit here is that you can write arbitrary Python functionality to process your arrays inside here by just clicking open in editor and writing appropriate Python code. Now again, like I keep mentioning, while this is not mandatory to know, there are several situations where writing the code in terms of a GNU radio block can make a lot of difference, can make the implementation a lot easier than having to use your own code. There is only one catch that I wish to warn you of. If you write the code in a very inefficient way and make it slow, that can slow down the performance of the whole block. So just be aware to write short efficient code within your own blocks. Just to have another example, let us consider a decimation block just to add some variety. We will write a decimation block that does block based addition. That is, let's say it takes three numbers adds them and gives you the sum four numbers adds them gives you the sum and replaces every four numbers, let's say with a single number so that you have a decimation block. So we'll say controller for command f block, we get a Python block. We double click it and we say open in editor and we open it in our favorite editor. Now we are going to use a decimation block and this requires some bit of changes. So the first thing is that we derive from the same block as opposed to sync block. We're going to just have one parameter, which is the decimation rate, which is an integer. We are going to call this block adder because it's going to add based on blocks, we keep it as float 32 float 32 and we need to add another parameter called decim rate, which we have to pass to the superclass, which is the decim block. So now you have the decim block, they'll self dot example param, we'll just store the decim rate instead. We also need to tell GNU radio the relative rate of this block is one upon the decimation rate that is relative rate. This will ensure that new radio knows that for every sample that comes in, only half a sample goes out that is because if for example, if the decimation rate is two, you're only going to get for every two samples, you're only going to get one output sample. This is what GNU radio uses to check to set the output length in the work function. Now in the work function, the input items and output items would automatically be sized appropriately based on the decimation rate. So now to do the block addition, I am going to actually create a blank array and then start populating the contents instead. So let's say I say result is NP dot zeros, and I'll say something like length of output items zero, I guess that's one way to do it, or we can say length of input item zero integer divided by self dot decim rate. This will create a result array which is all zeros that contains exactly the input items divided by decimation rate number of zeros. Then I'm going to write a for loop in Python. So I'm going to say for I in range self dot decimation rate so that I can start accumulating the array elements using offsets. So I'm going to say result is equal to result plus. Now what I'm going to do is I'm going to take the 0th element, then the let's say our decimation rate is four, I will take the 0th element, the fourth element, the eighth element. The next I'll take the first element, fifth element, ninth element and so on. So I'm going to say input items zero, start with the ith element, keep taking elements stepped by decimation rate and then just set this to be the output array. So what does this code mean, this particular slice mean? This means start at the ith element, get all the elements stepped at decim rate. For example, if the first run of this loop, you'll get the 0th element, the fourth element, the eighth element and so on, assuming the decim rate is four. In the next run, you'll get the first element, the fifth element, the ninth element and so on. Now in result, what happens is that the first element is going to have the 0, 1, 2, 3th element added. The second element is going to have the 1, 4, 5, 6, 7th element added and so on. And this will ensure that your four successive entries in input items get added up. Let's check this. So now I'm going to save this and exit. It says unexpected keyword, okay, we'll just check the code over here. Ah, yes. So this should be decim. So this you can find out from the grew radio documentation, okay. So now, now you can say, okay, okay. So now let's add a throttle, okay. And we'll make the throttle for float to float and say, okay. Then we will take a vector source because you'll have easier control and see how this block behaves. Vector source and we'll add a time sink. We'll add a time sink. We'll set the time sink to float. We'll set the vector source to float. And the vector source, let's give the elements, we'll give it as a Python list and grew radio will take care of it. We'll give 1, 1, 2, 3, only these many because what you'll find is that grew radio is going to repeat this and the result is going to be just the summation of these. So let's check. We'll connect this, we'll connect this, we'll connect this. Before we start 1 and 1 and 2 and 3, I think they add up to 7, okay. Keep that in mind. Oops. So I think there's a small syntax error, sorry. So there's no syntax error, there's no syntax error. We need to specify the decimation rate over here and set it to let's say 4. That's what we need. Now let's execute this. It's 7, okay. Why is it 7? Because every 4 elements are being added up. So if you don't believe me, let's make it 8 elements and let's make this 1, 1, minus 1, minus 1. So if you add these 4 elements, you get 7. If you add these 4 elements, you get 0. So you will get 7 followed by 0, followed by 7, followed by 0. And because you're decimating by 4, these 4 samples are going to, these, you know, this full 8 sample cycle. It's very easy to check. Will be 32K divided by 4, 32K divided by 8 will be 4K. 4K is, you know, 4 kilohertz will be the frequency that corresponds to, I think about, I think 2.25 milliseconds. Let's check. Oops. Yes. Let's get this kind of structure. Okay. Let's check the gap between the two peaks, 14.0, 14.06, 14.25, yeah, I think, let's actually just do it in a slightly different way. Let's add more points, 1, let's say 8192, okay. So again, 1, 1, 2, 3 adds up to 7. 1, 1, minus 1, minus 1 adds up to 0. Okay. Let's zoom in here. Zoom in here. Let's zoom in here. Let's zoom in here. Yes. So this point is at 7, okay, because it's adding up as you go, 107.3, 107.37, 107.31. So it's 0.0625, yes. So why is that the case? So it's 8 samples. Yeah. So these, yeah. 8 samples over here, and because it undergoes a decimation rate of 4, instead of 0.25 milliseconds, you get it at 0.25 milliseconds upon 4 that corresponds to 0.0625 milliseconds. So if you look closely now over here, let's look closely, okay. So let's look at these two peaks, this is around 16.25, this is at 16.31, which corresponds to 0.0625. That's the magic of this decimation. Let's change things around and let's say we make it 1, 1, followed by minus 1, 1. So 1, 1 followed by minus 1, let's say minus 1, minus 1, just to make it more fun and let's make the decimation rate 2. So now you can clearly see that if the decimation rate is 2, you get 2, 0, 2, 0. If you make the decimation rate 1, it doesn't do this addition and you get something like a square wave because you have 2 samples of 1, 2 samples of minus 1 and so on. So this is definitely key to understanding how this works. So what we've seen is that you can get adders or you can, sorry, you can get blocks which have this different decimation rates as well and make your own blocks and you can check the GNU Radio documentation to understand how you can use this feature for good effect. What we just saw was a very basic introduction to how you can use Python in order to extend the functionalities of GNU Radio. In particular, you were able to write embedded Python blocks where you can just insert pieces of Python code to perform processing in a very efficient way. Just keep in mind that while writing blocks does look attractive, there is always a balance. Writing really complicated processing in blocks will end up slowing down your flow graph because if you write a lot of Python computations inefficiently, then you may end up hurting the performance of your simulation. However, in several situations where it is easier to express some functionality or some processing using a few lines of Python code rather than bringing in a lot of blocks and performing a lot of wiring, then use of an embedded Python block is a very good way to extend the functionality of GNU Radio. I encourage you to read the GNU Radio documentation on writing your own blocks both in Python and C++ so that when the need arises, you can implement your own blocks efficiently. Thank you.