adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
MIT License
3.96k stars 1.16k forks source link

Unwanted CPY statusbar output in between REPL output of a running script #7111

Closed PaulskPt closed 8 months ago

PaulskPt commented 1 year ago

CircuitPython version

Adafruit CircuitPython 8.0.0-beta.3-11-gedce717cf on 2022-10-23; FeatherS3 with ESP32S3

Code/REPL

code ```python # SPDX-FileCopyrightText: 2022 Paulus Schulinck # # SPDX-License-Identifier: MIT ############################## """ This CircuitPython script is created to show how I managed to sort received WiFi Access Points in one of the following sorting orders: 1) on ssid; 2) on channel; 3) on rssi. See function 'pr_scanned_ap()'. This script also contains the function 'ck_double()' that I created to prevent listing of 'double Access Points'. I experienced that the list of AP's contained AP's with the same SSID, on the same CHANNEL but with a slight different RSSI value. In the function ck_doubles() AP's with a same SSID on the same CHANNEL within +/- 5 points of RSSI difference will be regarded as 'double' and will not be added to the list of found AP's. Note: the CircuitPython (V8.x) status_bar is set ON/off in file: boot.py This script is tested successfully on an Unexpected Maker ProS3 and FeatherS3. Want to see more of my work: Github @PaulskPt """ import time import sys import board import wifi import ipaddress import socketpool from collections import OrderedDict my_debug = False # Set to True if you need debug output to REPL use_ping = True start = True ap_dict = {} # dictionary of WiFi access points ap_cnt = 0 ip = None s_ip = None le_s_ip = 0 tz_offset = 0 pool = None id = board.board_id print() # Cause REPL output be on a line under the status_bar print(f"WiFi test for board: \'{id}\'") print("waiting 5 seconds...") time.sleep(5) if my_debug: if wifi is not None: print(f"wifi= {type(wifi)}") try: from secrets import secrets except ImportError: print("WiFi secrets are kept in secrets.py, please add them there!") raise ssid = secrets["ssid"] pw = secrets["password"] if id == 'unexpectedmaker_feathers3': use_neopixel = True import feathers3 import neopixel my_brightness = 0.005 BLK = 0 RED = 1 GRN = 200 else: use_neopixel = False my_brightness = None BLK = None RED = None GRN = None """ * @brief In this version of CircuitPython one can only check if there is a WiFi connection * by checking if an IP address exists. * In the function do_connect() the global variable s_ip is set. * * @param None * * @return boolean. True if exists an ip address. False if not. """ def wifi_is_connected(): ip = wifi.radio.ipv4_address if ip is None: return False else: s__ip = str(ip) return True if s__ip is not None and len(s__ip) > 0 and s__ip != '0.0.0.0' else False """ * @brief this function sets the WiFi.AuthMode. Then the function calls the function do_connect() * to establish a WiFi connection. * * @param None * * @return None """ def setup(): global pixels, my_brightness # Create a colour wheel index int color_index = 0 if id == 'unexpectedmaker_feathers3': try: # Turn on the power to the NeoPixel feathers3.set_ldo2_power(True) if use_neopixel: pixels = neopixel.NeoPixel(board.NEOPIXEL, 1) #for i in range(len(pixels)): # pixels[i] = RED neopixel.NeoPixel.brightness = my_brightness r,g,b = feathers3.rgb_color_wheel( BLK ) pixels[0] = ( r, g, b, my_brightness) pixels.write() except ValueError: pass wifi.AuthMode.WPA2 # set only once """ * @brief function checks for double ssid's while the list of received WiFi * Access Points is being scanned by function do_scan() * It copies the global ap_dict (to not 'interfere' while traversing * this dictionary). * If netw.ssid occurs in the dictionary and if netw.channel is the same as the one in the dictionary * and if the netw.rssi has a value in a range from -5 >= rssi value of AP in the dictionary <= +5 * then the netw object of the parameter is regarded as 'double'. The function will return a value of True * Otherwise the function returns a False. * * @param netw: network object: containing ssid, rssi and channel number the WiFi Access Point received * * @return Boolean: True if a 'double' AP has been found. False if no double found for the given AP """ def ck_double(netw): global ap_dict t_dict = ap_dict le = len(t_dict) ret = False if le > 0: for _ in range(le): s = netw.ssid rs = netw.rssi ch = netw.channel # Double if rssi is within +- 5 with same ssid on same channel # because it happened that the same ssid on the same channel had a small difference in rssi # (maybe because I moved my hands near the microcontroller) if s == t_dict[_]["ssid"] and (rs >= t_dict[_]["rssi"]-5 and rs <= t_dict[_]["rssi"]+5) and ch == t_dict[_]["channel"]: ret = True break return ret """ * @brief function starts scanning for Access Points received. * This function, while scanning for Access Points, calls function ck_double() on each iteration * It fills the global dictionary variable ap_dict. * * @param None * * @return None """ def do_scan(): global ap_cnt, ap_dict TAG = "do_scan(): " wifi.radio.stop_scanning_networks() # Prevent RuntimeError Already scanning networks try: for network in wifi.radio.start_scanning_networks(): if not ck_double(network): ap_dict[ap_cnt] = {"ap_nr": ap_cnt+1, "ssid": network.ssid, "rssi": network.rssi, "channel": network.channel} ap_cnt += 1 wifi.radio.stop_scanning_networks() if my_debug: print("ap_dict=", ap_dict) except RuntimeError as e: print(TAG+f"Error: {e}") return """ * @brief function prints to REPL a sorted list of found Access Points. * Available sort orders are: 0: 'ap_nr' (an integer count value assigned by the do_scan() function; * 1: rssi; * 2: channel number. * * @param integer: sort_order * * @return None """ # Note: I increased the 'width' for ssid to 32 characters # because I found in the listing a Printer that had a long name of 31 characters def pr_scanned_ap(sort_order: int=0): TAG = "pr_scanned_ap(): " sort_dict = {0: 'ap_nr', 1: 'rssi', 2: 'channel'} le = len(sort_dict) n_max = le-1 if sort_order >= 0 and sort_order <= n_max: sort_on = sort_dict[sort_order] else: s = "Value sort_order must be between 0 and {}".format(n_max) raise ValueError(s) print(TAG+"\nAvailable WiFi networks (sorted on: \'{}\')".format(sort_on)) if len(ap_dict) > 0: if sort_on == 'ap_nr': od = OrderedDict(sorted(ap_dict.items(), key= lambda x: x[1]['ap_nr'], reverse=False)) for k, v in od.items(): s = "\tAP nr {:2d}\tSSID: {:32s}\tRSSI: {:d}\tChannel: {:2d}".format(v["ap_nr"], v["ssid"], v["rssi"], v["channel"]) print(s) elif sort_on == 'rssi' or sort_on == 'channel': od = OrderedDict(sorted(ap_dict.items(), key= lambda x: x[1]['ap_nr'], reverse=False)) # results in TypeError: 'int' object is not iterable t_dict ={} for i in od.items(): # print("od.item[i]=", i) if sort_on == 'rssi': t_dict[i[0]] = str(i[1]['rssi']) elif sort_on == 'channel': t_dict[i[0]] = "{:02d}".format(i[1]['channel']) if len(t_dict) > 0: # print("t_dict =", t_dict) od2 = OrderedDict(sorted(t_dict.items(), key= lambda x: x[1], reverse=False)) for k, v in od2.items(): if sort_on == 'rssi': s = "\tAP nr {:2d}\tSSID: {:32s}\tRSSI: {:s}\tChannel: {:2d}".format(ap_dict[k]["ap_nr"], ap_dict[k]["ssid"], v, ap_dict[k]["channel"]) elif sort_on == 'channel': s = "\tAP nr {:2d}\tSSID: {:32s}\tRSSI: {:d}\tChannel: {:s}".format(ap_dict[k]["ap_nr"], ap_dict[k]["ssid"], ap_dict[k]["rssi"], v) print(s) else: print("Sorting selection \'{}\' unknown".format(sort_on)) else: print("Dictionary of scanned access points (ap_dict) is empty") """ * @brief function performs a ping test with google.com * * @param None * * @return None """ def ping_test(): global pool TAG="ping_test(): " ip = wifi.radio.ipv4_address s__ip = str(ip) ret = False if my_debug: print("s_ip= \'{}\'".format(s__ip)) if use_ping: try: if not pool: pool = socketpool.SocketPool(wifi.radio) # print(TAG+f"pool= {pool}, type(pool)= {type(pool)}") addr_idx = 1 addr_dict = {0:'LAN gateway', 1:'google.com'} info = pool.getaddrinfo(addr_dict[addr_idx], 80) print(TAG+f"info= {info}") addr = info[0][4][0] print(f"Resolved google address: \'{addr}\'") ipv4 = ipaddress.ip_address(addr) if ipv4 is not None: ret = True for _ in range(10): result = wifi.radio.ping(ipv4) if result: print("Ping google.com [%s]: %.0f ms" % (addr, result*1000)) break else: print("Ping no response") except OSError as e: print(TAG+f"Error: {e}") raise return ret """ * @brief function that establish WiFi connection * Function tries to establish a WiFi connection with the given Access Point * If a WiFi connection has been established, function will: * sets the global variables: 'ip' and 's_ip' ( the latter used by function wifi_is_connected() ) * * @param None * * @return None """ def do_connect(): global ip, s_ip, le_s_ip, start try: wifi.radio.connect(ssid=ssid, password=pw) except ConnectionError as e: print("WiFi connection Error: \'{}\'".format(e)) except Exception as e: print("Error:", dir(e)) ip = wifi.radio.ipv4_address if ip: s_ip = str(ip) le_s_ip = len(s_ip) """ * @brief function print hostname to REPL * * @param None * * @return None """ def hostname(): print(f"wifi.radio.hostname= \'{wifi.radio.hostname}\'") """ * @brief function prints mac address to REPL * * @param None * * @return None """ def mac(): mac = wifi.radio.mac_address le = len(mac) if le > 0: print("wifi.radio.mac_address= ", end='') for _ in range(le): if _ < le-1: print("{:x}:".format(mac[_]), end='') else: print("{:x}".format(mac[_]), end='') print('', end='\n') """ * @brief this is the main function that controls the flow of the * execution of this CircuitPython script. * The user can interrupt the running process * by typing the key-combination: CTRL+C * * @param None * * @return None """ def main(): global start TAG="main(): " print("Waiting another 5 seconds for mu-editor etc. getting ready") time.sleep(5) setup() print("Entering loop") discon_msg_shown = False s__ip = None ping_done = False grn_set = False red_set = False count_tried = 0 count_tried_max = 10 stop = False while True: try: if start: start = False if not my_debug: do_scan() sort_order = 1 # <<<=== Choose here how you want to sort the received list of Available WiFi networks (range: 0 - 2) pr_scanned_ap(sort_order) s__ip = str(wifi.radio.ipv4_address) if not wifi_is_connected(): print(TAG+"going to establish a WiFi connection...") do_connect() if wifi_is_connected(): # Check again. if id == 'unexpectedmaker_feathers3': if use_neopixel and not grn_set: grn_set = True r,g,b = feathers3.rgb_color_wheel( GRN ) pixels[0] = ( r, g, b, my_brightness) pixels.write() if not ping_done: print(TAG+f"connected to {ssid}!") #%s!"%ssid) print(TAG+f"IP address is {str(wifi.radio.ipv4_address)}") hostname() mac() ping_done = ping_test() if not ping_done: count_tried += 1 if count_tried >= count_tried_max: print(TAG+f"ping test failed {count_tried} times. Skipping this test.") ping_done = True else: if not discon_msg_shown: discon_msg_shown = True print(TAG+"WiFi disconnected") if id == 'unexpectedmaker_feathers3': if neopixel and not red_set: red_set = True r,g,b = feathers3.rgb_color_wheel( RED ) pixels[0] = ( r, g, b, my_brightness) pixels.write() time.sleep(2) except KeyboardInterrupt: print("KeyboardInterrupt. Exiting...") sys.exit() except Exception as e: print("Error", e) raise if __name__ == '__main__': main() ```

Behavior

0;🐍Wi-Fi: off | Done | 8.0.0-beta.3-11-gedce717cf\

soft reboot

Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.
code.py output:
]0;🐍Wi-Fi: off | code.py | 8.0.0-beta.3-11-gedce717cf\
]0;🐍Wi-Fi: No IP | code.py | 8.0.0-beta.3-11-gedce717cf\WiFi test for board: 'unexpectedmaker_feathers3'
waiting 5 seconds...
Waiting another 5 seconds for mu-editor etc. getting ready
Entering loop
]0;🐍Wi-Fi: No IP | code.py | 8.0.0-beta.3-11-gedce717cf\]0;🐍Wi-Fi: No IP | code.py | 8.0.0-beta.3-11-gedce717cf\]0;πŸ‘—i-Fi: No IP | code.py | 8.0.0-beta.3-11-gedce717cf\]0;🐍Wi-Fi: No IP | code.py | 8.0.0-beta.3-11-gedce717cf\]0;🐍Wi-Fi: No IP | code.py | 8.0.0-beta.3-11-gedce717cf\]0;πŸ‘—i-Fi: No IP | code.py | 8.0.0-beta.3-11-gedce717cf\]0;🐍Wi-Fi: No IP | code.py | 8.0.0-beta.3-11-gedce717cf\]0;🐍Wi-Fi: No IP | code.py | 8.0.0-beta.3-11-gedce717cf\]0;🐍Wi-Fi: No IP | code.py | 8.0.0-beta.3-11-gedce717cf\]0;πŸ‘—i-Fi: No IP | code.py | 8.0.0-beta.3-11-gedce717cf\]0;πŸ‘—i-Fi: No IP | code.py | 8.0.0-beta.3-11-gedce717cf\pr_scanned_ap(): 
Available WiFi networks (sorted on: 'rssi')
    AP nr 11    SSID: <SSID_of_my_WiFi_extender>        RSSI: -51   Channel:  9
    AP nr  8    SSID: MEO-AFM_EXT                       RSSI: -72   Channel:  3
    AP nr 12    SSID: <SSID_of_my_router_WiFi>          RSSI: -74   Channel:  9
    AP nr  5    SSID: MEO-FEEE80                        RSSI: -78   Channel: 11
    AP nr  6    SSID: MEO-WiFi                          RSSI: -79   Channel: 11
    AP nr  1    SSID: devolo-1a9                        RSSI: -82   Channel:  6
    AP nr  2    SSID: MEO-FIBRA                         RSSI: -85   Channel:  6
    AP nr  3    SSID: MEO-WiFi                          RSSI: -85   Channel:  6
    AP nr  9    SSID: MEO-AFM                           RSSI: -90   Channel:  3
    AP nr 13    SSID: Vodafone-5756DB                   RSSI: -91   Channel:  9
    AP nr 10    SSID: MEO-WiFi                          RSSI: -91   Channel:  3
    AP nr  7    SSID: MEO-WiFi                          RSSI: -91   Channel: 11
    AP nr  4    SSID: devolo-847                        RSSI: -94   Channel:  6
main(): going to establish a WiFi connection...
]0;πŸ‘—i-Fi: No IP | code.py | 8.0.0-beta.3-11-gedce717cf\]0;🐍192.168.x.yyy | code.py | 8.0.0-beta.3-11-gedce717cf\main(): connected to <SSID_of_my_WiFi_extender> !
main(): IP address is <IP-address received>
wifi.radio.hostname= 'UMFeatherS3'
wifi.radio.mac_address= 70:4:1d:ad:c3:fc
ping_test(): info= [(0, 0, 0, '', ('216.58.215.142', 80))]
Resolved google address: '216.58.215.142'
Ping no response
Ping no response
Ping google.com [216.58.215.142]: 19 ms
KeyboardInterrupt. Exiting...

Description

There is no need for more detailed description. See the REPL output under 'Behavior' and you can see what happens.

Note: This script depends on the following files: feathers3.py boot.py secrets.py

Additional information

What I think should not be is: CircuitPython statusbar lines in between the REPL output of the running script.

This never saw this happen before. I don't like this to happen. I think: during the execution of a script one should only see the REPL output of that script. After ending the script it is OK to see the CPY statusbar again.

I had this 'experience' also the past days on other boards running CircuitPython V8..0.0-beta.3. At first I didn't want to report this 'phenomena'. Instead I used a file boot.py to

import supervisor
supervisor.status_bar.console = False

However, I think such a drastic measure shouldn't be necessary.

PaulskPt commented 1 year ago

I think I discovered the origin of this 'behavior': as soon as I added a file .env containing the WiFi credentials of the WiFi AP to be connected, the unwanted CPY statusbar lines in between the REPL output of the running script disappeared. See below:

soft reboot

Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.
code.py output:
]0;🐍192-168.x.yyy | code.py | 8.0.0-beta.3-11-gedce717cf\
WiFi test for board: 'unexpectedmaker_feathers3'
waiting 5 seconds...
Waiting another 5 seconds for mu-editor etc. getting ready
Entering loop
pr_scanned_ap(): 
Available WiFi networks (sorted on: 'rssi')
    AP nr  8    SSID: <SSID_of_my_WiFi_extender>        RSSI: -54   Channel:  9
    AP nr  7    SSID: MEO-AFM_EXT                       RSSI: -67   Channel:  3
    AP nr  1    SSID: devolo-1a9                        RSSI: -83   Channel:  6
    AP nr  9    SSID: <SSID_of_my_router_WiFi>          RSSI: -84   Channel:  9
    AP nr  4    SSID: MEO-FEEE80                        RSSI: -87   Channel: 11
    AP nr  5    SSID: MEO-WiFi                          RSSI: -89   Channel: 11
    AP nr  3    SSID: MEO-WiFi                          RSSI: -90   Channel:  6
    AP nr  2    SSID: MEO-FIBRA                         RSSI: -90   Channel:  6
    AP nr 10    SSID: Vodafone-F19ACA                   RSSI: -92   Channel:  5
    AP nr  6    SSID: ThomsonDAA08B                     RSSI: -92   Channel: 11
main(): connected to <SSID_of_my_WiFi_extender>!
main(): IP address is 192.168.x.yyy
wifi.radio.hostname= 'UMFeatherS3'
wifi.radio.mac_address= 70:4:1d:ad:c3:fc
ping_test(): info= [(0, 0, 0, '', ('142.250.200.110', 80))]
Resolved google address: '142.250.200.110'
Ping no response
Ping no response
Ping google.com [142.250.200.110]: 21 ms

So, with this 'experience', I should add to my 'Note' in the text of the issue above:

This script depends on the following files:
boot.py
.env
feathers3.py
secrets.py
dhalbert commented 1 year ago

Is this in Mu or in another terminal program?

PaulskPt commented 1 year ago

It happens mainly in Mu:

2022-10-30_13h56pt_ESP32-S3-Box_CPYV8b3-24_REPL_output_in_Mu-Editor

In PuTTY the CPY V8 statusbar text is seen instead of 'PuTTY' in the caption of the PuTTY app window only but not in the print output:

2022-10-30_13h46pt_ESP32-S3-Box_CPYV8b3-24_REPL_output_in_PuTTY

dhalbert commented 1 year ago

The released version of Mu has no support for the escape codess produced by the status bar, and will interleave them with the REPL output. There is a fix in Mu for this, but there has been no release yet that includes it. I am inquiring about when another beta of Mu might be released.

PaulskPt commented 1 year ago

Thank you for your response and inquiry Dan.

DJDevon3 commented 1 year ago

@PaulskPt Time to revisit for 8.0.5 stable? At least with my Mu it no longer behaves in this manner on any of my Circuit Python devices. I'm currently testing the UM FeatherS2 & S3 in Mu serial. I'm not testing with web workflow though.

dhalbert commented 8 months ago

Closing because i think this has been addressed upstream.