dotENV Files

All ESP32 boards running MicroPython.
Target audience: MicroPython users with an ESP32 board.
Post Reply
RedDirt
Posts: 8
Joined: Fri Nov 27, 2020 1:43 am

dotENV Files

Post by RedDirt » Sat Nov 28, 2020 4:49 am

Is there a native way in Micropython to read a .env file? I know there are third-party packages out there for Python, but I was hoping there was something native to Micropython so I could read credentials and API keys from there instead of hard coding them into the script.

RedDirt
Posts: 8
Joined: Fri Nov 27, 2020 1:43 am

Re: dotENV Files

Post by RedDirt » Wed Dec 23, 2020 4:42 am

I had forgotten about this post. Having come across it again, I thought I would share what I ended up doing.

I created a file which I named config.py and uploaded that the ESP32 board. Within that file I would add any credentials, API keys, etc. that I would need. For example:

Code: Select all

WIFI_SSID = 'myssid'
WIFI_PASSWD = 'mywifipassword'
SOME_API_KEY = 'myapikey'
Then, wherever I needed those credentials I would just use an import statement, import config. From there it was just a matter of using it within the code as you would any other imported module:

Code: Select all

import config
import network

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(config.WIFI_SSID, config.WIFI_PASSWD)
It's by no means perfect (or even the best) but when I am writing code that I will hold in my Github repo, I can use the .gitignore file to prevent uploading the config.py file and I don't have to hard code any credentials or keys into the code. The added bonus is it is portable. I can just store that file and move it over to any new ESP32 project I am working on and everything I need is right there.

I'd really love to learn how to get the board to scan for WiFi AP's and detect any that may be open and connect to them and if none are available, then revert back to the config.py file and try to connect to that one. Or maybe even better, give it a list of AP's that it can attempt to connect to with the required credentials and try that if no open WiFi is available.

marcidy
Posts: 133
Joined: Sat Dec 12, 2020 11:07 pm

Re: dotENV Files

Post by marcidy » Wed Jan 06, 2021 6:46 am

The btree module is well suited for storing key-value pair data in a file on the file system.
I wrapped it in a module called "storage" with get and put methods that take ascii values, like this:

Code: Select all

import btree

CORE_STORAGE = "data"                                                                                                                        

# Do this only once, it will overwite a previous file with a new one if it exists                                                                                
def init():                                                                     
    with open(CORE_STORAGE, 'w+b') as f:                                        
        db = btree.open(f)                                                      
        db[b"MODE"] = b"INIT"                                                   
        db.close()                                                              
                                                                                
                                                                                
def get(item):                                                                  
    value = None                                                                
    with open(CORE_STORAGE, 'r+b') as f:                                        
        db = btree.open(f)                                                      
        b_item = item.encode('ascii')                                           
        value = db.get(b_item)                                                  
        if value:                                                               
            value = value.decode('ascii')                                       
        db.close()                                                              
    return value                                                                
                                                                                
                                                                                
def put(item, value):                                                           
    b_item = None                                                               
    b_value = None                                                              
    try:                                                                        
        b_item = item.encode('ascii')                                           
        b_value = value.encode('ascii')                                         
    except AttributeError:                                                      
        print("storage items and values must be ascii strings")                 
        return False                                                            
                                                                                
    if b_item and b_value:                                                      
        with open(CORE_STORAGE, 'r+b') as f:                                    
            db = btree.open(f)                                                  
            db[b_item] = b_value                                                
            db.flush()                                                          
            db.close()                                                          
        return True 
For things which need to be written a relatively small number of times, this works fine.

Code: Select all

>>> import storage
>>> storage.put("SSID", "My AP ESSID")
>>> ssid = storage.get("SSID")
You scan run an access point at the same time as the station, giving you the ability to access the device if you are in the proximity to add settings. For example, I created a small page which accepts config values and stores them so I can update device credentials from my phone, so I can boot it up and connect it to any network.

When you scan for networks, it returns a list of tuples for each network.

Code: Select all

(b'titopuente', b'\x08\x11\x96\xc1\xc9\xdc', 6, -38, 3, False)
(b'ATTPm5PQgs', b'\x88\x96N>s0', 11, -59, 3, False)
(b'Sonic.net-209', b'\x94\xc1P\x00[:', 6, -73, 3, False)
(b'ATT5veM6wF', b'\x0c|(w?\x04', 6, -89, 3, False)
(b'Leslie Riley', b'T\xbe\xf7\xfeC\xc0', 11, -90, 3, False)
(b'DIRECT-94-HP DeskJet 3630 series', b'\xc4e\x16[=\x95', 6, -92, 3, False)
(b'xfinitywifi', b'^\xbe\xf7\xfeC\xc0', 11, -92, 0, False)
(b'ATT2UyA68M', b'8;\xc8>9\x81', 11, -94, 3, False)
(b'Wemo.Mini.1F7', b'$\xf5\xa2\x1a\xa0j', 11, -94, 0, False)
Looking at ports/esp32/modnetwork.c in the scan function:

Code: Select all

            t->items[0] = mp_obj_new_bytes(wifi_ap_records[i].ssid, ssid_len);
            t->items[1] = mp_obj_new_bytes(wifi_ap_records[i].bssid, sizeof(wifi_ap_records[i].bssid));
            t->items[2] = MP_OBJ_NEW_SMALL_INT(wifi_ap_records[i].primary);
            t->items[3] = MP_OBJ_NEW_SMALL_INT(wifi_ap_records[i].rssi);
            t->items[4] = MP_OBJ_NEW_SMALL_INT(wifi_ap_records[i].authmode);
            t->items[5] = mp_const_false; // XXX hidden?

so the 5th item is the auth mode. (This is also noted in the docs).

These objects are wifi_ap_record_t structs, and the 5th item is a wifi_auth_mode_t enum, both of which are defined in esp_wifi_types.h in esp-idf:

Code: Select all

typedef enum {
    WIFI_AUTH_OPEN = 0,         /**< authenticate mode : open */
    WIFI_AUTH_WEP,              /**< authenticate mode : WEP */
    WIFI_AUTH_WPA_PSK,          /**< authenticate mode : WPA_PSK */
    WIFI_AUTH_WPA2_PSK,         /**< authenticate mode : WPA2_PSK */
    WIFI_AUTH_WPA_WPA2_PSK,     /**< authenticate mode : WPA_WPA2_PSK */
    WIFI_AUTH_WPA2_ENTERPRISE,  /**< authenticate mode : WPA2_ENTERPRISE */
    WIFI_AUTH_MAX
} wifi_auth_mode_t;
so iterating over the scan results and trying the AP's where the auth mode is 0 should work.
Remember that you may not get access to the network even if you can associate with the AP. An open access point may still require a login (like on public wifi with a captive portal).
If you successfully associate, you should check to see if you can access something known.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: dotENV Files

Post by pythoncoder » Wed Jan 06, 2021 9:51 am

RedDirt wrote:
Wed Dec 23, 2020 4:42 am
...when I am writing code that I will hold in my Github repo, I can use the .gitignore file to prevent uploading the config.py file and I don't have to hard code any credentials or keys into the code...
My approach to this is to have a config.py file similar to yours but containing dummy data such as "YOUR_PASSWORD". That file is under Git control and is published. Another file my_config.py has my actual credentials. This is not under Git control so never gets uploaded. I copy that file to config.py on the hardware.

Most of my projects have a private directory containing files and notes not under Git control.
Peter Hinch
Index to my micropython libraries.

Post Reply