Skip to content

Commit 6f83615

Browse files
committed
update readme with examples
1 parent ab617f3 commit 6f83615

File tree

1 file changed

+279
-3
lines changed

1 file changed

+279
-3
lines changed

README.md

Lines changed: 279 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ Working on it:
4040
* [Getting Data from Active Screen](#getting-data-from-active-screen)
4141
* [Saving Screen Images](#saving-screen-images)
4242
* [Plotting Data with Matplotlib](#plotting-data-with-matplotlib)
43+
* [Example 1: Plot using On-Screen Trace Data and Frequencies](#example-1-plot-using-on-screen-trace-data-and-frequencies)
44+
* [Example 2: Plot using Scan Data and Frequencies](#example-2-plot-using-scan-data-and-frequencies)
45+
* [Example 3: Plot using SCAN and SCANRAW Data and Calculated Frequencies](#example-3-plot-using-scan-and-scanraw-data-and-calculated-frequencies)
46+
* [Example 4: Plot a Waterfall using SCAN and Calculated Frequencies](#example-4-plot-a-waterfall-using-scan-and-calculated-frequencies)
47+
* [Saving SCAN Data to CSV](#saving-scan-data-to-csv)
4348
* [Accessing the tinySA Directly](#accessing-the-tinysa-directly)
4449
* [List of tinySA Commands and their Library Commands](#list-of-tinysa-commands-and-their-library-commands)
4550
* [List of Commands Removed from Library](#list-of-commands-removed-from-library)
@@ -501,7 +506,7 @@ else: # port open, complete task(s) and disconnect
501506

502507
### Plotting Data with Matplotlib
503508

504-
**Example 1: Plot using On-Screen Trace Data and Frequencies**
509+
#### **Example 1: Plot using On-Screen Trace Data and Frequencies**
505510
This example plots the last/current sweep of data from the tinySA device.
506511
`data()` gets the trace data. `frequencies()` gets the frequency values used.
507512
`byteArrayToNumArray(byteArr)` takes in the returned trace data and frequency
@@ -577,7 +582,7 @@ else: # port open, complete task(s) and disconnect
577582
<p align="center">Plotted On-Screen Trace Data of a Frequency Sweep from 100 kHz to 800 MHz</p>
578583

579584

580-
**Example 2: Plot using Scan Data and Frequencies**
585+
#### **Example 2: Plot using Scan Data and Frequencies**
581586

582587
This example uses `scan()` to take a data measurement of data that DOES NOT need to been on the screen, unlike **Example 1** above. Then, the frequencies on the x-axis are calculated between the `start` and `stop` frequencies using the `number of points`. This is done because `frequencies()` would have the values of the last scan, which are connected to `RBW` and not the `number of points`.
583588

@@ -667,7 +672,7 @@ else: # if port found and connected, then complete task(s) and disconnect
667672
<p align="center">Plotted Scan Data of a Frequency Sweep from 1 GHz to 3 GHz</p>
668673

669674

670-
**Example 3: Plot using SCAN and SCANRAW Data and Calculated Frequencies**
675+
#### **Example 3: Plot using SCAN and SCANRAW Data and Calculated Frequencies**
671676

672677
This example uses `scan()` and `scanraw()` to take a data measurement of data that DOES NOT need to been on the screen, unlike **Example 1** above. Then, the frequencies on the x-axis are calculated between the `start` and `stop` frequencies using the `number of points`. This is done because `frequencies()` would have the values of the last scan, which are connected to `RBW` and not the `number of points`.
673678

@@ -824,6 +829,277 @@ else: # if port found and connected, then complete task(s) and disconnect
824829

825830

826831

832+
#### **Example 4: Plot a Waterfall using SCAN and Calculated Frequencies**
833+
834+
```python
835+
# import tinySA library
836+
# (NOTE: check library path relative to script path)
837+
from src.tinySA_python import tinySA
838+
839+
# imports FOR THE EXAMPLE
840+
import csv
841+
import numpy as np
842+
import matplotlib.pyplot as plt
843+
import time
844+
from datetime import datetime
845+
846+
def convert_data_to_arrays(start, stop, pts, data):
847+
# using the start and stop frequencies, and the number of points,
848+
freq_arr = np.linspace(start, stop, pts) # note that the decimals might go out to many places.
849+
# you can truncate this because its only used
850+
# for plotting in this example
851+
# As of the Jan. 2024 build in some data returned with SWEEP or SCAN calls there is error data.
852+
# https://groups.io/g/tinysa/topic/tinasa_ultra_sweep_command/104194367
853+
# this shows up as "-:.000000e+01".
854+
# TEMP fix - replace the colon character with a -10. This puts the 'filled in' points around the noise floor.
855+
# more advanced filtering should be applied for actual analysis.
856+
857+
data1 = bytearray(data.replace(b"-:.0", b"-10.0"))
858+
859+
# get both values in each row returned (for reference)
860+
#data_arr = [list(map(float, line.split())) for line in data.decode('utf-8').split('\n') if line.strip()]
861+
862+
# get first value in each returned row
863+
data_arr = [float(line.split()[0]) for line in data1.decode('utf-8').split('\n') if line.strip()]
864+
return freq_arr, data_arr
865+
866+
def collect_waterfall_data(tsa, start, stop, pts, outmask, num_scans, scan_interval):
867+
868+
waterfall_data = [] # 2D array of scan data (time x frequency)
869+
timestamps = []
870+
freq_arr = None
871+
872+
print(f"Collecting {num_scans} scans with {scan_interval}s intervals...")
873+
874+
for i in range(num_scans):
875+
print(f"Scan {i+1}/{num_scans}")
876+
877+
# Perform scan
878+
data_bytes = tsa.scan(start, stop, pts, outmask)
879+
880+
# Convert to arrays
881+
if freq_arr is None:
882+
freq_arr, data_arr = convert_data_to_arrays(start, stop, pts, data_bytes)
883+
else:
884+
_, data_arr = convert_data_to_arrays(start, stop, pts, data_bytes)
885+
886+
# Store data and timestamp
887+
waterfall_data.append(data_arr)
888+
timestamps.append(datetime.now())
889+
890+
# Wait before next scan (except for last scan)
891+
if i < num_scans - 1:
892+
time.sleep(scan_interval)
893+
894+
return freq_arr, np.array(waterfall_data), timestamps
895+
896+
def plot_waterfall(freq_arr, waterfall_data, timestamps, start, stop):
897+
# Create figure with subplots
898+
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10))
899+
900+
# Waterfall plot (main plot)
901+
# Create time array for y-axis (scan number or elapsed time)
902+
time_arr = np.arange(len(timestamps))
903+
904+
# Create meshgrid for pcolormesh
905+
freq_mesh, time_mesh = np.meshgrid(freq_arr, time_arr)
906+
907+
# Plot waterfall
908+
im = ax1.pcolormesh(freq_mesh/1e9, time_mesh, waterfall_data,
909+
shading='nearest', cmap='viridis')
910+
911+
ax1.set_xlabel('Frequency (GHz)')
912+
ax1.set_ylabel('Scan Number')
913+
ax1.set_title(f'Waterfall Plot: {start/1e9:.1f} - {stop/1e9:.1f} GHz')
914+
915+
# Add colorbar
916+
cbar = plt.colorbar(im, ax=ax1)
917+
cbar.set_label('Signal Strength (dBm)')
918+
919+
# Latest scan plot (bottom subplot)
920+
ax2.plot(freq_arr/1e9, waterfall_data[-1])
921+
ax2.set_xlabel('Frequency (GHz)')
922+
ax2.set_ylabel('Signal Strength (dBm)')
923+
ax2.set_title('Latest Scan')
924+
ax2.grid(True, alpha=0.3)
925+
926+
plt.tight_layout()
927+
return fig
928+
929+
# create a new tinySA object
930+
tsa = tinySA()
931+
# set the return message preferences
932+
tsa.set_verbose(True) #detailed messages
933+
tsa.set_error_byte_return(True) #get explicit b'ERROR' if error thrown
934+
935+
# attempt to autoconnect
936+
found_bool, connected_bool = tsa.autoconnect()
937+
938+
# if port closed, then return error message
939+
if connected_bool == False:
940+
print("ERROR: could not connect to port")
941+
else: # if port found and connected, then complete task(s) and disconnect
942+
try:
943+
# set scan values
944+
start = int(1e9) # 1 GHz
945+
stop = int(3e9) # 3 GHz
946+
pts = 450 # sample points
947+
outmask = 2 # get measured data (y axis)
948+
949+
# waterfall parameters
950+
num_scans = 50 # number of scans to collect
951+
scan_interval = 0.5 # seconds between scans
952+
953+
# collect waterfall data
954+
freq_arr, waterfall_data, timestamps = collect_waterfall_data(
955+
tsa, start, stop, pts, outmask, num_scans, scan_interval)
956+
957+
print("Data collection complete!")
958+
959+
# resume and disconnect
960+
tsa.resume() #resume so screen isn't still frozen
961+
tsa.disconnect()
962+
963+
# processing after disconnect
964+
print("Creating waterfall plot...")
965+
966+
# create waterfall plot
967+
fig = plot_waterfall(freq_arr, waterfall_data, timestamps, start, stop)
968+
969+
# Save data out to .csv
970+
filename = "waterfall_1_sample.csv"
971+
972+
# Create CSV with frequency headers and time/scan data
973+
with open(filename, 'w', newline='') as csvfile:
974+
writer = csv.writer(csvfile)
975+
976+
# Write header row with frequencies (in Hz)
977+
header = ['Scan_Number', 'Timestamp'] + [f'{freq:.0f}' for freq in freq_arr]
978+
writer.writerow(header)
979+
980+
# Write data rows
981+
for i, (scan_data, timestamp) in enumerate(zip(waterfall_data, timestamps)):
982+
row = [i+1, timestamp.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]] + scan_data.tolist()
983+
writer.writerow(row)
984+
985+
print(f"Data saved to {filename}")
986+
print(f"CSV contains {len(waterfall_data)} scans with {len(freq_arr)} frequency points each")
987+
988+
# show plot
989+
plt.show()
990+
991+
except KeyboardInterrupt:
992+
print("\nScan interrupted by user")
993+
tsa.resume()
994+
tsa.disconnect()
995+
except Exception as e:
996+
print(f"Error occurred: {e}")
997+
tsa.resume()
998+
tsa.disconnect()
999+
```
1000+
<p align="center">
1001+
<img src="media/example4_waterfall_1.png" alt="Waterfall Plot for SCAN Data Over 50 Readings" height="350">
1002+
</p>
1003+
<p align="center">Waterfall Plot for SCAN Data Over 50 Readings</p>
1004+
1005+
1006+
### Saving SCAN Data to CSV
1007+
1008+
```python
1009+
1010+
# import tinySA library
1011+
# (NOTE: check library path relative to script path)
1012+
from src.tinySA_python import tinySA
1013+
1014+
1015+
# imports FOR THE EXAMPLE
1016+
import csv
1017+
import numpy as np
1018+
1019+
def convert_data_to_arrays(start, stop, pts, data):
1020+
# using the start and stop frequencies, and the number of points,
1021+
1022+
freq_arr = np.linspace(start, stop, pts) # note that the decimals might go out to many places.
1023+
# you can truncate this because its only used
1024+
# for plotting in this example
1025+
1026+
# As of the Jan. 2024 build in some data returned with SWEEP or SCAN calls there is error data.
1027+
# https://groups.io/g/tinysa/topic/tinasa_ultra_sweep_command/104194367
1028+
# this shows up as "-:.000000e+01".
1029+
# TEMP fix - replace the colon character with a -10. This puts the 'filled in' points around the noise floor.
1030+
# more advanced filtering should be applied for actual analysis.
1031+
1032+
data1 =bytearray(data.replace(b"-:.0", b"-10.0"))
1033+
1034+
# get both values in each row returned (for reference)
1035+
#data_arr = [list(map(float, line.split())) for line in data.decode('utf-8').split('\n') if line.strip()]
1036+
1037+
# get first value in each returned row
1038+
data_arr = [float(line.split()[0]) for line in data1.decode('utf-8').split('\n') if line.strip()]
1039+
1040+
return freq_arr, data_arr
1041+
1042+
1043+
# create a new tinySA object
1044+
tsa = tinySA()
1045+
1046+
# set the return message preferences
1047+
tsa.set_verbose(True) #detailed messages
1048+
tsa.set_error_byte_return(True) #get explicit b'ERROR' if error thrown
1049+
1050+
# attempt to autoconnect
1051+
found_bool, connected_bool = tsa.autoconnect()
1052+
1053+
# if port closed, then return error message
1054+
if connected_bool == False:
1055+
print("ERROR: could not connect to port")
1056+
else: # if port found and connected, then complete task(s) and disconnect
1057+
# set scan values
1058+
start = int(1e9) # 1 GHz
1059+
stop = int(3e9) # 3 GHz
1060+
pts = 450 # sample points
1061+
outmask = 2 # get measured data (y axis)
1062+
1063+
# scan
1064+
data_bytes = tsa.scan(start, stop, pts, outmask)
1065+
1066+
print(data_bytes)
1067+
1068+
tsa.resume() #resume so screen isn't still frozen
1069+
1070+
tsa.disconnect()
1071+
1072+
# processing after disconnect (just for this example)
1073+
1074+
# convert data to 2 arrays
1075+
freq_arr, data_arr = convert_data_to_arrays(start, stop, pts, data_bytes)
1076+
1077+
1078+
# Save the data to CSV
1079+
filename = "scan_sample.csv"
1080+
1081+
# Write out to csv where column 1 is frequency and col 2 is data
1082+
with open(filename, 'w', newline='') as csvfile:
1083+
writer = csv.writer(csvfile)
1084+
1085+
# Write header row
1086+
writer.writerow(['Frequency_Hz', 'Signal_Strength_dBm'])
1087+
1088+
# Write data rows (frequency, signal strength pairs)
1089+
for freq, signal in zip(freq_arr, data_arr):
1090+
writer.writerow([f'{freq:.0f}', signal])
1091+
1092+
print(f"Data saved to {filename}")
1093+
print(f"CSV contains {len(freq_arr)} frequency/signal pairs")
1094+
1095+
1096+
print(f"Data saved to {filename}")
1097+
1098+
1099+
```
1100+
1101+
1102+
8271103
### Accessing the tinySA Directly
8281104

8291105
In some cases, this library may not cover all possible command versions, or new features might not be included yet. The tinySA can be accessed directly using the `command()` function. There is NO ERROR CHECKING on this function. It takes the full argument, just as if arguments were entered on the command line.

0 commit comments

Comments
 (0)