0

Library suggestion for #CircuitPython2022
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Library suggestion for #CircuitPython2022

by ksprayberry on Wed Jan 05, 2022 2:03 pm

I saw the call for suggestions come out on the "Python for Microcontrollers" newsletter and thought I would make a suggestion. I feel a bit foolish for suggesting this or sharing this. I'm not a very good programmer, I'm a mechanical engineer by trade, but I have a fondness for these microcontrollers and the whole adafruit site has really helped me in many ways, so maybe take this suggestion as one that comes from someone not 100% savvy in programming. I've noticed as you go from platform to platform, there seems to be very small changes in the way you connect to the internet. On the ESP32s, The Funhouse, and a couple others that I can't think of, the connection to the wifi SSID is handled in the "code.py" file or in the front end code if I may refer to it that way. When It's done like this, I was able to write code that would allow me to connect to different SSID's depending on where I am at. I noticed on the Matrix Portal and the Magtag, the connection is made in the back end or in the library. When It's like this I really can't add the code to go choose the network connection I need. I guess I could, but I would have to modify the library everytime a new one was put out or updated. It's not the end of the world, but It sure is handy when you want to just take a device from one place to another and it work. I usually have my computer or tablet with me, so changing the values in the secrets file isn't that big of a deal. It's just a thought. I sure welcome suggestions.

#CircuitPython2022

Thanks
Kelly

ksprayberry
 
Posts: 33
Joined: Tue Feb 05, 2019 8:52 pm

Re: Library suggestion for #CircuitPython2022

by kipd on Thu Jan 06, 2022 7:50 pm

Did you look at the "secrets.py" file (located at the root of your CircuitPython drive)? In the code examples I am working on (https://learn.adafruit.com/magtag-weather)
the WiFi settings are set there ('ssid' and 'password') and your code just needs to add ("from secrets import secrets").

secrets = {
'ssid' : 'Example_SSID',
'password' : 'Example_PASSWORD',
}

kipd
 
Posts: 9
Joined: Wed Mar 02, 2016 4:28 am

Re: Library suggestion for #CircuitPython2022

by blakebr on Fri Jan 07, 2022 12:32 pm

Fallback SSID & PASSWORDs are a good idea for the Secrets File.

blakebr
 
Posts: 315
Joined: Tue Apr 17, 2012 6:23 pm

Re: Library suggestion for #CircuitPython2022

by ksprayberry on Fri Jan 07, 2022 2:49 pm

kipd wrote:Did you look at the "secrets.py" file (located at the root of your CircuitPython drive)? In the code examples I am working on (https://learn.adafruit.com/magtag-weather)
the WiFi settings are set there ('ssid' and 'password') and your code just needs to add ("from secrets import secrets").

secrets = {
'ssid' : 'Example_SSID',
'password' : 'Example_PASSWORD',
}


I did and I really like how that works and I think it's a good idea to separate your credentials from your main code so that you can share it with others and not really have to worry about accidentally sharing your credentials. What I'm more driving at is having the ability to have multiple SSIDs and passwords in the secrets file so that if you go from place to place with your device, if it can't find one SSID, it can look for another you have setup. I currently have another program setup and it looks for my home one first, our cabin SSID second and then my tablet SSID as the final alternative. that's more what I was driving at. The "connection" to the SSID is made on the backend so to speak, in the portalbase library as opposed to in your "code.py" as such, you really can't modify it for fallback SSIDs
Thanks

ksprayberry
 
Posts: 33
Joined: Tue Feb 05, 2019 8:52 pm

Re: Library suggestion for #CircuitPython2022

by toby_m on Fri Jan 14, 2022 1:18 pm

I would like multi SSID capability on MagTag. I found https://github.com/gmparis/CircuitPytho ... _multissid
and tried to adapt it. Having only recently begun using circuitPy, I spent a lot of time but have been unsuccessful. I began with the Weather project and developed an application to display the content of a Google Sheet that is 'published'. I gave the MagTag to my grandson as a gift. The sheet has birthdays, riddles, etc. that updates automatically daily. I reserved one line that I can update from my phone using a Google Form linked to the sheet. I have a Secrets file that contains SSIDs for his home and mine.

In my earlier effort, I was able to scan wifi networks and if one was recognized, to connect to it. However, the rest of the application did not recognize the ssid I wanted to use and the effort failed. I need to control the SSID & password that is used for the several internet connections in the code. I would appreciate guidance on what modules to create and where to put them in /lib

Code: Select all | TOGGLE FULL SIZE
"""Gets and displays JSON spreadsheet from Google."""
import time
import rtc
import terminalio
from adafruit_display_shapes.rect import Rect
from adafruit_magtag.magtag import MagTag
from secrets import secrets #for openweather, sheets, time.

# CONFIGURABLE SETTINGS and ONE-TIME INITIALIZATION ------------------------
TESTING = False #for debug. Otherwise sleep until 3:15AM
MsgList = " "
NEED_TO_CHARGE = 3.4 #battery voltage below this indicates time to charge
# MagTag will stop operating below ~ 3.0 volts.
JSON_URL = secrets["json_url"] #link to published Google sheet.
MAGTAG = MagTag(rotation=270) # Portrait (vertical) display

# GRAPHICS INITIALIZATION --------------------------------------------------
MAGTAG.add_text(
    text_font= terminalio.FONT,
    text_position=(0, 0),
    line_spacing=1.0,
    text_color=0x000000,
    text_anchor_point=(0, 0), # Top left
    is_data=False,
)

# Black bar at bottom of display is a distinct layer not just background
MAGTAG.graphics.splash.append(Rect(0, MAGTAG.graphics.display.height - 13,
                                   MAGTAG.graphics.display.width,
                                   MAGTAG.graphics.display.height, fill=0x0))

# Center white text over black bar. show last update time & battery info.
MAGTAG.add_text(
    text_font='/fonts/helvB12.pcf',
    text_position=(MAGTAG.graphics.display.width // 2,
                   MAGTAG.graphics.display.height - 5),
    text_color=0xFFFFFF,
    text_anchor_point=(0.5, 0.7), # Center bottom
    is_data=False,              # Text will be set manually
)

#Battery voltage and admonition to charge if voltage < NEED_TO_CHARGE
BATT = "{:.1f}".format(MAGTAG.peripherals.battery) + " "
if MAGTAG.peripherals.battery < NEED_TO_CHARGE :   
   BATT += "Charge me today. "
# WEATHER --------------------------------------------------------------------|
def get_data_source_url(api="onecall", location=None):
    """Build and return the URL for the OpenWeather API."""
    if api.upper() == "FORECAST5":
        URL = "https://api.openweathermap.org/data/2.5/forecast?"
        URL += "q=" + location
    elif api.upper() == "ONECALL":
        URL = "https://api.openweathermap.org/data/2.5/onecall?exclude=minutely,hourly,alerts"
        URL += "&lat={}".format(location[0])
        URL += "&lon={}".format(location[1])
    else:
        raise ValueError("Unknown API type: " + api)
    return URL + "&appid=" + secrets["openweather_token"]

def get_latlon():
    """Use the Forecast5 API to determine lat/lon for given city."""
    MAGTAG.url = get_data_source_url(api="forecast5", location=secrets["openweather_location"])
    MAGTAG.json_path = ["city"]
    raw_data = MAGTAG.fetch()
    return raw_data["coord"]["lat"], raw_data["coord"]["lon"]

def get_forecast(location):
    """Use OneCall API to fetch forecast and timezone data."""
    resp = MAGTAG.network.fetch(get_data_source_url(api="onecall", location=location))
    json_data = resp.json()
    return json_data["daily"], json_data["current"]["dt"], json_data["timezone_offset"]

def temperature_text(tempK):
   return "{:3.0f}F".format(32.0 + 1.8 * (tempK - 273.15))

# MAIN ----------------------------------------------------------------
try:
    MAGTAG.network.connect() # Do this last, as WiFi uses power

    print('Updating time')
    MAGTAG.get_local_time()
    NOW = rtc.RTC().datetime
    print(NOW)
    print('-'*20)
    print("Time is", str(NOW.tm_hour) + ':' + str(NOW.tm_min))
    print('-'*20)
    print('Getting Latitude & Longitude')

    latlon = get_latlon()
    print(secrets["openweather_location"])
    print(latlon)

    print("Fetching forecast...")
    forecast_data, utc_time, local_tz_offset = get_forecast(latlon)
    max = forecast_data[0]['temp']['max']
    max_F = "High today" + temperature_text(max)
    print (max_F)

    print('Spreadsheet content')
    RESPONSE = MAGTAG.network.fetch(JSON_URL)
    if RESPONSE.status_code == 200:
        JSON_DATA = RESPONSE.json()
        print('OK response')
    print('-'*40)
    print (JSON_DATA)

# Show battery voltage, ask to charge if needed, and Time of update.
    time_string = str(NOW.tm_hour % 12)
    if NOW.tm_hour == 0 : time_string = '12'
    SUFFIX = 'am'
    if NOW.tm_hour > 12 : SUFFIX = 'pm'
    time_string += ':' + "{:02d}".format(NOW.tm_min) + SUFFIX   
    MAGTAG.set_text('%s %s %s %s' % (BATT, 'Updated at', time_string, max_F)
       , 1, auto_refresh=False)

# Place spreadsheet data on display
    ENTRIES = JSON_DATA['values'] # List of cell data
    for entry in ENTRIES:
       MsgList += str(*entry)  + "\n "
    print ('='*40)      
    MAGTAG.set_text(MsgList) # Update list on the display
    print (MsgList)

# Allow refresh to finish before deep sleep. Compute to wake at 3:15am.
    time.sleep(2)
    seconds_since_midnight = 60 * (NOW.tm_hour * 60 + NOW.tm_min) + NOW.tm_sec
    three_fifteen = (3 * 60 + 15) * 60
    seconds_to_sleep = (24 * 60 * 60 - seconds_since_midnight) + three_fifteen
    if TESTING == True : seconds_to_sleep = 121
    print("Sleep for {} hours, {} minutes".format(
            seconds_to_sleep // 3600, (seconds_to_sleep // 60) % 60
        )
    )
    MAGTAG.exit_and_deep_sleep(seconds_to_sleep)

except ConnectionError as my_error:
   MAGTAG.set_text(" Unable to connect to WiFi.\n Will try again hourly.")
   MAGTAG.exit_and_deep_sleep(60 * 60) # 60 minute deep sleep

except RuntimeError as error:
    # If there's an error above, no harm, just try again in ~15 minutes.
    # Usually it's a common network issue or time server hiccup.
    print('Retry in 15 min - ', error)
    MAGTAG.set_text("error")
    MAGTAG.exit_and_deep_sleep(15 * 60) # 15 minute deep sleep

toby_m
 
Posts: 11
Joined: Mon Feb 18, 2013 7:01 pm

Re: Library suggestion for #CircuitPython2022

by blakebr on Fri Jan 14, 2022 3:14 pm

This is what I would like to see:
Code: Select all | TOGGLE FULL SIZE
# This file is where you keep secret settings, passwords, and tokens!
# If you put them in the code you risk committing that info or sharing it

secrets = {
   'ssid' : 'WIFI_SSID',
   'password' : 'PASSWORD,
   'ssid2' : 'WIFI_SSID2',
   'password2' : 'PASSWORD2,
   'ssid3' : 'WIFI_SSID3',
   'password3' : 'PASSWORD3,
   'openweather_token' : 'xyzxyzxyz',
   'aio_username' : "butter",
   'aio_key' : 'abcabcabcabc',
   'location' : 'Baltimore, US'
}

The SSID & PASSWORD pairs would be cycled through before failing

Bruce

P.S. I learned something new today.
Apparently using a string of x characters to block out sensitive information is a banned word.

blakebr
 
Posts: 315
Joined: Tue Apr 17, 2012 6:23 pm

Re: Library suggestion for #CircuitPython2022

by ksprayberry on Sun Jan 16, 2022 2:14 pm

blakebr wrote:This is what I would like to see:
Code: Select all | TOGGLE FULL SIZE
# This file is where you keep secret settings, passwords, and tokens!
# If you put them in the code you risk committing that info or sharing it

secrets = {
   'ssid' : 'WIFI_SSID',
   'password' : 'PASSWORD,
   'ssid2' : 'WIFI_SSID2',
   'password2' : 'PASSWORD2,
   'ssid3' : 'WIFI_SSID3',
   'password3' : 'PASSWORD3,
   'openweather_token' : 'xyzxyzxyz',
   'aio_username' : "butter",
   'aio_key' : 'abcabcabcabc',
   'location' : 'Baltimore, US'
}

The SSID & PASSWORD pairs would be cycled through before failing

Bruce

P.S. I learned something new today.
Apparently using a string of x characters to block out sensitive information is a banned word.



So this is what I did that works, you set it up just like you suggest in the secrets.py file and then you iterate through the list of available networks looking for ssid that matches your "preferred list" of ssids. The issue I see is that when you pull these wifi connections into the library files, it does not give you a chance to have multiple ssids for your project. I guess you could edit the libraries each time, but the libraries are updated often. not the end of the world, but..

try:
for network in wifi.radio.start_scanning_networks():
# print("\t%s\t\tRSSI: %d\tChannel: %d" % (str(network.ssid, "utf-8"),
# network.rssi, network.channel))

if network.ssid == secrets["ssidhome"]:
print("found home network")
wifi.radio.connect(secrets["ssidhome"], secrets["passwordhome"])
# print("Connecting to %s" % secrets["ssidhome"])
wifi.radio.stop_scanning_networks()
break
if network.ssid == secrets["ssid2"]:
wifi.radio.connect(secrets["ssidkelly"], secrets["password2"])
# print("Connecting to %s" % secrets["ssid2"])
# print("My IP address is", wifi.radio.ipv4_address)
wifi.radio.stop_scanning_networks()
break
if network.ssid == secrets["ssid3"]:
wifi.radio.connect(secrets["ssid3"], secrets["password3"])
# print("Connecting to %s" % secrets["ssid3"])
wifi.radio.stop_scanning_networks()
break

wifi.radio.stop_scanning_networks()
except OSError:
print("OS error")
pass
except RuntimeError:
print("Runtime error")
pass
else:
print("some error")
pass
finally:
print("finally")
pass

ksprayberry
 
Posts: 33
Joined: Tue Feb 05, 2019 8:52 pm

Re: Library suggestion for #CircuitPython2022

by blakebr on Sun Jan 16, 2022 3:08 pm

I'll give it a try. What libraries are you using?

I assume you use cut and paste to include your code.
Hint Click [CODE} above
Paste your code between the two code markers. You will then get this.
Code: Select all | TOGGLE FULL SIZE
try:
    for network in wifi.radio.start_scanning_networks():
    # print("\t%s\t\tRSSI: %d\tChannel: %d" % (str(network.ssid, "utf-8"),
    # network.rssi, network.channel))

        if network.ssid == secrets["ssidhome"]:
            print("found home network")
            wifi.radio.connect(secrets["ssidhome"], secrets["passwordhome"])
            # print("Connecting to %s" % secrets["ssidhome"])
            wifi.radio.stop_scanning_networks()
            break
        if network.ssid == secrets["ssid2"]:
            wifi.radio.connect(secrets["ssidkelly"], secrets["password2"])
            # print("Connecting to %s" % secrets["ssid2"])
            # print("My IP address is", wifi.radio.ipv4_address)
            wifi.radio.stop_scanning_networks()
            break
        if network.ssid == secrets["ssid3"]:
            wifi.radio.connect(secrets["ssid3"], secrets["password3"])
            # print("Connecting to %s" % secrets["ssid3"])
            wifi.radio.stop_scanning_networks()
            break

    wifi.radio.stop_scanning_networks()
except OSError:
    print("OS error")
    pass
except RuntimeError:
    print("Runtime error")
    pass
else:
    print("some error")
    pass
finally:
    print("finally")
    pass
The reader can then click 'SELECT ALL' to have it copied to the clipboard. It will also preserve any indents or tabs that my be in your code.

Bruce

P.S. I just learned this a couple weeks ago.

blakebr
 
Posts: 315
Joined: Tue Apr 17, 2012 6:23 pm

Re: Library suggestion for #CircuitPython2022

by ksprayberry on Sun Jan 16, 2022 3:37 pm

Doh! I knew to do that. Sorry!

ksprayberry
 
Posts: 33
Joined: Tue Feb 05, 2019 8:52 pm

Re: Library suggestion for #CircuitPython2022

by blakebr on Sun Jan 16, 2022 4:06 pm


blakebr
 
Posts: 315
Joined: Tue Apr 17, 2012 6:23 pm

Re: Library suggestion for #CircuitPython2022

by toby_m on Mon Jan 17, 2022 7:42 pm

I made a determined effort at finding what functions were being called so I could implement multi SSID in my MagTag. I wound up at network.py in adafruit_portabase. I was reluctant to add modules, fearing it would impact existing operations. For example, I was unable to implement scanning and decided not to import wifi.

I was pleased that connect() in network.py from portalbase had a parameter and code for multiple attempts but the module that implements _wifi.connect() in wifi_esp32s2.py sets self._connect = True independent of whether a connection was actually established. This negates the code for multiple attempts.

I connect to each SSID in my secrets file with several attempts at each. It is fairly quick. Instead of using network.connect(), I wrote function htm_connect() (in network.py in adafruit_portalbase). With two SSIDs in Secrets, htm_connect() ping-pongs between the two SSIDs for a total of 9 tries. If it fails, a message is posted on the display and retries in one hour. I tried placing htm_connect() in code.py but it failed and I was unwilling to import modules. My approach is crude but the best I can do with my limited understanding and experience.

htm_connect()
Code: Select all | TOGGLE FULL SIZE
def htm_connect(self):
      attemptnumber = 1   
      print ("htm_connect in network.py  attempt=",attemptnumber)
      self._wifi.neo_status(STATUS_CONNECTING)
      while not self._wifi.is_connected:
         if attemptnumber % 2 == 0 :
            htm_ssid = secrets['ssid']
            htm_password = secrets ['password']
         else :
            htm_ssid = secrets['ssid1']
            htm_password = secrets['password1']   
         print("Connecting to AP", htm_ssid,attemptnumber)
         self._wifi.neo_status(STATUS_NO_CONNECTION)  # red = not connected
         try:
            self._wifi.connect(htm_ssid, htm_password)
            self.requests = self._wifi.requests
         except ConnectionError as error:
            print("connect error with",htm_ssid,"attempt",attemptnumber)
            attemptnumber += 1
            if attemptnumber >= 10 :
               return ("failed after 10 attempts.")
         gc.collect()

toby_m
 
Posts: 11
Joined: Mon Feb 18, 2013 7:01 pm

Re: Library suggestion for #CircuitPython2022

by blakebr on Mon Jan 17, 2022 8:07 pm

This is the code I got working on a Feather with AirLift. It is not elegant at all. After it connects it disconnects and closes.

If you change to ca = False it will try to connect to all WiFi signals it can find one at a time then disconnect and try the next. As it is, it starts with the strongest signal and works down the list. I would prefer it start with the order that they are in the boot.py code. Next version.

Code: Select all | TOGGLE FULL SIZE
import time
import board
import busio
import displayio
from   digitalio import DigitalInOut, Direction, Pull

import adafruit_esp32spi.adafruit_esp32spi_socket as socket
from   adafruit_esp32spi import adafruit_esp32spi
from   adafruit_esp32spi import PWMOut
from   adafruit_esp32spi import *

try:
    from secrets import secrets
except ImportError:
    print("WiFi secrets are kept in secrets.py, please add them there!")
    raise

#################### Figure out what board we have ####################
Pico, Itsy, QTPy, Fthr, ANRC, Chal = False, False, False, False, False, False
brd = board.board_id # Ask the AdaFruit Circuit Python OS what board we are using
print(brd, end=" - ")
#           "12345678901234567890123456789
if  (brd == "raspberry_pi_pico"):           Pico = True # 17
elif(brd == "adafruit_qtpy_rp2040"):        QTPy = True # 20
elif(brd == "adafruit_feather_rp2040"):     Fthr = True # 23
elif(brd == "adafruit_itsybitsy_rp2040"):   Itsy = True # 25
elif(brd == "challenger_rp2040_wifi"):      Chal = True # 22
elif(brd == "challenger_nb_rp2040_wifi"):   Chal = True # 25
elif(brd == "arduino_nano_rp2040_connect"): ANRC = True # 27
else:
    while(True):
        print("Board Not Supported.")
        time.sleep(15)
        pass # raise
print("Board Supported.")
#if board.board_id not in ["feather_m0_express", "trinket_m0"]:
#    raise ValueError(f"unsupported board: {board.board_id}")
# If you are using a board with pre-defined ESP32 Pins:
if(ANRC or Chal):
    esp32_cs    = DigitalInOut(board.ESP_CS1)
    esp32_ready = DigitalInOut(board.ESP_BUSY)
    esp32_reset = DigitalInOut(board.ESP_RESET)
else:
# If you have an externally connected ESP32: AirLift etc.
    esp32_cs    = DigitalInOut(board.D13) # Change to match your build
    esp32_ready = DigitalInOut(board.D6 )
    esp32_reset = DigitalInOut(board.D12)
    esp32_GPIO0 = DigitalInOut(board.D10) # ?
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
wifi = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
if wifi.status == adafruit_esp32spi.WL_IDLE_STATUS:
    print("ESP32 found and in idle mode")
print("Firmware version :  ", str(wifi.firmware_version, 'utf-8'))
print("MAC addr reversed:  ", [hex(i) for i in wifi.MAC_address])
print("MAC addr actual  :  ", [hex(i) for i in wifi.MAC_address_actual])
##########################################################
ca = True
try:
    for network in wifi.scan_networks():
    # Connect to WiFi Router        print(network)
    #   {'rssi': -39, 'ssid': bytearray(b'aristotle'), 'bssid': bytearray(b'\x9bv\xabh\xefX'), 'encryption': 4, 'channel': 6}
        print("\nConnecting to AP...", end='')
        print("\tSSID: %s\tRSSI: %d\tChannel: %d\tEncryption: %d" % (str(network['ssid'], 'utf-8'), network['rssi'], network['channel'], network['encryption']))
        if network['ssid'] == secrets["ssid"]:
            print("found home network")
            wifi.connect_AP(secrets["ssid"], secrets["password"])
            print("Connecting to %s" % secrets["ssid"])
            print("My IP address is ", wifi.ip_address)
            print("Pretty IP Address", wifi.pretty_ip(wifi.ip_address))
            print("Connected to:", str(wifi.ssid, "utf-8"), "\tRSSI:", wifi.rssi)
            if(ca): break
        if network['ssid'] == secrets["ssid2"]:
            print("found secondary network")
            wifi.connect_AP(secrets["ssid2"], secrets["password2"])
            print("Connecting to %s" % secrets["ssid2"])
    #        print("My IP address is", wifi.ip_address)
            print("Pretty IP Address", wifi.pretty_ip(wifi.ip_address))
            print("Connected to:", str(wifi.ssid, "utf-8"), "\tRSSI:", wifi.rssi)
            if(ca): break
        if network['ssid'] == secrets["ssid3"]:
            print("found tertiary network")
            wifi.connect_AP(secrets["ssid3"], secrets["password3"])
            print("Connecting to %s" % secrets["ssid3"])
            print("My IP address is", wifi.ip_address)
            print("Pretty IP Address", wifi.pretty_ip(wifi.ip_address))
            print("Connected to:", str(wifi.ssid, "utf-8"), "\tRSSI:", wifi.rssi)
            if(ca): break
        print("Disconnecting: %s" % wifi.disconnect())
except OSError:
    print("OS Error")
    pass
except RuntimeError:
    print("Runtime Error")
    pass
else:
    print("Some Error")
    pass
finally:
    pass
#print(network)
print("Finally")
pass
print(wifi.disconnect())

blakebr
 
Posts: 315
Joined: Tue Apr 17, 2012 6:23 pm

Please be positive and constructive with your questions and comments.