1+ #! /usr/bin/python3
2+
3+ ##------------------------------------------------------------------------------------------------\
4+ # tinySA_python (tsapython)
5+ # './tests/test_hardware.py'
6+ # UNOFFICIAL Python API based on the tinySA official documentation at https://www.tinysa.org/wiki/
7+ #
8+ # Hardware tests for tsapython library. This REQUIRES a serial connected tinySA device
9+ #
10+ # Run with: python tests/test_hardware.py
11+ #
12+ #
13+ # Author(s): Lauren Linkous
14+ # Last update: August 17, 2025
15+ ##--------------------------------------------------------------------------------------------------\
16+
17+
18+ def test_device_connection ():
19+ """Test basic device connection.
20+ Can we even detect/connect to a tinySA device via serial"""
21+
22+ print ("Testing device connection..." )
23+ try :
24+ from tsapython import tinySA
25+
26+ # create obj and set the prefered test configs
27+ tsa = tinySA ()
28+ tsa .set_verbose (True )
29+ tsa .set_error_byte_return (True )
30+
31+ found_bool ,connected_bool = tsa .autoconnect ()
32+
33+ if connected_bool :
34+ print ("PASS! Device connected successfully" )
35+ tsa .disconnect ()
36+ return True
37+ else :
38+ print ("FAIL. Could not connect to device (this is OK if no hardware)" )
39+ return False
40+
41+ except Exception as e :
42+ print (f"FAIL. Connection test failed:{ e } " )
43+ return False
44+
45+
46+ def test_device_info ():
47+ """Test device information retrieval.
48+ All devices should be able to return a device ID if they are properly connected.
49+ This works will all models"""
50+
51+ print ("Testing device info retrieval..." )
52+ try :
53+ from tsapython import tinySA
54+
55+ # create obj and set the prefered test configs
56+ tsa = tinySA ()
57+ tsa .set_verbose (True )
58+ tsa .set_error_byte_return (True )
59+
60+ found_bool ,connected_bool = tsa .autoconnect ()
61+
62+ if not connected_bool :
63+ print ("FAIL. No device connected - skipping device info test" )
64+ return False
65+
66+ # Test device ID
67+ device_id = tsa .get_device_id ()
68+ if device_id :
69+ print (f"PASS! Device ID:{ device_id } " )
70+ else :
71+ print ("FAIL. Could not get device ID" )
72+ tsa .disconnect ()
73+ return False
74+
75+ # Test device info
76+ info = tsa .info ()
77+ if info :
78+ print (f"PASS! Device Info:{ info } " )
79+ else :
80+ print ("FAIL. Could not get device info" )
81+ tsa .disconnect ()
82+ return False
83+
84+ tsa .disconnect ()
85+ print ("PASS! Device info test passed" )
86+ return True
87+
88+ except Exception as e :
89+ print (f"FAIL. Device info test failed:{ e } " )
90+ return False
91+
92+
93+ def test_data_collection ():
94+ """Test basic data collection. Hop works pretty consistently for pulling data at intervals"""
95+
96+ print ("Testing data collection..." )
97+ try :
98+ from tsapython import tinySA
99+
100+ # create obj and set the prefered test configs
101+ tsa = tinySA ()
102+ tsa .set_verbose (True )
103+ tsa .set_error_byte_return (True )
104+
105+ found_bool ,connected_bool = tsa .autoconnect ()
106+
107+ if not connected_bool :
108+ print ("FAIL. No device connected - skipping data collection test" )
109+ return False
110+
111+ # Test parameters
112+ start_freq = 100e6 # 100 MHz
113+ stop_freq = 500e6 # 500 MHz
114+ n_pts = 12 # Just 11 points for quick test
115+
116+ # Pause for consistent measurements
117+ tsa .pause ()
118+
119+ # Get frequency data
120+ freq_vals = tsa .hop (start_freq ,stop_freq ,n_pts ,1 )
121+ if not freq_vals :
122+ print ("FAIL. Could not collect frequency data" )
123+ tsa .disconnect ()
124+ return False
125+
126+ # Get power data
127+ dbm_vals = tsa .hop (start_freq ,stop_freq ,n_pts ,2 )
128+ if not dbm_vals :
129+ print ("FAIL. Could not collect power data" )
130+ tsa .disconnect ()
131+ return False
132+
133+ # Validate data
134+ freq_list = [float (x )for x in freq_vals .decode ('utf-8' ).split ()]
135+ power_list = [float (x )for x in dbm_vals .decode ('utf-8' ).split ()]
136+
137+ print (power_list )
138+
139+ if len (freq_list )!= (n_pts + 1 )or len (power_list )!= (n_pts + 1 ):
140+ print (f"FAIL. Data length mismatch: got{ len (freq_list )} freq,{ len (power_list )} power, expected{ n_pts } " )
141+ tsa .disconnect ()
142+ return False
143+
144+ print (f"PASS! Collected{ len (freq_list )} frequency points and{ len (power_list )} power measurements." )
145+ print ("NOTE: it's expected to return 1 MORE data point than requested due to the initial frequency point being included!" )
146+
147+ tsa .disconnect ()
148+ return True
149+
150+ except Exception as e :
151+ print (f"FAIL. Data collection test failed:{ e } " )
152+ return False
153+
154+
155+ def run_all_hardware_tests ():
156+ """Run all hardware tests and report results."""
157+
158+
159+ print ("=" * 50 )
160+ print ("RUNNING HARDWARE TESTS (Requires Connected tinySA Device)" )
161+ print ("=" * 50 )
162+
163+ # call by function names in this file, NOT file names in the test directory
164+ tests = [
165+ test_device_connection ,
166+ test_device_info ,
167+ test_data_collection
168+ ]
169+
170+ passed = 0
171+ total = len (tests )
172+ skipped = 0
173+
174+ for test_func in tests :
175+ result = test_func ()
176+ if result :
177+ passed += 1
178+ elif "skipping" in str (result ):
179+ skipped += 1
180+ print ("-" * 30 )
181+
182+ print (f"\n RESULTS:{ passed } /{ total } tests passed" )
183+ if skipped > 0 :
184+ print (f"Note:{ skipped } tests were skipped (no hardware)" )
185+
186+ if passed > 0 :
187+ print ("Hardware tests completed!" )
188+ return True
189+ else :
190+ print ("ERROR: No hardware tests passed (check device connection)" )
191+ return False
192+
193+
194+ if __name__ == "__main__" :
195+ # this will run hardware tests when the file is executed directly.
196+ # this call does not happen anywhere in the core library, so it needs to be deliberate
197+
198+ success = run_all_hardware_tests ()
199+
200+ # Don't exit with error if just no hardware. That's OK.
201+ # It just means that the hardware was not detected
202+ # The library can still be operating fine if the test_basic.py tests are used to check