Installation de librairie simple ?

Sur ESP, si on est sur un réseau connecté au web, il est facile d'installer une librairie à l'aide de upip depuis l'interpréteur, ce qui est très pratique !

Par exemple, on peut faire :

>>> import upip as pip
>>> pip.install("module")

Les fichiers de la lib sont installés dans un répertoire /lib/ de la carte. C'est tout simplement nickel !

Je me demande par contre si on aurait pas une possibilité aussi simple et facile d'installter une lib à partir d'une url d'un github par exemple (les fichiers de libs /drivers sont souvent sous cette forme) ? avec pip ou un équivalent wget en micropython. çà serait tout simplement très pratique pour installer les libs et maintenir un dépôt de drivers dans lequel on aurait qu'à donner l'url à aller chercher, le tout depuis interpréteur ou code Python.

Il faut probablement regarder du côté de urequest : https://github.com/micropython/micropython-lib/tree/master/urequests

On devrait pouvoir faire quelque chose comme :

import requests
url = "https://raw.githubusercontent.com/mchobby/esp8266-upy/master/mcp4725/lib/mcp4725.py"
#just a random link of a dummy file
r = requests.get(url)
#retrieving data from the URL using get method
with open("mcp4725.py", 'wb') as f:
#giving a name and saving it in any required format
#opening the file in write mode
    f.write(r.content) 
#writes the URL contents from the server

Vu ici : https://www.codespeedy.com/how-to-download-files-from-url-using-python/

En mettant tout çà dans une petite fonction install(url) d'un module driver on devrait arriver à quelque chose de pas mal et simple. Et avec un dict, on pourrait même prédéfinir les url pour n'avoir à saisir que le nom de la lib avec install("nomdriver")... çà serait pas mal du tout...

on pourrait même pousser jusqu'à intégrer l'installation des libs utiles d'un code dans le code, avant les imports, en faisant :

import drivers, os
files=os.listdir('/')
if not "ds18b20.py" in files : drivers.install("ds18b20")
if not "onewire.py" in files : drivers.install("onewire")
#etc...

import ds18b20
import onewire

Ce faisant, dans le code lui-même, on installe les librairies utiles sans autre manip' que d'exécuter le code.

Version "universelle" à partir du poste fixe

De façon plus universelle, c'est à dire pouvant fonctionner avec n'importe quelle carte avec wifi ou non, on pourrait aussi faire un petit script Python sur le poste principal qui récupérerai le fichier avec request sur le poste principal et l'enverrai vers la carte avec pyboard.py, l'utilitaire de transfert depuis Python vers carte micropython. (voir en bas de page de https://docs.micropython.org/en/latest/reference/pyboard.py.html : çà fonctionne nickel, même depuis Jupyter par exemple..., genre pour grapher à la volée dans un notebook des data venant de la carte micropython... mais c'est pas le sujet là)

Et là idem, en faisant un dict, on pourrait très bien n'avoir qu'à faire install("nommodule"), voire install(typecarte,"nommodule") par exemple install(RP2,"ds18b20") et zou, çà va chercher sur l'url prédéfinie et çà pose le fichier de lib sur la carte. C'est une solution 2 qui fonctionnera pour toutes les cartes.

A minima, çà peut se faire via un script bash, mais dans ce cas, on n'aurait pas la correspondance des urls et noms via un dict. Code Python semble plus pertinent.

Essayons çà...

On peut commencer par télécharger le fichier voulu à partir d'une url :

import requests
url = "https://raw.githubusercontent.com/mchobby/esp8266-upy/master/mcp4725/lib/mcp4725.py"
#just a random link of a dummy file
r = requests.get(url)
#retrieving data from the URL using get method
with open("mcp4725.py", 'wb') as f:
#giving a name and saving it in any required format
#opening the file in write mode
    f.write(r.content) 
#writes the URL contents from the server

Vu ici : https://www.codespeedy.com/how-to-download-files-from-url-using-python/

On pourrait améliorer en :

import requests
url = "https://raw.githubusercontent.com/mchobby/esp8266-upy/master/mcp4725/lib/mcp4725.py"
filename=url.split('/')[-1] # dernier element est le nom de fichier
r = requests.get(url)

with open(filename, 'wb') as f:
    f.write(r.content) 

Ensuite, il suffit de copier le fichier sur la carte en appelant la ligne de commande depuis le code Python en faisant :

import subprocess

# python3 pyboard.py --device $1 -f cp $2 :main.py # commande pour copier dans main.py
cmd=['python3', 'pyboard.py', '--device', '/dev/ttyUSB0' , '-f', 'cp', filename, ':'+filename, '--no-follow']
# cmd=['python3', 'pyboard.py', '--device', '/dev/ttyUSB0' , '-f', 'cp', 'test.py', ':test.py', '--no-follow']

p=subprocess.run(cmd)

Ensuite, on peut vérifier, toujours depuis le même terminal que la copie s'est bien déroulée :

import pyboard
pyb=pyboard.Pyboard('/dev/ttyUSB0', 115200)
pyb.enter_raw_repl()
ret=pyb.exec("""import os
print(os.listdir())
""")

print(ret)

pyb.exit_raw_repl() 

On peut rassembler tout çà dans un script drivers.py :

#!/usr/bin/env python3
# by X.H. - www.micropython.fr - 2021

import requests
import subprocess

def install(urlIn, portIn='/dev/ttyUSB0'):

    #-- copie locale du fichier
    #url = "https://raw.githubusercontent.com/mchobby/esp8266-upy/master/mcp4725/lib/mcp4725.py"
    url=urlIn
    filename=url.split('/')[-1] # dernier element est le nom de fichier
    r = requests.get(url)

    with open(filename, 'wb') as f:
        f.write(r.content)     

    #-- copie du fichier sur la carte Micropython 
    cmd=['python3', 'pyboard.py', '--device', portIn , '-f', 'cp', filename, ':'+filename, '--no-follow']

    p=subprocess.run(cmd)

    print("Copie de : " + filename + " depuis : " + url + " OK !")

def listing(portIn='/dev/ttyUSB0'):
    import pyboard
    pyb=pyboard.Pyboard('/dev/ttyUSB0', 115200)
    pyb.enter_raw_repl()
    ret=pyb.exec("""import os
print(os.listdir())
""")

    print(ret)

    pyb.exit_raw_repl()

Ensuite, il suffit de faire :

>>> import drivers
>>> url = "https://raw.githubusercontent.com/mchobby/esp8266-upy/master/mcp4725/lib/mcp4725.py"
>>> drivers.install(url)
>>> drivers.listing()
b"['app', 'boot.py', 'ds18x20.py', 'lib', 'main.py', 'mcp4725.py', 'smoothie.js', 'static', 'test.py', 'webrepl_cfg.py', 'wifi.py', 'ws2812.py']\r\n"

Pour finaliser, on peut passer par un dictionnaire d'url prédéfinies par drivers, en utilisant le même nom que l'import de module. Ce qui nous donne :

urls=({
"ds18x20":"https://raw.githubusercontent.com/micropython/micropython/master/drivers/onewire/ds18x20.py",
"ds18b20":"https://raw.githubusercontent.com/micropython/micropython/master/drivers/onewire/ds18x20.py",
"ht16k33_matrix":"https://raw.githubusercontent.com/hybotics/Hybotics_Micropython_HT16K33/master/ht16k33_matrix.py",
"onewire": "https://raw.githubusercontent.com/micropython/micropython/master/drivers/onewire/onewire.py", 
"tm1637":"https://raw.githubusercontent.com/mcauser/micropython-tm1637/master/tm1637.py"
})

Le code drivers.py devient :

#!/usr/bin/env python3
# by X.H. - www.micropython.fr - 2021

import requests
import subprocess

# listing des drivers

urls=({
"ds18x20":"https://raw.githubusercontent.com/micropython/micropython/master/drivers/onewire/ds18x20.py",
"ds18b20":"https://raw.githubusercontent.com/micropython/micropython/master/drivers/onewire/ds18x20.py",
"ht16k33_matrix":"https://raw.githubusercontent.com/hybotics/Hybotics_Micropython_HT16K33/master/ht16k33_matrix.py",
"lcd_api":"https://github.com/dhylands/python_lcd/blob/master/lcd/lcd_api.py",
"machine_i2_lcd":"https://raw.githubusercontent.com/dhylands/python_lcd/master/lcd/machine_i2c_lcd.py",
"onewire": "https://raw.githubusercontent.com/micropython/micropython/master/drivers/onewire/onewire.py", 
"tm1637":"https://raw.githubusercontent.com/mcauser/micropython-tm1637/master/tm1637.py"
})

# fonctions d'installation :

def install_from_url(urlIn, portIn='/dev/ttyUSB0'):
    #-- copie locale du fichier
    #url = "https://raw.githubusercontent.com/mchobby/esp8266-upy/master/mcp4725/lib/mcp4725.py"
    url=urlIn
    filename=url.split('/')[-1] # dernier element est le nom de fichier
    r = requests.get(url)

    with open(filename, 'wb') as f:
        f.write(r.content)     

    #-- copie du fichier sur la carte Micropython 
    cmd=['python3', 'pyboard.py', '--device', portIn , '-f', 'cp', filename, ':'+filename, '--no-follow']

    p=subprocess.run(cmd)

    print("Copie de : " + filename + " depuis : " + url + " OK !")

def install(driverIn, portIn='/dev/ttyUSB0'):

    #-- copie locale du fichier
    #url = "https://raw.githubusercontent.com/mchobby/esp8266-upy/master/mcp4725/lib/mcp4725.py"
    url=urls[driverIn] # url a partir du dict
    filename=url.split('/')[-1] # dernier element est le nom de fichier
    r = requests.get(url)

    with open(filename, 'wb') as f:
        f.write(r.content)     

    print("-> Download  de : " + filename + " depuis : " + url + " OK !")

    #-- copie du fichier sur la carte Micropython 
    cmd=['python3', 'pyboard.py', '--device', portIn , '-f', 'cp', filename, ':'+filename, '--no-follow']

    p=subprocess.run(cmd)

    print("-> Copie de : " + filename + " vers " +  portIn + " OK !")

def installed(portIn='/dev/ttyUSB0'):
    import pyboard
    pyb=pyboard.Pyboard('/dev/ttyUSB0', 115200)
    pyb.enter_raw_repl()
    ret=pyb.exec("""import os
print(os.listdir())
""")

    print(ret)

    pyb.exit_raw_repl()

def listing():

    for item in urls.keys():
        print("- "+item)

Version Micropython pour ESP

A présent, on va se place dans le cas de Micropython sur une carte ESP qui a accès wifi.

On commence par initialiser le wifi :

import wifi # voir ma rubrique initialisation wifi
wifi.start("ssid", "password")

ou bien manuellement :

>>> import network
>>> station=network.WLAN(network.STA_IF)
>>> station.active(True)
True
>>> station.connect("ssid","password")
>>> station.isconnected()
True
>>> station.ifconfig()
('192.168.0.101', '255.255.255.0', '192.168.0.1', '0.0.0.0')

Ensuite, on installe le module urequests :

import upip as pip
pip.install("urequests")
Installing to: /lib/
Warning: micropython.org SSL certificate is not validated
Installing urequests 0.6 from https://micropython.org/pi/urequests/urequests-0.6.tar.gz

Ensuite :

>>>url = "https://raw.githubusercontent.com/mchobby/esp8266-upy/master/mcp4725/lib/mcp4725.py"
>>>filename=url.split('/')[-1] # dernier element est le nom de fichier
>>>print(filename)
mcp4725.py
>>>r = requests.get(url)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'requests' isn't defined
>>>import urequests as requests
>>>r = requests.get(url)
>>>print(r)
<Response object at 3fff0670>
>>>r.content()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'bytes' object isn't callable
>>>print(r.content)
b'# The MIT License (MIT)\n#\n# 2019 Meurisse D. for MCHobby.be - MicroPython backportage (keeping same license)\n# Copyright (c) 2017 Tony DiCola for Adafruit Industries - original CircuitPython\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the "Software"), to deal\n# in the Software without restriction, including without limitation the rights\n# to (....) 
def raw_value(self, val):\n        self._write_fast_mode(val)\n\n    @property\n    def normalized_value(self):\n        """The DAC value as a floating point number in the range 0.0 to 1.0.\n        """\n        return self._read()/4095.0\n\n    @normalized_value.setter\n    def normalized_value(self, val):\n        assert 0.0 <= val <= 1.0\n        raw_value = int(val * 4095.0)\n        self._write_fast_mode(raw_value)\n'

>>>with open(filename, 'wb') as f:
    f.write(r.content) 
>>>import os
>>>os.listdir()
>>>print(os.listdir())
['app', 'boot.py', 'ds18x20.py', 'ht16k33_matrix.py', 'lib', 'main.py', 'mcp4725.py', 'onewire.py', 'smoothie.js', 'static', 'test.py', 'tm1637.py', 'webrepl_cfg.py', 'wifi.py', 'ws2812.py']

Yes !

Vérification :

>>>os.rmdir('mcp4725.py')
>>>print(os.listdir())
['app', 'boot.py', 'ds18x20.py', 'ht16k33_matrix.py', 'lib', 'main.py', 'onewire.py', 'smoothie.js', 'static', 'test.py', 'tm1637.py', 'webrepl_cfg.py', 'wifi.py', 'ws2812.py']
>>>with open(filename, 'wb') as f:
    f.write(r.content) 

>>>print(os.listdir())
['app', 'boot.py', 'ds18x20.py', 'ht16k33_matrix.py', 'lib', 'main.py', 'mcp4725.py', 'onewire.py', 'smoothie.js', 'static', 'test.py', 'tm1637.py', 'webrepl_cfg.py', 'wifi.py', 'ws2812.py']
>>>import mcp4725
>>>print(dir(mcp4725))
['__class__', '__name__', 'const', '__file__', '_MCP4725_DEFAULT_ADDRESS', 'MCP4725']

On peut donc créer un fichier drivers.py de la forme :

#!/usr/bin/env python3
# by X.H. - www.micropython.fr - 2021

import urequests as requests

# listing des drivers

urls=({
"ds18x20":"https://raw.githubusercontent.com/micropython/micropython/master/drivers/onewire/ds18x20.py",
"ds18b20":"https://raw.githubusercontent.com/micropython/micropython/master/drivers/onewire/ds18x20.py",
"ht16k33_matrix":"https://raw.githubusercontent.com/hybotics/Hybotics_Micropython_HT16K33/master/ht16k33_matrix.py",
"lcd_api":"https://github.com/dhylands/python_lcd/blob/master/lcd/lcd_api.py",
"machine_i2_lcd":"https://raw.githubusercontent.com/dhylands/python_lcd/master/lcd/machine_i2c_lcd.py",
"onewire": "https://raw.githubusercontent.com/micropython/micropython/master/drivers/onewire/onewire.py", 
"tm1637":"https://raw.githubusercontent.com/mcauser/micropython-tm1637/master/tm1637.py"
})

# fonctions d'installation :

def install_from_url(urlIn):

    #-- copie locale du fichier
    #url = "https://raw.githubusercontent.com/mchobby/esp8266-upy/master/mcp4725/lib/mcp4725.py"
    url=urlIn
    filename=url.split('/')[-1] # dernier element est le nom de fichier
    r = requests.get(url)

    with open(filename, 'wb') as f:
        f.write(r.content)     


    print("Download de : " + filename + " depuis : " + url + " OK !")


def install(driverIn):

    #-- copie locale du fichier et c'est tout !
    #url = "https://raw.githubusercontent.com/mchobby/esp8266-upy/master/mcp4725/lib/mcp4725.py"
    url=urls[driverIn] # url a partir du dict
    filename=url.split('/')[-1] # dernier element est le nom de fichier
    r = requests.get(url)

    with open(filename, 'wb') as f:
        f.write(r.content)     

    print("-> Download  de : " + filename + " depuis : " + url + " OK !")

def install(driverIn):

    #-- copie locale du fichier et c'est tout !
    #url = "https://raw.githubusercontent.com/mchobby/esp8266-upy/master/mcp4725/lib/mcp4725.py"
    url=urls[driverIn] # url a partir du dict
    filename=url.split('/')[-1] # dernier element est le nom de fichier
    r = requests.get(url)

    with open(filename, 'wb') as f:
        f.write(r.content)     

    print("-> Download  de : " + filename + " depuis : " + url + " OK !")

def installed():
        import os
        print(os.listdir())

def listing():

    for item in urls.keys():
        print("- "+item)

Ensuite, il suffit de faire :

>>>import drivers
>>>print(dir(drivers))
['__class__', '__name__', '__file__', 'install', 'requests', 'urls', 'install_from_url', 'installed', 'listing']
>>>print(drivers.listing)
<function listing at 0x3ffe5fd0>
>>>print(drivers.listing())
- lcd_api
- machine_i2_lcd
- onewire
- tm1637
- ds18x20
- ds18b20
- ht16k33_matrix
None
>>>drivers.install('onvewire')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "drivers.py", line 52, in install
KeyError: onvewire
>>>drivers.install('onewire')
-> Download  de : onewire.py depuis : https://raw.githubusercontent.com/micropython/micropython/master/drivers/onewire/onewire.py OK !
>>>drivers.install('ds18b20')
-> Download  de : ds18x20.py depuis : https://raw.githubusercontent.com/micropython/micropython/master/drivers/onewire/ds18x20.py OK !
>>>drivers.install_from_url('https://raw.githubusercontent.com/micropython/micropython/master/drivers/onewire/ds18x20.py')
Download de : ds18x20.py depuis : https://raw.githubusercontent.com/micropython/micropython/master/drivers/onewire/ds18x20.py OK !

Conclusion

That is it ! C'est nickel en fait.

La suite consiste à :

  • enrichir au fil des usages la liste des drivers utilisés/utilisables / vérifiés / testés pour une plateforme donnée
  • gérer le dict dans un fichier plutôt que le mettre dans le fichier drivers
  • changer le nom pour en faire un outil plus général, pour d'autres fichiers... cf mypip par exemple.
  • ajouter une fonction qui traite un fichier d'installation = une list d'url successives à utiliser pour installer une appli "clé en main"... une fonction process() qui ouvre un fichier à partir d'une url() et traite une à une les url présentes dans ce fichier...