trx/radio_if.py: implement AFC for both RX and TX paths

AFC (Automatic Frequency Control) was previously only utilized in
the receive path of the radio interface. Now we also need to keep
the transmitter frequency as accurate as possible.
diff --git a/python/trx/radio_if.py b/python/trx/radio_if.py
index dbccae7..1ded337 100644
--- a/python/trx/radio_if.py
+++ b/python/trx/radio_if.py
@@ -29,12 +29,14 @@
 
 from math import pi
 
-from gnuradio.filter import firdes
 from gnuradio import digital
 from gnuradio import blocks
 from gnuradio import uhd
 from gnuradio import gr
 
+from gnuradio import filter
+from gnuradio.filter import firdes
+
 
 # HACK: should be implemented in C++!
 class burst_to_fn_time(gr.basic_block):
@@ -55,10 +57,29 @@
         if pmt.to_python(fn_time) is not None:
             self.message_port_pub(pmt.intern("fn_time_out"), fn_time_msg)
 
+class dict_toggle_sign(gr.basic_block):
+    def __init__(self):  # only default arguments here
+        gr.basic_block.__init__(self,
+            name='Change sign of elts in dict',
+            in_sig=[],
+            out_sig=[]
+        )
+        self.message_port_register_in(pmt.intern("dict_in"))
+        self.message_port_register_out(pmt.intern("dict_out"))
+        self.set_msg_handler(pmt.intern("dict_in"), self.change_sign)
+
+    def change_sign(self, msg):
+        if pmt.is_dict(msg):
+            d = pmt.to_python(msg)
+            for key, value in d.items():
+                d[key] *= -1
+            self.message_port_pub(pmt.intern("dict_out"), pmt.to_pmt(d))
+
 class radio_if(gr.top_block):
 	# PHY specific variables
 	rx_freq = 935e6
 	tx_freq = 890e6
+	osr = 4
 
 	# Application state flags
 	trx_started = False
@@ -75,8 +96,10 @@
 		print("[i] Init Radio interface")
 
 		# PHY specific variables
+		self.sample_rate = phy_sample_rate
 		self.rx_gain = phy_rx_gain
 		self.tx_gain = phy_tx_gain
+		self.ppm = phy_ppm
 
 		gr.top_block.__init__(self, "GR-GSM TRX")
 
@@ -97,14 +120,15 @@
 		self.phy_src.set_bandwidth(650e3, 0)
 		self.phy_src.set_gain(phy_rx_gain)
 
-		self.gsm_input = grgsm.gsm_input(
-			ppm = phy_ppm - int(phy_ppm), osr = 4,
-			fc = self.rx_freq, samp_rate_in = phy_sample_rate)
+		self.msg_to_tag_src = grgsm.msg_to_tag()
 
-		self.gsm_receiver = grgsm.receiver(4, ([0]), ([]))
+		self.rotator_src = grgsm.controlled_rotator_cc(
+			self.calc_phase_inc(self.rx_freq))
 
-		self.gsm_clck_ctrl = grgsm.clock_offset_control(
-			self.rx_freq, phy_sample_rate, osr = 4)
+		self.lpf = filter.fir_filter_ccf(1, firdes.low_pass(
+			1, phy_sample_rate, 125e3, 5e3, firdes.WIN_HAMMING, 6.76))
+
+		self.gsm_receiver = grgsm.receiver(self.osr, ([0]), ([]))
 
 		self.ts_filter = grgsm.burst_timeslot_filter(0)
 		self.ts_filter.set_policy(grgsm.FILTER_POLICY_DROP_ALL)
@@ -112,10 +136,18 @@
 		# Connections
 		self.connect(
 			(self.phy_src, 0),
-			(self.gsm_input, 0))
+			(self.msg_to_tag_src, 0))
 
 		self.connect(
-			(self.gsm_input, 0),
+			(self.msg_to_tag_src, 0),
+			(self.rotator_src, 0))
+
+		self.connect(
+			(self.rotator_src, 0),
+			(self.lpf, 0))
+
+		self.connect(
+			(self.lpf, 0),
 			(self.gsm_receiver, 0))
 
 		self.msg_connect(
@@ -126,14 +158,6 @@
 			(self.ts_filter, 'out'),
 			(self.trx_burst_if, 'bursts'))
 
-		self.msg_connect(
-			(self.gsm_receiver, 'measurements'),
-			(self.gsm_clck_ctrl, 'measurements'))
-
-		self.msg_connect(
-			(self.gsm_clck_ctrl, 'ctrl'),
-			(self.gsm_input, 'ctrl_in'))
-
 
 		# TX Path Definition
 		self.phy_sink = uhd.usrp_sink(phy_args,
@@ -156,12 +180,17 @@
 			blocks.byte_t, 'packet_len')
 
 		self.gmsk_mod = grgsm.gsm_gmsk_mod(
-			BT = 4, pulse_duration = 4, sps = 4)
+			BT = 0.3, pulse_duration = 4, sps = self.osr)
 
 		self.burst_shaper = digital.burst_shaper_cc(
 			(firdes.window(firdes.WIN_HANN, 16, 0)),
 			0, 20, False, "packet_len")
 
+		self.msg_to_tag_sink = grgsm.msg_to_tag()
+
+		self.rotator_sink = grgsm.controlled_rotator_cc(
+			-self.calc_phase_inc(self.tx_freq))
+
 		# Connections
 		self.msg_connect(
 			(self.trx_burst_if, 'bursts'),
@@ -185,6 +214,14 @@
 
 		self.connect(
 			(self.burst_shaper, 0),
+			(self.msg_to_tag_sink, 0))
+
+		self.connect(
+			(self.msg_to_tag_sink, 0),
+			(self.rotator_sink, 0))
+
+		self.connect(
+			(self.rotator_sink, 0),
 			(self.phy_sink, 0))
 
 
@@ -205,19 +242,46 @@
 			(self.burst_to_fn_time, 'fn_time_out'),
 			(self.tx_time_setter, 'fn_time'))
 
+
+		# AFC (Automatic Frequency Correction)
+		self.gsm_clck_ctrl = grgsm.clock_offset_control(
+			self.rx_freq, phy_sample_rate, osr = self.osr)
+
+		self.dict_toggle_sign = dict_toggle_sign()
+
+		# Connections
+		self.msg_connect(
+			(self.gsm_receiver, 'measurements'),
+			(self.gsm_clck_ctrl, 'measurements'))
+
+		self.msg_connect(
+			(self.gsm_clck_ctrl, 'ctrl'),
+			(self.msg_to_tag_src, 'msg'))
+
+		self.msg_connect(
+			(self.gsm_clck_ctrl, 'ctrl'),
+			(self.dict_toggle_sign, 'dict_in'))
+
+		self.msg_connect(
+			(self.dict_toggle_sign, 'dict_out'),
+			(self.msg_to_tag_sink, 'msg'))
+
 	def shutdown(self):
 		print("[i] Shutdown Radio interface")
 		self.stop()
 		self.wait()
 
+	def calc_phase_inc(self, fc):
+		return self.ppm / 1.0e6 * 2 * pi * fc / self.sample_rate
+
 	def set_rx_freq(self, fc):
 		self.phy_src.set_center_freq(fc, 0)
-		self.gsm_clck_ctrl.set_fc(fc)
-		self.gsm_input.set_fc(fc)
+		self.rotator_src.set_phase_inc(self.calc_phase_inc(fc))
 		self.rx_freq = fc
 
 	def set_tx_freq(self, fc):
 		self.phy_sink.set_center_freq(fc, 0)
+		self.rotator_sink.set_phase_inc(-self.calc_phase_inc(fc))
 		self.tx_freq = fc
 
 	def set_rx_gain(self, gain):