Небольшая статья для начала работы на питоне с приемником HackRF One под Windows. Когда мне захотелось работать с приемником HackRF напрямую из Python, я обнаружил, что существующая библиотека pyhackrf работает только на Linux. Это подтолкнуло меня к доработке подхода для работы под Windows через прямое взаимодействие с DLL. Возможно, мой опыт кому-то пригодится.

Почему напрямую через DLL?

  • pyhackrf изначально заточен под Linux и использует libhackrf.so

  • На Windows libhackrf.so заменяется на hackrf.dll

  • Прямой вызов DLL через Python позволяет полностью контролировать устройство

  • Минимальные зависимости и прозрачность работы

  • Полная свобода в реализации нужного функционала

    Создание Python-библиотеки для HackRF

Код hackrf_dll.py

from ctypes import *
import os
import numpy as np

# Путь к DLL
dll_path = os.path.join(os.path.dirname(__file__), "hackrf.dll")
lib = CDLL(dll_path)

p_hackrf_device = c_void_p

# ==== Прототипы функций ====
lib.hackrf_init.restype = c_int
lib.hackrf_exit.restype = c_int
lib.hackrf_open.argtypes = [POINTER(p_hackrf_device)]
lib.hackrf_open.restype = c_int
lib.hackrf_close.argtypes = [p_hackrf_device]
lib.hackrf_close.restype = c_int

lib.hackrf_set_freq.argtypes = [p_hackrf_device, c_uint64]
lib.hackrf_set_sample_rate.argtypes = [p_hackrf_device, c_double]
lib.hackrf_set_amp_enable.argtypes = [p_hackrf_device, c_uint8]
lib.hackrf_set_lna_gain.argtypes = [p_hackrf_device, c_uint32]
lib.hackrf_set_vga_gain.argtypes = [p_hackrf_device, c_uint32]

# ==== RX структура ====
class hackrf_transfer(Structure):
    _fields_ = [
        ("device", c_void_p),
        ("buffer", POINTER(c_byte)),
        ("buffer_length", c_int),
        ("valid_length", c_int),
        ("rx_ctx", c_void_p),
        ("tx_ctx", c_void_p),
    ]

rx_callback_t = CFUNCTYPE(c_int, POINTER(hackrf_transfer))
tx_callback_t = CFUNCTYPE(c_int, POINTER(hackrf_transfer))

lib.hackrf_start_rx.argtypes = [p_hackrf_device, rx_callback_t, c_void_p]
lib.hackrf_stop_rx.argtypes = [p_hackrf_device]

lib.hackrf_start_tx.argtypes = [p_hackrf_device, tx_callback_t, c_void_p]
lib.hackrf_stop_tx.argtypes = [p_hackrf_device]


# ==== Класс HackRF ====
class HackRF:
    def __init__(self):
        self.dev = p_hackrf_device(None)
        if lib.hackrf_init() != 0:
            raise RuntimeError("HackRF init error")
        if lib.hackrf_open(pointer(self.dev)) != 0:
            raise RuntimeError("HackRF open error")
        print("[INFO] HackRF открыт!")

        self._rx_cb = None
        self._tx_cb = None

    # ---- Настройка ----
    def set_freq(self, freq_hz):
        if lib.hackrf_set_freq(self.dev, int(freq_hz)) != 0:
            raise RuntimeError("HackRF set_freq error")
        print(f"[INFO] Частота {freq_hz} Hz")

    def set_sample_rate(self, rate_hz):
        if lib.hackrf_set_sample_rate(self.dev, float(rate_hz)) != 0:
            raise RuntimeError("HackRF set_sample_rate error")
        print(f"[INFO] Частота дискретизации {rate_hz} Hz")

    def enable_amp(self, en=True):
        if lib.hackrf_set_amp_enable(self.dev, 1 if en else 0) != 0:
            raise RuntimeError("HackRF amp error")
        print(f"[INFO] Усилитель {'включен' if en else 'выключен'}")

    def set_lna_gain(self, gain):
        lib.hackrf_set_lna_gain(self.dev, int(gain))
        print(f"[INFO] LNA gain {gain}")

    def set_vga_gain(self, gain):
        lib.hackrf_set_vga_gain(self.dev, int(gain))
        print(f"[INFO] VGA gain {gain}")

    # ---- Приём ----
    def start_rx(self, callback):
        def rx_cb(trans_ptr):
            t = trans_ptr.contents
            length = t.valid_length
            if length <= 0:
                print("[RX] Пустой буфер")
                return 0
            buf = cast(t.buffer, POINTER(c_byte * length)).contents
            raw = np.frombuffer(buf, dtype=np.int8, count=length)
            iq = raw[0::2].astype(np.float32) + 1j * raw[1::2].astype(np.float32)

            print(f"[RX] Получено {len(iq)} сэмплов")
            if callback:
                callback(iq)
            return 0

        self._rx_cb = rx_callback_t(rx_cb)
        res = lib.hackrf_start_rx(self.dev, self._rx_cb, None)
        if res != 0:
            raise RuntimeError("hackrf_start_rx error")
        print("[INFO] Приём запущен")

    def stop_rx(self):
        lib.hackrf_stop_rx(self.dev)
        print("[INFO] Приём остановлен")

    # ---- Передача ----
    def start_tx(self, callback):
        def tx_cb(trans_ptr):
            t = trans_ptr.contents
            length = t.buffer_length
            if length <= 0:
                return 0

            buf = (c_byte * length).from_address(addressof(t.buffer.contents))

            if callback:
                iq = callback(length // 2)  # ожидаем комплексные отсчёты I/Q
                iq = np.array(iq, dtype=np.complex64)
                iq_i = np.clip(np.real(iq), -127, 127).astype(np.int8)
                iq_q = np.clip(np.imag(iq), -127, 127).astype(np.int8)
                interleaved = np.empty(length, dtype=np.int8)
                interleaved[0::2] = iq_i[:len(interleaved)//2]
                interleaved[1::2] = iq_q[:len(interleaved)//2]
                buf[:len(interleaved)] = interleaved

            return 0

        self._tx_cb = tx_callback_t(tx_cb)
        res = lib.hackrf_start_tx(self.dev, self._tx_cb, None)
        if res != 0:
            raise RuntimeError("hackrf_start_tx error")
        print("[INFO] Передача запущена")

    def stop_tx(self):
        lib.hackrf_stop_tx(self.dev)
        print("[INFO] Передача остановлена")

    # ---- Закрытие ----
    def close(self):
        lib.hackrf_close(self.dev)
        lib.hackrf_exit()
        print("[INFO] HackRF закрыт")

Пример использования библиотеки

from hackrf_dll import HackRF
import time

# Работа с HackRF через контекстный менеджер
with HackRF() as hackrf:
    hackrf.set_freq(433_920_000)   # Частота для радиоуправления RC
    hackrf.set_lna_gain(16)
    hackrf.set_vga_gain(16)
    hackrf.enable_amp()
    
    # Простейшая "тестовая передача" — пауза
    time.sleep(2)

Итоги

  • Работа напрямую с hackrf_dll даёт полный контроль и кроссплатформенную совместимость в Windows.

  • Мини-библиотека позволяет управлять частотой, усилением и включением передатчика, оставаясь лёгкой и прозрачной.

Полните что включать на передачу без антенны можно спалить передатчик. Так же антенна должна быть правильная для той частоты которую выбрали.

Комментарии (3)


  1. POST45DT
    08.09.2025 11:51

    Будет что попробовать


  1. stilrambler Автор
    08.09.2025 11:51

    В качестве более интересного примера прослушать с фм радио и сохранить в файл

    from hackrf_dll import HackRF
    import numpy as np
    import soundfile as sf
    from scipy.signal import resample, firwin, lfilter, butter, filtfilt
    
    SAMPLE_RATE = 2_000_000
    AUDIO_RATE = 48000
    CENTER_FREQ = 103_000_000 
    OUTPUT_FILE = "fm_audio.wav"
    
    def fm_demod(iq):
        # Простая и стабильная FM демодуляция
        iq_normalized = iq / (np.max(np.abs(iq)) + 1e-9)
        return np.angle(iq_normalized[1:] * np.conj(iq_normalized[:-1]))
    
    def remove_dc_offset(signal, alpha=0.99):
        """Удаление постоянной составляющей"""
        result = np.zeros_like(signal)
        result[0] = signal[0]
        for i in range(1, len(signal)):
            result[i] = alpha * result[i-1] + signal[i] - signal[i-1]
        return result
    
    def apply_lowpass(audio, cutoff_freq, sample_rate, order=5):
        """ФНЧ с butterworth фильтром"""
        nyquist = sample_rate / 2
        normal_cutoff = cutoff_freq / nyquist
        b, a = butter(order, normal_cutoff, btype='low', analog=False)
        return filtfilt(b, a, audio)
    
    def process_audio(audio, original_rate, target_rate):
        # Удаление DC смещения
        audio = remove_dc_offset(audio)
        
        # Фильтр низких частот
        audio = apply_lowpass(audio, 15000, original_rate)
        
        # Ресемплинг
        n_samples = int(len(audio) * target_rate / original_rate)
        audio_resampled = resample(audio, n_samples)
        
        # Нормализация
        max_val = np.max(np.abs(audio_resampled))
        if max_val > 0:
            audio_resampled = audio_resampled / max_val
        
        return audio_resampled
    
    # Основной код
    hackrf = HackRF()
    hackrf.set_freq(CENTER_FREQ)
    hackrf.set_sample_rate(SAMPLE_RATE)
    
    # Начните с умеренных усилений
    hackrf.set_lna_gain(16)
    hackrf.set_vga_gain(12)
    hackrf.enable_amp(True)
    
    iq_buffer = []
    
    def callback(iq):
        global iq_buffer
        try:
            demod = fm_demod(iq)
            iq_buffer.append(demod)
            print(f"[RX] Демодулировано {len(demod)} сэмплов")
        except Exception as e:
            print(f"Ошибка обработки: {e}")
    
    print("Начинаем прием...")
    hackrf.start_rx(callback)
    
    try:
        input("Нажмите Enter для остановки...\n")
    except KeyboardInterrupt:
        pass
    
    hackrf.stop_rx()
    hackrf.close()
    
    # Обработка данных
    if iq_buffer:
        audio = np.concatenate(iq_buffer)
        audio_processed = process_audio(audio, SAMPLE_RATE, AUDIO_RATE)
        
        sf.write(OUTPUT_FILE, audio_processed.astype(np.float32), AUDIO_RATE)
        print(f"[OK] Сохранено аудио в {OUTPUT_FILE}")
    else:
        print("[ERROR] Нет данных для обработки")
    


  1. G-leo
    08.09.2025 11:51

    Для hackrf существует библиотека python_hackrf которая работает на всех платформах ( в том числе и андроид). Работает как обертка cython над libhackrf