Skip to main content

Binary Files Parser resulted from the script Logger from CRFS - Rfeye Node Equipament

Project description

RFPYE

Este módulo tem como objetivo o processamento e extração otimizada de dados dos arquivos .bin de monitoramento do espectro provenientes do script Logger executados nas estações de Monitoramento CRFS RFeye Node. Para tal utilizamos as várias funcionalidades da biblioteca fastcore, que expande e otimiza as estruturas de dados da linguagem python.

import warnings
with warnings.catch_warnings():
    warnings.simplefilter("ignore")
from rich import print

Instalação

Ubuntu:

python -m pip install rfpye

Windows:

Como parte dessa lib utiliza código c compilado com Cython, é preciso que um compilador C esteja instalado. Em Windows, uma opção é instalar a versão apropriada do Visual Studio seguindo as orientações do site da Microsoft. No entanto uma solução mais simples e a recomendada é utilizando o conda.

Primeiramente instale o miniconda. Com o conda instalado e disponível no seu PATH ou através do Anaconda Prompt execute o comando:

conda install -c intel libpython m2w64-toolchain -y

echo [build] > %CONDA_PREFIX%\Lib\distutils\distutils.cfg

echo compiler = mingw32 >> %CONDA_PREFIX%\Lib\distutils\distutils.cfg

Depois disso basta instalar normalmente a lib: python -m pip install rfpye

Em Linux normalmente o sistema já possui o compilador gcc instalado então basta executar o comando pip install acima.

Como utilizar

Abaixo mostramos as funcionalidades principais dos módulos, utilizando-os dentro de algum outro script ou REPL

Precisamos necessariamente de um diretório de entrada, contendo um ou mais arquivos .bin e um diretório de saída no qual iremos salvar os arquivos processados.

Mude os caminhos abaixo para suas pastas locais caso for executar o exemplo.

Ao utilizar o script process_bin, as pastas entrada e saída esses serão repassadas como parâmetros na linha de comando.

from fastcore.xtras import Path
VERBOSE = True
entrada = Path(r'D:\OneDrive - ANATEL\Backup_Rfeye_SP\CGH\2021')
saida = Path(r'C:\Users\rsilva\Downloads\saida')

Leitura de Arquivos

No módulo parser.py, há funções auxiliares para lidar com os arquivos .bin, pastas e para processar tais arquivos em formatos úteis. Nesse caso utilizaremos a função get_files que busca de maneira recursiva arquivos de dada extensão, inclusive links simbólicos se existirem O caráter recursivo e a busca em links, recurse e followlinks simbólicos pode ser desativados por meio dos parâmetros e opcionalmente pode ser varrido somente o conjunto de pastas indicado em folders

from rfpye.utils import get_files
arquivos = get_files(entrada, extensions=['.bin']) ; print(arquivos[:10])
[Path('D:/OneDrive - 
ANATEL/Backup_Rfeye_SP/CGH/2021/rfeye002279-SP-Congonhas_210319_T160137.bin'), 
Path('D:/OneDrive - 
ANATEL/Backup_Rfeye_SP/CGH/2021/rfeye002279-SP-Congonhas_210325_T230001.bin'), 
Path('D:/OneDrive - 
ANATEL/Backup_Rfeye_SP/CGH/2021/rfeye002279-SP-Congonhas_210401_T060001.bin'), 
Path('D:/OneDrive - 
ANATEL/Backup_Rfeye_SP/CGH/2021/rfeye002279-SP-Congonhas_210404_T152752.bin'), 
Path('D:/OneDrive - 
ANATEL/Backup_Rfeye_SP/CGH/2021/rfeye002279-SP-Congonhas_210410_T222501.bin'), 
Path('D:/OneDrive - 
ANATEL/Backup_Rfeye_SP/CGH/2021/rfeye002279-SP-Congonhas_210416_T104037.bin'), 
Path('D:/OneDrive - 
ANATEL/Backup_Rfeye_SP/CGH/2021/rfeye002279-SP-Congonhas_210422_T113942.bin'), 
Path('D:/OneDrive - 
ANATEL/Backup_Rfeye_SP/CGH/2021/rfeye002279-SP-Congonhas_210428_T183501.bin'), 
Path('D:/OneDrive - 
ANATEL/Backup_Rfeye_SP/CGH/2021/rfeye002279-SP-Congonhas_210505_T013201.bin'), 
Path('D:/OneDrive - 
ANATEL/Backup_Rfeye_SP/CGH/2021/rfeye002279-SP-Congonhas_210511_T083001.bin')]

O Objeto retornado L é uma extensão da lista python com funcionalidades adicionais, uma delas como podemos ver é que a representação da lista impressa mostra o comprimento da lista. Esse objeto pode ser usado de maneira idêntica à uma lista em python e sem substituição desta.

bin_file = arquivos[-3] ; print(bin_file.name)
rfeye002279-SP-Congonhas_210516_T144208.bin
from rfpye.parser import parse_bin, extract_metadata, extract_level

Processamento dos blocos

A função seguinte parse_bin recebe um arquivo .bin e mapeia os blocos contidos nele retornando um dicionário que tem como chave o tipo de bloco e os valores como uma lista com os blocos extraídos sequencialmente.

%%time
map_bin = parse_bin(bin_file)['blocks']
Wall time: 5.26 s
for k, b in map_bin.items():
    print(f'Tipo de Bloco: {k[0]}, Fluxo (Thread ID): {k[1]}, #Blocos: {len(b)}')
Tipo de Bloco: 21, Fluxo (Thread ID): 0, #Blocos: 1
Tipo de Bloco: 42, Fluxo (Thread ID): 0, #Blocos: 2
Tipo de Bloco: 40, Fluxo (Thread ID): 1, #Blocos: 6341
Tipo de Bloco: 67, Fluxo (Thread ID): 300, #Blocos: 6341
Tipo de Bloco: 67, Fluxo (Thread ID): 310, #Blocos: 6341
Tipo de Bloco: 67, Fluxo (Thread ID): 100, #Blocos: 1268
Tipo de Bloco: 67, Fluxo (Thread ID): 110, #Blocos: 1268
Tipo de Bloco: 67, Fluxo (Thread ID): 130, #Blocos: 1268
Tipo de Bloco: 67, Fluxo (Thread ID): 210, #Blocos: 423
Tipo de Bloco: 67, Fluxo (Thread ID): 220, #Blocos: 423
Tipo de Bloco: 67, Fluxo (Thread ID): 230, #Blocos: 423
Tipo de Bloco: 67, Fluxo (Thread ID): 240, #Blocos: 423
Tipo de Bloco: 67, Fluxo (Thread ID): 320, #Blocos: 423
Tipo de Bloco: 67, Fluxo (Thread ID): 340, #Blocos: 423
Tipo de Bloco: 67, Fluxo (Thread ID): 360, #Blocos: 423
Tipo de Bloco: 67, Fluxo (Thread ID): 380, #Blocos: 423
Tipo de Bloco: 42, Fluxo (Thread ID): 1, #Blocos: 105
gps = map_bin[(40,1)]
spec = map_bin[(67,110)]

A seguir é mostrado um exemplo dos atributos contidos num bloco de gps e num bloco de espectro

from rfpye.utils import getattrs
print(getattrs(gps[0][1]))
{
    'altitude': 815.3,
    'data_size': 40,
    'gps_datetime': datetime.datetime(2021, 5, 16, 14, 43),
    'gps_status': 'Differential GPS',
    'heading': 0.0,
    'latitude': -23.635858,
    'longitude': -46.654255,
    'num_satellites': 12,
    'speed': 0.014,
    'thread_id': 1,
    'type': 40,
    'wallclock_datetime': numpy.datetime64('2021-05-16T14:43:00.532926')
}
print(getattrs(spec[0][1]))
{
    'antenna_id': 0,
    'bw': 40,
    'data_size': 1164,
    'data_type': 1,
    'desclen': 36,
    'description': 'PRD 2021 (Faixa principal 2 de 4).',
    'dynamic_id': 0,
    'gerror': -1,
    'gflags': -1,
    'group_id': 0,
    'minimum': -32.5,
    'n_agc': 7,
    'n_tunning': 7,
    'namal': 1,
    'ndata': 1024,
    'npad': 1,
    'offset': 95,
    'padding': 0,
    'processing': 'peak',
    'resolution_bw': 73828,
    'sample': 6230,
    'start': 139,
    'start_channel': 0,
    'start_mega': 70,
    'start_mili': 0,
    'step': 0.039100684261974585,
    'stop': 1163,
    'stop_channel': 0,
    'stop_mega': 110,
    'stop_mili': 0,
    'thread_id': 110,
    'type': 67,
    'wallclock_datetime': numpy.datetime64('2021-05-16T14:45:00.790881')
}

Metadados

A função seguinte extrai os metadados META definidos no cabeçalho do arquivo constants.py e retorna um DataFrame.

gps_meta = extract_metadata(gps)
gps_meta.head()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
start_byte stop_byte altitude data_size gps_datetime gps_status heading latitude longitude num_satellites speed thread_id type
wallclock_datetime
2021-05-16 14:43:00.532926 344 399 815.299988 40 2021-05-16 14:43:00 Differential GPS 0.0 -23.635859 -46.654255 12 0.014 1 40
2021-05-16 14:44:00.330070 5324 5379 817.299988 40 2021-05-16 14:43:59 Differential GPS 0.0 -23.635857 -46.654263 12 0.025 1 40
2021-05-16 14:45:00.752895 10304 10359 816.200012 40 2021-05-16 14:45:00 Differential GPS 0.0 -23.635859 -46.654266 12 0.007 1 40
2021-05-16 14:46:00.402776 86092 86147 815.299988 40 2021-05-16 14:46:00 Differential GPS 0.0 -23.635864 -46.654263 12 0.005 1 40
2021-05-16 14:47:00.302816 91072 91127 818.099976 40 2021-05-16 14:47:00 Differential GPS 0.0 -23.635859 -46.654266 12 0.025 1 40
spec_meta = extract_metadata(spec)
spec_meta.tail()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
start_byte stop_byte antenna_id bw data_size data_type desclen description dynamic_id gerror ... start_channel start_mega start_mili step stop stop_channel stop_mega stop_mili thread_id type
wallclock_datetime
2021-05-21 00:00:00.695624 69731068 69732247 0 40 1164 1 36 PRD 2021 (Faixa principal 2 de 4). 0 -1 ... 0 70 0 0.039101 1163 0 110 0 110 67
2021-05-21 00:05:00.635638 69826776 69827955 0 40 1164 1 36 PRD 2021 (Faixa principal 2 de 4). 0 -1 ... 0 70 0 0.039101 1163 0 110 0 110 67
2021-05-21 00:10:00.606404 69861632 69862811 0 40 1164 1 36 PRD 2021 (Faixa principal 2 de 4). 0 -1 ... 0 70 0 0.039101 1163 0 110 0 110 67
2021-05-21 00:15:00.355210 69896488 69897667 0 40 1164 1 36 PRD 2021 (Faixa principal 2 de 4). 0 -1 ... 0 70 0 0.039101 1163 0 110 0 110 67
2021-05-21 00:20:00.385654 69992196 69993375 0 40 1164 1 36 PRD 2021 (Faixa principal 2 de 4). 0 -1 ... 0 70 0 0.039101 1163 0 110 0 110 67

5 rows × 34 columns

Frequência e Nível

A função seguinte extrai as frequências e nível num formato de Tabela Dinâmica:

  • Colunas: Frequências (MHz)
  • Índice: Números de Bloco
  • Valores: Níveis (dBm ou dBuV/m)
levels = extract_level(spec, dtype='float16')
levels.head()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
70.000000 70.039101 70.078201 70.117302 70.156403 70.195503 70.234604 70.273705 70.312805 70.351906 ... 109.648094 109.687195 109.726295 109.765396 109.804497 109.843597 109.882698 109.921799 109.960899 110.000000
0 48.5 47.5 46.0 42.5 32.0 39.0 32.5 41.0 44.5 45.5 ... 34.0 37.0 38.5 38.0 32.5 28.0 36.0 34.5 27.5 34.5
1 35.5 35.5 36.5 46.0 43.0 44.5 47.0 45.5 40.0 38.5 ... 35.5 34.5 31.0 34.5 35.5 28.5 21.5 28.5 27.0 37.5
2 47.0 44.5 44.0 41.5 40.5 42.0 40.0 40.0 43.0 39.5 ... 31.0 38.0 41.5 38.5 31.0 31.0 31.0 39.5 39.5 37.5
3 40.0 43.0 42.0 46.5 45.5 40.5 35.0 35.0 37.5 37.0 ... 37.0 38.5 39.0 35.5 36.5 32.5 26.5 33.5 35.5 33.0
4 40.0 35.0 32.0 42.0 41.0 40.5 36.5 32.5 46.0 47.5 ... 31.0 28.0 41.0 42.5 39.0 31.0 25.0 16.5 23.0 23.0

5 rows × 1024 columns

Processamento, Extração e Salvamento dos Metadados e Espectro

A função a seguir é um wrapper de toda funcionalidade desta biblioteca. Ela recebe o caminho entrada para um arquivo .bin ou pasta contendo vários arquivos .bin, extrai os metadados e os dados de espectro. Mescla o timestamp dos metadados com o arquivo de espectro e salva ambos na pasta saida. Essa pasta é usada como repositório e cache dos dados processados que serão utilizados pela função extract_bin_stats.

from rfpye.filter import process_bin, extract_bin_stats
process_bin(bin_file, saida)
─────────────────────────── Lista de Arquivos a serem processados ───────────────────────────
['rfeye002279-SP-Congonhas_210516_T144208.bin']                                              
Processando Blocos de: rfeye002279-SP-Congonhas_210516_T144208.bin
Extraindo Metadados de: rfeye002279-SP-Congonhas_210516_T144208.bin

kbô 😆

Se chamarmos a função novamente:

process_bin(bin_file, saida)
─────────────────────────── Lista de Arquivos a serem processados ───────────────────────────
['rfeye002279-SP-Congonhas_210516_T144208.bin']                                              
😴 Nenhum arquivo novo a processar 💤
☝ use --substituir no terminal ou substituir=True na chamada caso queira reprocessar os bins 
e sobrepôr os arquivos existentes 😉

Como vemos pela mensagem de saída, nada foi feito porque esse arquivo já foi processado anteriormente e todos os arquivos de metadados e espectros presentes já foram salvos na pasta saida

Resumo do arquivo

Se o que interessa é somente os dados estatísticos do arquivo como Min, Max e Mean basta utilizar:

stats = extract_bin_stats(bin_file, cache=saida)
stats
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
Frequency Min Max Mean
0 50.000000 18.0 48.5 37.81250
1 50.039101 10.5 49.0 37.62500
2 50.078201 8.5 48.5 37.59375
3 50.117302 17.5 49.5 37.96875
4 50.156403 17.5 50.0 38.00000
... ... ... ... ...
43003 5159.843750 -128.5 -104.0 -112.43750
43004 5159.882812 -125.0 -103.5 -112.25000
43005 5159.921875 -127.0 -101.5 -111.37500
43006 5159.960938 -123.5 -101.0 -111.12500
43007 5160.000000 -125.5 -103.5 -112.06250

43008 rows × 4 columns

Podemos filtrar o intervalo tanto de frequência quanto de tempo da extração:

stats = extract_bin_stats(bin_file, cache=saida, freq_start=88.1, freq_stop=107.9)
print(stats.head(20))
    Frequency   Min   Max      Mean
0   88.123169  69.0  96.0  89.43750
1   88.162270  65.0  96.0  89.43750
2   88.201370  53.5  96.0  83.62500
3   88.240471  46.5  95.5  83.81250
4   88.279572  37.5  95.0  71.93750
5   88.318672  34.0  94.0  72.37500
6   88.357773  28.0  87.0  57.40625
7   88.396873  21.5  85.0  57.90625
8   88.435974  24.0  71.0  46.00000
9   88.475075  24.5  70.0  46.40625
10  88.514175  12.5  55.0  40.71875
11  88.553276   9.5  55.0  40.93750
12  88.592377  20.0  53.5  38.06250
13  88.631477  17.5  56.5  38.28125
14  88.670578  16.5  60.0  41.59375
15  88.709679  10.0  60.5  41.90625
16  88.748779  12.5  62.0  49.96875
17  88.787880  17.5  62.5  50.25000
18  88.826981  28.0  64.0  54.37500
19  88.866081  31.5  63.5  54.68750
print(stats.tail(20))
      Frequency    Min   Max      Mean
833  107.705833 -133.0 -64.0 -90.00000
834  107.715599 -120.5 -62.0 -88.81250
835  107.725372 -117.5 -60.5 -88.00000
836  107.735138   19.0  75.0  46.31250
837  107.744904 -128.5 -58.5 -86.68750
838  107.754677 -121.0 -57.0 -84.31250
839  107.764442 -116.5 -49.0 -80.68750
840  107.774208 -120.5 -41.0 -76.37500
841  107.783981   18.0  79.5  59.62500
842  107.793747 -121.5 -38.5 -72.37500
843  107.803520 -115.0 -38.5 -69.00000
844  107.813286 -116.5 -38.5 -66.68750
845  107.823051 -118.0 -40.5 -65.12500
846  107.832825   25.0  79.5  69.31250
847  107.842590 -124.5 -42.5 -62.59375
848  107.852356 -111.0 -41.5 -59.12500
849  107.862129 -119.5 -39.5 -55.75000
850  107.871895 -110.5 -38.0 -53.15625
851  107.881660   43.0  81.5  73.25000
852  107.891434 -111.0 -37.0 -51.71875

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rfpye-0.1.0.tar.gz (218.2 kB view hashes)

Uploaded Source

Built Distribution

rfpye-0.1.0-cp37-cp37m-win_amd64.whl (210.2 kB view hashes)

Uploaded CPython 3.7m Windows x86-64

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page