-
Notifications
You must be signed in to change notification settings - Fork 10
/
DemoController.cpp
221 lines (190 loc) · 8.24 KB
/
DemoController.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
// Copyright (c) 2014-2017 Josh Blum
// SPDX-License-Identifier: BSL-1.0
#include <Pothos/Framework.hpp>
#include <Poco/Logger.h>
#include <iostream>
#include <chrono>
#include <algorithm> //min/max
/***********************************************************************
* |PothosDoc SDR Demo Controller
*
* Demonstration controlling the SDR source and sink blocks from the topology.
* This block demonstrates:
* <ul>
* <li>setting hardware time</li>
* <li>getting hardware time</li>
* <li>timed frequency tuning</li>
* <li>requesting receive bursts</li>
* <li>parsing receive labels</li>
* <li>sending transmit bursts</li>
* </ul>
*
* |category /SDR
*
* |param dtype[Data Type] The data type used by the stream ports.
* |widget DTypeChooser(float=1,cfloat=1,int=1,cint=1)
* |default "complex_float32"
* |preview disable
*
* |factory /soapy/demo_controller(dtype)
* |alias /sdr/demo_controller
**********************************************************************/
class DemoController : public Pothos::Block
{
public:
DemoController(const Pothos::DType &dtype):
_lastHardwareTimeNs(0),
_lastRxHardwareTimeNs(0),
_rxTimeLabelIndex(0),
_lastKnownRxRate(1.0)
{
this->setupInput(0, dtype);
this->setupOutput(0, dtype);
this->registerCall(this, POTHOS_FCN_TUPLE(DemoController, handleHardwareTime));
this->registerSignal("streamControl"); //connect to streamControl on source block
this->registerSignal("setHardwareTime"); //connect to set hardware time
this->registerSignal("setCommandTime"); //connect to set command time
this->registerSignal("setFrequency"); //connect to set frequency
}
static Block *make(const Pothos::DType &dtype)
{
return new DemoController(dtype);
}
/*!
* Handle a hardware time event.
* We record the hardware time and when we observed this time event.
* Both pieces of information can be used to estimate the hardware
* time in the future to use it for scheduling bursts and commands.
*
* To keep the controller block up-to-data with the hardware time,
* the handleHardwareTime() slot may be connected to the
* "getHardwareTimeTriggered" signal from a SDR block.
* The receive stream may provide an "rxTime" label,
* which can be used to also update the hardware time.
*/
void handleHardwareTime(const long long timeNs)
{
_hardwareTimeEvent = std::chrono::high_resolution_clock::now();
_lastHardwareTimeNs = timeNs;
}
void work(void);
void activate(void);
private:
/*******************************************************************
* Dealing with time - there is more than one way.
*
* A periodic signal from SDR source or sink block
* can keep us up-to-date about the current hardware time.
*
* Or using the receive stream labels to associate the
* hardware time with an absolute stream element index.
******************************************************************/
//the last time we received a hardware time (nanoseconds)
long long _lastHardwareTimeNs;
//the PC timestamp when we received the hardware time
std::chrono::high_resolution_clock::time_point _hardwareTimeEvent;
/*!
* Get the approximate time on the hardware at a given point in time.
*/
long long getApproximateHardwareTime(const std::chrono::high_resolution_clock::time_point &point)
{
const auto deltaNs = std::chrono::duration_cast<std::chrono::nanoseconds>(point - _hardwareTimeEvent);
return _lastHardwareTimeNs + deltaNs.count();
}
//the hardware timestamp from the last rx time label
long long _lastRxHardwareTimeNs;
//The absolute element index for when we saw the time label
long long _rxTimeLabelIndex;
//the last known receive sample rate provided by a label
double _lastKnownRxRate;
/*!
* Get the time of a specific stream element given its absolute index.
*/
long long getStreamElementTime(const long long index)
{
const auto deltaSecs = (index - _rxTimeLabelIndex)/_lastKnownRxRate;
return _lastRxHardwareTimeNs + (deltaSecs*1e9);
}
};
void DemoController::activate(void)
{
//start HW time at 0 so the numbers are understandable
this->emitSignal("setHardwareTime", 0);
//request a tiny burst from the source block
//the work function will print when it gets the burst
this->emitSignal("streamControl", "ACTIVATE_BURST", 0, 100);
}
void DemoController::work(void)
{
auto inputPort = this->input(0);
auto outputPort = this->output(0);
//this block's work routine only reacts to input data
if (inputPort->elements() == 0) return;
bool sawRxEnd = false;
//Handle the input labels, check for time, and end of burst.
for (const auto &label : inputPort->labels())
{
if (label.id == "rxTime")
{
//updating the time-stamped hardware time
this->handleHardwareTime(label.data.convert<long long>());
//time tracking using the absolute element count
_lastRxHardwareTimeNs = label.data.convert<long long>();
_rxTimeLabelIndex = inputPort->totalElements()+label.index;
}
else if (label.id == "rxRate")
{
_lastKnownRxRate = label.data.convert<double>();
poco_notice_f1(Poco::Logger::get("DemoController"), "RX rate is %s Msps",
std::to_string(_lastKnownRxRate/1e6));
}
else if (label.id == "rxEnd")
{
sawRxEnd = true;
}
}
poco_notice_f2(Poco::Logger::get("DemoController"), "Got %s RX elements @ %s seconds",
std::to_string(inputPort->elements()),
std::to_string(this->getStreamElementTime(inputPort->totalElements())/1e9));
//The user should do something meaningful with the rx buffer...
//const auto &rxBuff = inputPort->buffer();
//consume the entire receive buffer
inputPort->consume(inputPort->elements());
//Use the rx end of burst event to trigger new actions:
if (sawRxEnd)
{
//perform a timed tune 0.5 seconds from the end of this burst
const auto commandIndex = inputPort->totalElements() + inputPort->elements() + size_t(_lastKnownRxRate/2);
const auto commandTimeNs = this->getStreamElementTime(commandIndex);
this->emitSignal("setCommandTime", commandTimeNs);
this->emitSignal("setFrequency", 1e9);
this->emitSignal("setCommandTime", 0); //clear
//request a timed burst 1.0 seconds from the end of this burst
const auto streamIndex = inputPort->totalElements() + inputPort->elements() + size_t(_lastKnownRxRate);
const auto burstTimeNs = this->getStreamElementTime(streamIndex);
this->emitSignal("streamControl", "ACTIVATE_BURST_AT", burstTimeNs, 100);
/***************************************************************
* Transmit an output burst at the same time as the input burst:
* The following code shows how to get access to the tx buffer,
* and post labels for both transmit time and end of burst.
**************************************************************/
//the user should fill tx buffer with something meaningful...
const auto numElems = std::min<size_t>(outputPort->elements(), 100);
auto &txBuff = outputPort->buffer();
std::memset(txBuff.as<void *>(), 0, numElems*outputPort->dtype().size());
//create a time label at the first index of the buffer
outputPort->postLabel(Pothos::Label("txTime", burstTimeNs, 0));
//create an end label at the last index of the buffer
outputPort->postLabel(Pothos::Label("txEnd", true, numElems-1));
//produce num elements of the transmit buffer
outputPort->produce(numElems);
//Alternative custom transmit buffer option:
//allocate a buffer: Pothos::BufferChunk txBuff(outputPort->dtype, numElems);
//fill the buffer and create labels as usual...
//post the buffer: outputPort->postBuffer(txBuff);
}
}
static Pothos::BlockRegistry registerDemoController(
"/soapy/demo_controller", &DemoController::make);
static Pothos::BlockRegistry registerDemoControllerAlias(
"/sdr/demo_controller", &DemoController::make);