logo Dokumentace

Toto je hlavní část dokumentace, popisující co a jak. V dalších částech, především pak v oddíle Workshopy / tutoriály se pak dozvíte jak na to.

Originální dokumentaci k MicroPythonu najdete na stránkách 🡒 docs.micropython/esp32


Moduly, třídy, funkce

Základ pro další pochopení (Micro)Pythonu

Téměř vše v Pythonu je objekt. Objekt je kolekce dat (proměnných) a metod (funkcí), které s danými daty pracují. Prototypem objektů jsou třídy, z nichž jsou všechny objekty (čísla, řetězce, funkce, moduly, metody, atp) odvozeny coby instance. Pokud vám to není jasné, trochu podrobněji se o tom rozepisujeme na samostatné stránce: class(). Pro správné pochopení a především v kontextu práce s hardware začátečníkům doporučujeme zmíněný odkaz alespoň letmo navštívit.

Note

Toto není výuka programování – ale jen ukázky a experimenty s přihlédnutím na sadu knihoven a modulů octopusLab pro práci s vybraným HW.

Pro podrobnější proniknutí do tajů programování v Pythonu doporučujeme:


Knihovny (components | utils | lib)

Jednotlivé moduly - knihovny (podprogramy, třídy) jsme rozdělili do několika základních adresářů:

  • /components, kam postupně přidáváme jednotlivé "osamostatnělé" komponenty.

  • /lib, kde jsou převážně knihovny třetích stran, a malé fragmenty, které mají výhodu, že se při importu v adresáři lib hledají, Micropython je nalezne bez udání cesty k nim.

  • /utils, (utility) moduly octopusLAB, a třídy pro práci s periferiemi.

Uživatele vlastně nemusí zajímat, kde to je uloženo, a tak na to důraz neklademe, jen je vhodné si to pohlídat při sestavování větších projektů.

Zdroje programového kódu

Github => stable.tar => docs

Naší snahou je udržet v souladu zdroj z githubu: github.com/octopusengine/octopuslab, který se po kompilaci a komprimaci stane stable.tar a k němu udržovat aktuální dokumentaci.


Adresářová strukrura na ESP32

|-- boot.py       # inicializace po startu
|      |-- reset
|-- main.py       # hlavní soubor programu
|-- /assets       # obrázky, zvuky, tabulky
|-- /config       # kofigurační soubory (.json)
|-- /lib          # obecně dostupná knihovna (lib)
|      |-- pubsub
|      |-- FTP
|      |-- /blesync_uart
|      |-- ...
|      |-- /bmp280   # i2c Atmospheric pressure sensor
|      |-- /bh1750   # i2c Light sensor
|      |-- ST7735.py # TFT128*166 color display
|      |-- colors_rgb.py
|      |-- hcsr04.py # ultrasonic
|      |-- lcd
|      |-- ...
|
|-- /components
|      |-- led
|      |-- rgb
|      |-- analog
|      |-- button
|      |-- display7
|      |-- oled
|      |-- buzzer
|      |-- servo
|      |-- dcmotors
|      |-- [plc]
|      |-- iot
|            | Relay | PWM | Thermometer
|
|-- /utils
|      |-- setup
|      |-- octopus_lib
|            | I2C | SPI | LCD | RTC | ...
|      |-- WiFiConnect
|      |-- pinout
|      |-- bits
|      |-- transform
|      |-- database
|      |-- mqtt
|      |-- octopus # WorkFrame with WebServer
|      |-- ...
|      |-- BLE
|
|-- /pinouts      # nastavení pinů
|-- /examples      # ukázky
|      |-- /asyncio
|      |-- /ble
|      |-- /param
|      |-- /pubsub
|      |-- blink.py
|      |-- ...
|
|-- /tests
|
|-- /shell
|      |-- shell
|      |-- editor
|-- ...

Pozor

Pokud jste používali náš systém už v roce 2019, přeinstalujte si na novou verzi. Velká část systému by vám už nefungovala. Od té doby došlo totiž k řadě změn. Především byly tři zásadní verze Micropythonu, kde se měnil i formát "kompilovaných" souborů .mpy, které jsou základem naší distribuce. Také se doplnilo BLE pro práci s BlueTooth low energy. A další změnou byla velká refaktorizace systému octopus, kde podardesář util byl rozdělen na utils (pro SW utility a hlavní framework) a components (kde jsou převážně knihovny pro hw komponenty a periferie.) Také shell byl přesunut z util/shell do rootu.

Soubory boot.py a main.py

• boot.py

je soubor, který se spouští jako první po bezprostřením startu nebo po resetu ESP. Zpravidla ho neměníme. Měl by obsahovat základní obecnou inicializaci. My tam máme především definice cest k modulům:

# boot.py
def setup():
    import utils.setup
    utils.setup.setup()

def octopus():
    import utils.octopus
    utils.octopus.octopus()
    return utils.octopus

def reset():
    from machine import reset
    reset()

def shell():
    import shell
    shell.shell()
Reset

Pro zjednodušené použití resetu pomocí příkazu reset() je v boot.py přednastaveno:

def reset():
    from machine import reset
    reset()

• main.py

je hlavní soubor uživatelského programu, který budeme využívat pro své projekty. Spustí se (pokud existuje) hned po boot.py. Často používáme jednoduché kopírování existujícího programu nebo ukázky (z examples) v prostředí uPyshell:

$ cp examples/blink.py main.py


OCTOPUS Components

hwsoc Led

Třída Led je vlastně jen jednoduchým rozšířením třídy Pin. Parametr při vytváření instance je číslo pinu. led = Led(2) Přidali jsme k základní metodě value() dalších několik metod: toggle(), blink()

Zdrojový kód knihovny: ./components/led

Nejkratší varianta použití:

from components.led import Led
led = Led(2)

while True:
    led.blink()
dir(led)
['class', 'init', 'module', 'qualname', 'value', 'dict', 'pin', 'blink', 'toggle', 'state']

>>> led.pin
> Pin(2)
>>> led.state
> False
>>> led.value(1) # svítí
>>> led.state    # info o stavu
1

. TAB nabídka metod: Micropython má obrovskou výhodu v tom, že běží jako interpret: když uživatel napíše název proměnné nebo instance objektu, skoro všechno se o nich můžeme dozvědět. Napište led pak . (tečka) a stiskněte TAB:

led.
class       init        module      qualname
value       dict        pin         blink
toggle      state

note

Pro obecnější práci s využitím set_pinout() (předdefinovaných pinů) a io_config předpokládáme, že pro Octopus FrameWork máte pomocí setup() nastavenu desku ds a periférie ios.

Číslo PINu v ukázce je 2, to je svítivá dioda vestavěná v DoIt modulech i v našem ESP32boardu. Ale pro práci s obecným modulem, kde máme možnost si nastavit, kde se Led dioda nachází, použijeme pak variantu základní ukázky z examples, kde BUILT_IN_LED je konstanta, ve které je číslo PINu uloženo:

from components.led import Led
from utils.pinout import set_pinout

pinout = set_pinout()           # set board pinout
led = Led(pinout.BUILT_IN_LED)  # BUILT_IN_LED = 2

print("---examples/blink.py---")
# start main loop

while True:
    led.blink()

🡒 pinout


hwsoc Rgb

Knihovna pro plnobarevné RGB led je vytvořena především pro práci s adresovatelým modulem typu WS2812b (proto se používá zkratka WS). Naše verze je rozšířením vestavěné třídy NeoPixel. Pro řízení klasických R-G-B diod je potřeba na každou barevnou složku samostatný PIN, což využíváme jen ojediněle, jelikož volných PINů na ESP už moc nezbývá.

Zdrojový kód knihovny Rgb 🡒 components/rgb (slouží i jako ukázka, jak lze třídu rozšířit)

Třída je rozšířena o některé nové metody:

  • color(color) # pro jednu LED diodu, color ve formátu (R,G,B), 0-255
  • color(color, index) # pro více modulů, indexováno
  • simpleTest() # proběhne R, G, B
  • wheel() # z čísla vygeneruje barvu
  • random_color() # náhodná barva
  • rainbow_cycle() # duha
from components.rgb import Rgb
ws = Rgb(15) # BUILT_IN_RGB (WS) ROBOTboard
ws.color((255,0,0)) # R G B => RED

ws.simpleTest()

import colors_rgb as rgb # definice barev v /lib - BLACK (nesvítí)
ws.color(rgb.BLUE)       # zobrazení barvy, rgb.RED/rgb.GREEN ...


note

Pro obecnější práci s využitím set_pinout() (předdefinovaných pinů) a io_config předpokládáme, že pro Octopus FrameWork máte pomocí setup() nastavenu desku ds a periférie ios.

Následující ukázka naznačuje komplexnější práci s předkonfigurovanými konstantamy, které určují na kterém pinu (pinout.WS_LED_PIN) a kolik modulů máme (io_conf.get('ws')).

from components.rgb import Rgb

from utils.pinout import set_pinout
pinout = set_pinout()   # set board pinout

from utils.io_config import get_from_file
io_conf = get_from_file()

ws = Rgb(pinout.WS_LED_PIN,io_conf.get('ws'))

print("---examples/rgb_blink.py---")
ws.simpleTest()

Zdrojový kód ukázky: examples/rgb_blink.py

Pro běžnou práci je v první fázi snadnější použít předchozí variantu, ale pro rozsáhlejší projekty a práci v týmu se musí zdokumentovat použití "magické konstanty" 15 v definici ws = Rgb(15).

🡒 pinout


hwsoc Analog

Tento modul je pro práci s analogovým vstupem pomocí DAC převodníku. Opět se jedná o rozšíření základní třídy ADC, kde vytvořením instance s parametrem vstupního PINu zjednodušujeme celou inicializaci na an = Analog(33). Základní metodu read() jsme rozšířili o get_adc_aver(num), kde počítáme průměr z num neměřených hodnot.

Zdrojový kód knihovny: components/analog

from time import sleep
from components.analog import Analog

an2 = Analog(33)

while True:
    data =  an2.get_adc_aver(8)
    print(data)
    sleep(5)

hwsoc Button

Pro základní práci s tlačítky. Původně jsme používali samostatný blok s přerušením, ale knihovna pak byla přepsána tak, že využívá dekorátor @led_button.on_press, kterým uvedeme (odekorujeme) vlastní funkci on_press_top_button(), která se vyvolá vždy, když se zmáčkne tlačítko. Celá funkce pak běží na pozadí, je neblokující, a snadno i spolehlivě se dá použít i pro více tlačítek.

Zdrojový kód knihovny: components/button

from machine import Pin
from components.button import Button

boot_pin = Pin(0, Pin.IN)
boot_button = Button(boot_pin, release_value=1)


@boot_button.on_press
def boot_button_on_press():
    print('boot_button_on_press')


@boot_button.on_long_press
def boot_button_on_long_press():
    print('boot_button_on_long_press')


@boot_button.on_release
def boot_button_on_release():
    print('boot_button_on_release')

Stará verze 1.0 měla v konstruktoru číslo PINu. Nová verze 2.0 má přímo instanci PINu.

from time import sleep
from machine import Pin
from components.button import Button

led_button = Button(0, release_value=1)
built_in_led = Pin(2, Pin.OUT)

built_in_led.on()
sleep(1)
built_in_led.off()

@led_button.on_press
def on_press_top_button():
    print("on_press_top_button")
    built_in_led.on()
    sleep(3)
    built_in_led.off()

🡒 Led | @Dekorátor

Práce se čtyřmi tlačítky (na ESP32board) - v ukázce je zakomentována spolupráce s displejem tft 🡒 st7735

from utils.pinout import set_pinout
pinout = set_pinout()

from components.button import Button
from utils.transform import Point2D

print("buttons init>")
button_dwn = Button(34, release_value=1)
button_top = Button(36, release_value=1)
button_lef = Button(35, release_value=1)
button_rig = Button(39, release_value=1)

# size = 3
cursor = Point2D(63,81) # center TFT128*166 dispaly (mod 3)

def position(dx,dy):
    global mx # cursor, fb, tft
    cursor.x = cursor.x + dx*3
    cursor.y = cursor.y + dy*3

    print(cursor.x,cursor.y)
    # fb.fill(color565(*BLACK))
    # tft.blit_buffer(fb, 0, 0, tft.width, tft.height)
    # tft.fill_rectangle(cursor.x,cursor.y, 6, 6, color565(*RED))


@button_dwn.on_press
def on_press_dwn():
    print("down")
    position(0,1)

@button_top.on_press
def on_press_top():
    print("top")
    position(0,-1)

@button_lef.on_press
def on_press_lef():
    print("left")
    position(-1,0)

@button_rig.on_press
def on_press_rig():
    print("right")
    position(1,0)


hwsoc Display7

Osm sedmisegmentovek s obvodem MAX na sběrnici SPI je do začátku ideální displej pro základy práce s mikrokontrolérem. Má "retro" sedm segmentů pro zobrazení čísel - proto disp7. Obdobný modul se shodným ovladačem je matice 8x8 svítivých diod, ten jsme pojmenovali disp8.

Zdrojový kód components/display7

Před inicializací se musí nejdříve připojit SPI. V následující ukázce je "dvojitě" zakomentovaná ## obecnější metoda a použitá je spi_init() z knihovny octopus_lib.

from machine import Pin, SPI
from components.display7 import Display7
## from utils.pinout import set_pinout
from utils.octopus_lib import spi_init

print("this is simple Micropython example | octopusLAB & ESP32")

print("--- spi-init ---")
## pinout = set_pinout()
## spi = SPI(1, baudrate=10000000, polarity=1, phase=0, sck=Pin(pinout.SPI_CLK_PIN), mosi=Pin(pinout.SPI_MOSI_PIN))
spi = spi_init()

ss = Pin(pinout.SPI_CS0_PIN, Pin.OUT)
#spi.deinit() #print("spi > close")

print("--- display7-init ---")
d7 = Display7(spi, ss) # 8 x 7segment display init
d7.write_to_buffer('octopus')
d7.display()

Nejkratší variantou je octopus framework verze, kde je ale nutno mít přes setup() a ds nastavenu desku (nějčastěji ROBOTboard nebo ESP32board) a dále pomocí ios nastaveno disp7 (4 | 1)

from time import sleep
from utils.octopus import disp7_init

print("this is simple Micropython example | ESP32 & octopusLAB")
print()

d7 = disp7_init()   # 8 x 7segment display init

for i in range(999):
    d7.show(1000-i)
    sleep(1)

hwsoc Oled

Oblíbili jsme si také malý 128x64px monochromatický OLED displej. Jeho přímé použítí vyžaduje už i inicializaci I2C a další drobnosti, proto jsme většinou využívali knihovny octopus. Ale ukázalo se, že pro vlastní projekty je lepší umět spouštět displej i "samostatně", což je v ukázce: examples/test_oled.py

Zjednodušené ovládání je pak tradičně:

from utils.octopus import oled_init
oled = oled_init()
...

Základ ale vychází z knihovny ssd1306, která je už součástí Micropythonu:

def oled_init():

    from utils.pinout import set_pinout
    from machine import Pin, I2C
    import ssd1306

    # pinout = set_pinout()
    # i2c = I2C(0, scl=Pin(pinout.I2C_SCL_PIN), sda=Pin(pinout.I2C_SDA_PIN), freq=100000)
    from utils.octopus_lib import i2c_init
    i2c = i2c_init()

    oled = ssd1306.SSD1306_I2C(128, 64, i2c, 0x3c)
    return oled

oled = oled_init()
oled.text("octopusLAB", 0, 0)
oled.show()

oled.draw_image() # default /assets/octopus_image.pbm
oled.invert(0)
...
>>> from assets.icons9x9 import ICON_clr, ICON_heart
>>> oled.draw_icon(ICON_heart,115,15) 

>>> def heartBeat()               
...    oled.draw_icon(ICON_heart,115,15)
...    sleep(1)
...    oled.draw_icon(ICON_clr,115,15)
...    sleep(1)
...

hwsoc Buzzer

Pasivní piezo "pípák" slouží pro akustická upozornění, ale umí přehrát i velmi jednoduché "retro" melodie.

Zdrojový kód knihovny: components/buzzer

Základ práce:

from components.buzzer import Buzzer
piezzo = Buzzer(33)
piezzo.beep()

Doplňující třída melody jako přidání další části kódu:

from components.buzzer.melody import jingle1
piezzo.play_melody(jingle1)

hwsoc Servo

Modul pro práci se servem, opět vytvořením instance na daném PINu (musí být PWM). Hlavní metodou je pak pootočení na daný úhel: set_degree().

Zdrojový kód knihovny: components/servo

from time import sleep
from utils.pinout import set_pinout

from components.servo import Servo
pinout = set_pinout()

# s1 = Servo(pinout.PWM1_PIN)
# s2 = Servo(pinout.PWM2_PIN)
s3 = Servo(pinout.PWM3_PIN)

angles = [0, 20, 50, 70, 90]

while True:
    for a in angles:
        s3.set_degree(a)
        sleep(1)

🡒 pinout


hwsoc DCmotors

Zdrojový kód knihovny: components/dcmotors

from utils.pinout import set_pinout
pinout = set_pinout()

from components.dcmotors import Motor, Steering

motor_r = Motor(pinout.MOTOR_1A, pinout.MOTOR_2A, pinout.MOTOR_12EN)
motor_l = Motor(pinout.MOTOR_3A, pinout.MOTOR_4A, pinout.MOTOR_34EN)
steering = Steering(motor_l, motor_r)
speed = 800

steering.center(0)
steering.center(-speed)
steering.right(speed)
steering.left(speed)

hwsoc IoT

Třída, která původně sloužila jako modul pro IoTboard, ale samostatná zahrnuje relé a PWM MOS-FET řízení.

Zdrojový kód knihovny: components/iot

Ukázka:

from components.iot import Relay
re1 = Relay() # default IoTboard pin 
re1.value(1)
re2 = Relay(26)


from components.iot import Pwm
pwm_led = Pwm(33)
pwm_led.duty(300)


from components.iot import Thermometer
tt = Thermometer(32)  # DEV1 pin (ROBOTboard)
tx = tt.ds.scan()  # get list of all Dallas sensors
tt.get_temp()  # default index 0 -> first sensor
tt.get_temp(0)  # first sensor explicitly

OCTOPUS Utils

hwsoc WiFiConnect

Hlavní výhodou našeho rozšíření pro připojení k WiFi je používání uloženého nastavení (v config/wifi.json). Nastavení WiFi se provádí pomocí setup()

from utils.wifi_connect import WiFiConnect

net = WiFiConnect()
net.connect()
if not net.isconnected()
    # hard reconect
    net.sta_if.disconnect()
    net.connect()

V případě "závažnějšího problému" s připojením někdy pomůže až "tvrdý" reset.

# hard reset
reset()

Využití Octopus FrameWork na maximum - k připojení k některé z uložených WiFi postačí příkaz w():

from utils.octopus_lib import w
w()

Variantně s dostupnými metodami:

wc = w()
ip = wc.sta_if.ifconfig()[0]
...
wc.isconnected()

A opačný extrém - klasické připojení k síti s předáním parametrů ssid (název) a password (heslo):

import network
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect('ssid', 'password')

hwsoc Bits

Pro práci s jednotlivými bity. B1 = 0b11111001. Bitové operace jsme si museli do Pythonu trochu doladit, aby se s nimi pracovalo lépe a intuitivně. Používané metody:

  • neg(B1) pro negaci - vrací 0b00000110
  • reverse() obrácení pořadí bitů - vrací 0b1001111
  • get_bit(B1,1) pro získání stavu jednoho bitu > 0
  • set_bit(B1,1) pro nastavení stavu jednoho bitu
  • int2bin() pomocná funkce pro převod čísla na binární

Zdrojový kód knihovny: utils/bits

from components.bits import neg
B1 = 0b11111001
neg(B1) # > 0b00000110


hwsoc Transform

Pomocné funkce pro mechatroniku, zaměřené na transformace souřadnicových systémů a základy inversní kinematiky.

  • Point2D() class p2 = (x,y) | p2.x, p2.y
  • distance2D(p1, p2, rr = 3) vzdálenost dvou bodů v rovině
  • využívá se round - zaokrouhlení na určitý počet míst: rr = 3
  • polar2cart(r, alfa, rr = 3)
  • cart2polar(point)
  • def cosangle(opp, adj1, adj2)
  • move_2d_line(p_start, p_stop, steps = 300, max_dist = 100)
  • invkin2_1(point2d, rr = 6) inversní kinematika 1
  • invkin2(point2d, angleMode=DEGREES)
  • Point3D() class p3 = [x,y,z]
  • invkin3(point3d, angleMode=DEGREES)
  • distance3() vzdálenost dvou bodů v prostoru
  • ...
from utils.transform import Point2D, polar2cart, cosangle
p1 = Point2D(1,3)
print(p1)   # (1,3)
print(p1.x) # 1
print(p1.y) # 3
p1.x, p1.y = polar2cart(10, 0)
print(p1)
...
from utils.transform import move_servo2, cosangle

...
def move_servo2(p1, p2, delay = delay):
    steps = move_2d_line(p1, p2)
    for step in steps:
        alfa = cosangle(step[0], dist, dist)[0]
        beta = cosangle(step[1], dist, dist)[0]
        print(step, alfa, beta)

        s1.set_degree(alfa)
        s2.set_degree(beta)
        sleep_ms(delay)

p1 = 0, 0 # strart point
p2 = 50, 50 # stop point
move_servo2(p1, p2)

Více plánujeme v samostatné sekci inversní kinematika


hwsoc Database

ESP díky paměti umožňuje bez nadsázky i základní práci s databází. Zaměříme se na dvě základní: lokální btree a vzdálené MySQL, InfluxDB.

from utils.database.btreedb import BTreeDB
db = BTreeDB("test")
db.addOne("one","1")
db.listAll()

Zvídavějším doporučujeme odkaz na práci s daty a databáze 🡒 Workshop Python DATA


hwsoc MQTT

  • MQTT - jako pub-sub
  • MQTT broker - na RPi
  • orchestrátor NodeJS
  • využití v IoT

hwsoc InfluxDB

Pro zobrazování dat v Grafaně průběžně posíláme údaje na vzdálený server, který je ukládá do databáze InfluxDB. Jednoduchý příklad jednorázového odeslání jedné hodnoty (timestamp - datum a čas se přidají při ukládání automaticky):

from utils.database.influxdb import InfluxDB

influx = InfluxDB("https://your.server.com/grafana/influx/user...", "user_db", "i_usr", "i_psw", "i_measurement")

value = 25.6  # zde je treba cist hodnotu z nejakeho senzoru, napr. teplomeru
influx.write(temperature=value)
...

Metoda write musí dostat pojmenované parametry (key word arguments - tzv. kwargs), které se v Influxu použijí, jako jednotlivé fieldy. V příkladu nahoře je to temperature, kam ukládáme hodnotu ze sensoru.

Funkční ukázka např: examples/influxdb_disp7_therm.py


Abychom mohli použít identický program s minimem změn, využijeme konfigurační soubor, načteme pomocí fromconfig(). A také nechceme mít přístupové údaje ve zdrojovém kódu!

from utils.database.influxdb import InfluxDB

influx = InfluxDB.fromconfig()

temp = ...
influx.write(temperature=temp)

fromconfig() bere volitelně jako první parametr, název konfiguračního souboru, default je "influxdb", tedy soubor config/influxdb.json.

Příklad obsahu je:

{"influxdb_url": "https://parallelgarden.surikata.info:8086", "influxdb_pass": "heslo", "influxdb_name": "nazev_db", "influxdb_user": "uzivatel", "influxdb_measurement": "meteo", "influxdb_tags": {"location": "balkon"}}

Více o vytváření a editaci konfiguračních souborů 🡒 Config


OCTOPUS Lib

LCD

Knihovna třetí strany pro ovládání dvou nebo čtyř-řádkového LCD displeje připojeného k i2c expandéru, byla původně určena pro starší ESP8266, ale funguje nám bez úprav i pro ESP32 (protože komunikační protokol na i2c se nemění).

Základem v nastavení je: počet řádků rows - ½/4, a počet "sloupců" col odpovídá počtu znaků na řádku. Ukázka pro displej 2x16 - s hlavní metodou putstr(STRING).

# from machine import I2C , Pin
# i = I2C(scl=Pin(22), sda=Pin(21), freq=100000)
from utils.octopus_lib import i2c_init
i2c = i2c_init()
# i2c.scan() # > [39]
from lib.esp8266_i2c_lcd import I2cLcd
lcd = I2cLcd(i2c, 39, 2, 16) # addr, rows, col
lcd.putstr("octopusLab") # write text
...

Pomocí speciálních znaků (už implementovaných) lze vykreslit například stupně Celsia (pro zobrazování teploty):

lcd.putstr(chr(223))

Nastavení kurzoru move_to() a ukázka zobrazení hodin s blikající dvojtečku:

def clock():
    lcd.move_to(5,1)
    lcd.putstr(get_hhmm(":"))
    sleep(0.5)
    lcd.move_to(5,1)
    lcd.putstr(get_hhmm(" "))
    sleep(1)

V našem podadresáři /assets máme v souboru lcd_chars.py tabulky některých "nově definovaných" znaků pro LCD:

import assets.lcd_chars as ch

from utils.octopus import lcd2_init
lcd = lcd2_init()

lcd.custom_char(0, ch.happy)
lcd.putchar(chr(0))

lcd.custom_char(1, ch.clock)
lcd.putchar(chr(1))

...

ST7735

Barevný displej TFT 128x160, který ale vyžaduje při práci s Micropythonem větší paměť.

Doporučené připojení k ESP32board:

Display | ESP32board
------------------------
1-RST   | PWM2 (16)
2-CS    | SCE0 (5)
3-D/C   | PWM1 (17)
4-DIN   | SPI_MOSI (23)
5-CLK   | SPI_CLK (18)
6-UCC   | 5V
7-BL    | 3V3
8-GND   | GND

Nová verze

from ST7735 import TFT, TFTColor
from sysfont import sysfont
from machine import SPI, Pin
from time import sleep_ms, ticks_ms
from math import pi
from utils.octopus_lib import w # need connection for FTP

SPI_SCLK = 18
SPI_MISO = 19
SPI_MOSI = 23

DC = 17   # PWM1
RST = 16  # PWM2
CS = 5    # SCE0

print("--- TFT 128x160px test ---")

spi = SPI(2, baudrate=20000000, polarity=0, phase=0, sck=Pin(SPI_SCLK), mosi=Pin(SPI_MOSI), miso=Pin(SPI_MISO))
tft=TFT(spi, DC, RST, CS)
tft.initr()
tft.rgb(True)

tft.fill(TFT.BLACK)
v = 30
tft.text((0, v), "octopus LAB (1)", TFT.RED, sysfont, 1, nowrap=True)
v += sysfont["Height"]

tft.fill(TFT.BLACK)
tft.rotation(1)
tft.text((0, 0), "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur adipiscing ante sed nibh tincidunt feugiat. Maecenas enim massa, fringilla sed malesuada et, malesuada sit amet turpis. Sed porttitor neque ut ante pretium vitae malesuada nunc bibendum. ", TFT.WHITE, sysfont, 1)
sleep_ms(2000)

...

Původní starší verze

from machine import Pin, SPI, SDCard
from time import sleep, sleep_ms

from utils.pinout import set_pinout
pinout = set_pinout()

import framebuf
from lib import st7735
from lib.rgb import color565

print("spi.TFT 128x160 init >")
spi = SPI(1, baudrate=10000000, polarity=1, phase=0, sck=Pin(pinout.SPI_CLK_PIN), mosi=Pin(pinout.SPI_MOSI_PIN))
ss = Pin(pinout.SPI_CS0_PIN, Pin.OUT)

rst = Pin(16, Pin.OUT) #PWM2 (16) / DEv3(27)
cs = Pin(5, Pin.OUT)   #SCE0 (5)
dc = Pin(26, Pin.OUT)  #PWM1 (17) /  IO26

tft = st7735.ST7735R(spi, cs = cs, dc = dc, rst = rst)

print("spi.TFT framebufer >")
fb = framebuf.FrameBuffer(bytearray(tft.width*tft.height*2), tft.width, tft.height, framebuf.RGB565)
fbp = fb.pixel

fb.line(128,0,0,166,color565(0,255,0))
tft.blit_buffer(fb, 0, 0, tft.width, tft.height)
...

🡒 pinout

examples/test_tft128x160.py


hcsr04

Ultrazvukový měřič vzdálenosti.

from time import sleep
from util.pinout import set_pinout

pinout = set_pinout()

from hcsr04 import HCSR04
print("ulrasonic distance sensor")
echo = HCSR04(trigger_pin=pinout.PWM2_PIN, echo_pin=pinout.PWM1_PIN)

while True:
    echo_cm = echo.distance_cm()
    print(echo_cm)
    sleep(1)

🡒 pinout

examples/ultrasonic.py


hwsoc pubsub

Nástroj pro předávání hodnot mezi nezávislými komponenty v rámci projektu a to i v samostatně běžících vláknech. Pracuje na principu publish and subscribe. Fork z basecue/micropython-pubsub.

Zdrojový kód knihovny: ./lib/pubsub.py

Základ práce: jedno vlákno (nebo část programu) publikuje získané hodnoty metodou publish kde parametrem je topic a hodnota value. Napříkladpubsub.publish('topic', value). (value může být libovolný objekt). V jednoduché ukázce jednou za vteřinu generujeme náhodná čísla, která "publikujeme". (pozor, používáme while True: - je to blokující, lepší je použít timer)

from time import sleep
from os import urandom
import pubsub


print("start: ps_random.py")

while True:
    value =  int(urandom(1)[0])
    print("rnd.: ", value)
    pubsub.publish('value', value)
    sleep(1)

Protistrana je odebírá / naslouchá. A může je třeba zobrazovat na displeji:

import pubsub
from utils.octopus import disp7_init

d7 = disp7_init()  # 8 x 7segment display init


@pubsub.subscriber("value")
def display_num(value):
    d7.show(value)

🡒 Disp7

Ukázky jsou z vybraných příkladů pro pubsub: examples/pubsub


hwsoc BLE

Jelikož obecná problematika BLE (Bluetooth low energy) je poměrně obsáhlá, tak i modul BLE je dost robustní. Zahrnuje několik částí: blesync, blesync_client, blesync_server a samostatný modul blesync_uart. Každopádně funguje velmi dobře a snahou bylo, aby práce s ním byla srozumitelná a přitom umožnila využít všechny možné výhody, které BLE obecně přináší. Projekt má svůj vlastní repozitář: /blesync.

Následující příklad umožní z mobilní aplikace nalézt ESP zařízení jako octopus-led-UID, kde UID je čás unikátního ID, které má každé ESP. Pomocí mobilní aplikace šipkami nahoru (Up) a dolů (DOWN) pak ovládáme vestavěnou Led diodu.

import blesync_server
import blesync_uart.server
import utils.ble.bluefruit as bf

from shell.terminal import getUid
uID5 = getUid(short=5)

from time import sleep
from components.led import Led
led = Led(2)


@blesync_uart.server.UARTService.on_message
def on_message(service, conn_handle, message):
    if message == bf.UP:
        led.value(1)
    if message == bf.DOWN:
        led.value(0)
    if message == bf.RIGHT:
        led.toggle()

    service.send(conn_handle, message)

_connections = []


devName = 'octopus-led-'+uID5
print("BLE ESP32 device name: " + devName)

server = blesync_server.Server(devName, blesync_uart.server.UARTService)
server.start()

hwsoc Mobilní aplikace pro BLE

Používáme Bluefruit connect od společnosti Adafruit. Jeden z odkazů na play.google.com/store/apps

Vytvořili jsme si pomocnou knihovnu pro "překládání" jimi definovaných kódů, která je zatím zde ./utils/ble/blefruit.py:

UP =   b'!B516'
DOWN = b'!B615'
LEFT = b'!B714'
RIGHT = b'!B813'
...

S touto knihovnou pak pracujeme takto:

import utils.ble.bluefruit as bf
...
    if message == bf.UP:
        led.value(1)
    if message == bf.DOWN:
        led.value(0)

Ostatní podpůrné moduly

hwsoc Config

Micropython má elegantně propacovanou práci se soubory (nahrávání a čtení) a i s json formátem, proto jsme toho využili pro externí konfigurační soubory. V adresáři /config jsou nahrány jednotlivé "jsony", které v sobě obsahují nějaké konstanty, nastavení a podobně. Proto můžeme daný projekt dynamicky konfigurovat. Využíváme to i v nastavení PINů jednotivých zařízení device.json nebo pro uložení přístupů k WiFi wifi.json.

Zdrojový kód knihovny: ./config/init.py

Jednotlivé metody pro základní varianty používání:

1) Vytvořením pomocí konstruktoru: myConfig = Config("myConfigFile",keys)
Vyžaduje předefinici klíčových "metrik": keys = ["tempMax","tempMin"]

  • setup()
  • print() # for keys

Mějme ukázkový projekt termostat, který na základě změřené teploty pustí buď topení, nebo chlazení (ventilátor). Volitelně si můžeme v programu definovat keys, kde máme uloženy názvy podstatných konstant. Instanci pak vytváříme conf = Config("your_file", keys), kde "your_file" je název - nejčastěji shodný s názvem projektu. Například "termostat". Se souborem se pak dá pracovat několika metodami, například: setup() (interaktivní mód - používáme nejčastěji), create_from_query(), set(), save() ...

>>> from config import Config
>>> keys = ["tempMax","tempMin"]
>>> conf = Config("termostat", keys) # > config/termostat.json
>>> conf.setup()

==================================================
        S E T U P - config/termostat.json
==================================================
[ 1] -          tempMax -
[ 2] -          tempMin -
[q] - Quit from json setup
==================================================
select:

Vidíme, že nastavení config je snadné. Stačí vyplnit nebo modifikovat interaktivní tabulku.

2) Vytvořením pomocí konstruktoru bez keys: myConfig = Config("myConfigFile")

  • print_all()
  • get("key")
  • set("key",value)
  • create_from_query("a=1&b=2") # key1 = "a", value1 = 1 ...
  • save()
# vytvoření konfigu: a = 1, b = 2
>>> conf = Config("your_config")
>>> conf.create_from_query("a=1&b=2")
{'a': '1', 'b': '2'}
>>> conf.set("c",3)
>>> conf.save()
Writing new config item to file config/your_config.json
>>> conf.print_all()
-----------------------------------------
                  a - 1
                  c - 3
                  b - 2
-----------------------------------------
>>>

Ve svém programu pak config použijeme následovně:

from config import Config
conf = Config("your_config")
a =  conf.get("a") # 1
b =  conf.get("b") # 2

Pro lepší pochopení datových struktur nastudujte 🡒 /ws-python-data


hwsoc octopus_lib

I2C

i2c_init()

>>> from utils.octopus_lib import i2c_init
>>> i2c = i2c_init()
>>> i2c.scan()
# I2C address:
OLED_ADDR = 0x3c
LCD_ADDR = 0x27
bhLight = 0x23
bh2Light = 0x5c
tslLight = 0x39

# PCF8574           PCF8574A
# AAA - hex (dec)
# 210
# 000 - 0x20 (32)   0x38 (56)
# 001 - 0x21 (33) * 0x39 (57)
# 010 - 0x22 (34)   0x3A (58)
# 011 - 0x23 (35) * 0x3B (59)
# 100 - 0x24 (36)   0x3C (60)
# 101 - 0x25 (37)   0x3D (61)
# 110 - 0x26 (38)   0x3E (62)
# 111 - 0x27 (39)   0x3F (63)
# * ROBOTboard
# některé další základní metody
ADDR = 42
i2c.writeto(ADDR, b'123')  # write 3 bytes to slave with 7-bit address 42
i2c.readfrom(ADDR, 8)      # read 8 bytes from slave with 7-bit address 42

i2c.readfrom_mem(ADDR, 8, 3) # ...

Odkaz na originální Micropython dokumentaci k I2C 🡒 https://docs.micropython.org/en/latest/library/machine.I2C.html


SPI

spi_init()

from machine import Pin, SPI
from components.display7 import Display7
## from utils.pinout import set_pinout
from utils.octopus_lib import spi_init

print("--- spi-init ---")
## spi = SPI(1, baudrate=10000000, polarity=1, phase=0, sck=Pin(pinout.SPI_CLK_PIN), mosi=Pin(pinout.SPI_MOSI_PIN))
spi = spi_init()

ss = Pin(pinout.SPI_CS0_PIN, Pin.OUT)
#spi.deinit() #print("spi > close")
...
# některé další základní metody
SPI.deinit()
SPI.read(nbytes, write=0)
SPI.readinto(buf, write=0)
SPI.write(buf)
SPI.write_readinto(write_buf, read_buf)
...

Odkaz na originální Micropython dokumentaci k SPI 🡒 https://docs.micropython.org/en/latest/library/machine.SPI.html


RTC

Modul reálného času má Micropython v poslední verzi doplněn i o ntp modulem ntptime.

Co se skrývá v 🡒 ntptime? Především metoda settime(), pomocí které získáme přesný čas ze serveru pool.ntp.org.

from ntptime import settime
from machine import RTC
from utils.octopus_lib import w, get_hhmm, setlocal


rtc = RTC()
w() # připojení k internetu
settime()
print(get_hhmm(rtc))

# + 2 h.
setlocal(2)
print(get_hhmm(rtc))

Zdroj ukázky 🡒 test_rtc_ntp.py

Odkaz na originální Micropython dokumentaci k RTC 🡒 https://docs.micropython.org/en/latest/library/machine.RTC.html


hwsoc pinout

Práci s PINy ("nožičky" kontroleru) nám ulehčuje přednastanený pinout, který je uložený v konfiguračním souboru. Konfigurační soubory pro jednotlivé hw moduly jsou v samostatném adresáři /pinouts. Podle toho, jakou máme HW platformu, máme přesně svázány konstanty (čísla PINů) s jejich názvy. Vybrané soubory zapojení pinů jsou na samostatné stránce 🡒 pinouts.

Zdrojový kód knihovny: 🡒 utils/pinout

Princip je jednoduchý: máme definovány číselné konstanty (v programu se neměnící čísla), například pro vestavěnou Led diodu: BUILT_IN_LED = const(číslo). Číslo je zde číslo PINu a může se lišit podle dané desky (deska je nastavena příkazem >>> setup()). Tato konstanta je uložena v souborech pinouts/file_name. Pro ROBOTboart je to 2, takže v souboru pro definici pinů najdete řádek BUILT_IN_LED = const(2). Výchozí společné piny jsou v /pinouts/olab_esp32_base.py a ROBOTboard je přebírá.

Jak se s modulem pinout pracuje? Můžete si zkusit z terminálu Micropythonu: >>>

>>> from utils.pinout import set_pinout
>>> pinout = set_pinout()

A už máme dostupné piny na pinout.NAZEV_PINU, dají se zjistit i osatní PINy, po pinout tečka TAB:

>>> pinout.
__class__       __name__        const           __file__
WS_LED_PIN      ONE_WIRE_PIN    PIEZZO_PIN      MOTOR_12EN
MOTOR_34EN      MOTOR_1A        MOTOR_2A        MOTOR_3A
MOTOR_4A        ANALOG_PIN      PWM1_PIN        PWM2_PIN
PWM3_PIN        SERVO_MIN       SERVO_MAX       I39_PIN
DEV1_PIN        DEV2_PIN        BUILT_IN_LED    HALL_SENSOR
I2C_SCL_PIN     I2C_SDA_PIN     SPI_CLK_PIN     SPI_MISO_PIN
SPI_MOSI_PIN    SPI_CS0_PIN     RXD0            TXD0
BUTT1_PIN       BUTT2_PIN       BUTT3_PIN       DEV3_PIN
...

>>> pinout.BUILT_IN_LED
2

Vidíme, že pro BUILT_IN_LED nám "dohledá" číslo deklarované 2, takže se to dá použít:

led = Led(pinout.BUILT_IN_LED)      # BUILT_IN_LED = 2

je shodné s:

led = Led(2)

ale číslo 2 si nemusíme pamatovat, navíc u různých modulů se může lišit.

Celá ukázka pro blikání vestavěné Ledky na různých modulech - může být na PINu 2 nebo také na 15... nebo úplně jiném. A my toto číslo při správné konfiguraci modulu (desky) nemusíme řešit a k vestavěné Ledce přistupujeme názvem PINu: BUILT_IN_LED.

from components.led import Led
from utils.pinout import set_pinout   # import library

pinout = set_pinout()   # set board pinout

led = Led(pinout.BUILT_IN_LED)

# start main loop
while True:
    led.blink()

🡒 Led


hwsoc Dekorátor

Možná jste si v některých našich ukázkách všimnuli speciálního použití @ před definicí funkce, například v 🡒 pubsub

@pubsub.subscriber("value")
def display_num(value):
    d7.show(value)

nebo v 🡒 button

@led_button.on_press
def on_press_top_button():
    print("on_press_top_button")
    built_in_led.on()

Dekorátor v Pythonu je funkce, která dostane jeden argument (funkci) a vrátí jednu hodnotu - opět funkci, která je modifikovanou verzí funkce původní. Původní funkce ja takzvaně "odekorovaná".

Použití dekorátorů velmi zjednoduší a zpřehlení váš kód. Používá se na registraci, modifikaci a podobně.

@dekorator
def funkce():
    pass

# je stejné jako:

def funkce():
    pass
funkce = dekorator(funkce)

Speciální @octopus_debug dekorátor 🡒 /octopus_decor.py vrací například čas, který trvalo provedení "odekorované" funkce.

@octopus_debug
def yourFunc(): 
    ...

Zrychlení práce procesoru

Zkoušíme dekorátory @micropython.native nebo @micropython.viper - podrobněji popsáno přímo na stránkách 🡒 micropython/reference/..speed_python

Jak se dá rychlost testovat (je použit další dekorátor @octopus_debug - ten vrací čas běhu funkce):

from utils.octopus_decor import octopus_debug


@octopus_debug
def test1():
    for i in range(10000000):
        xx=i

@octopus_debug
@micropython.native
def test2_native():
    for i in range(10000000):
        xx=i

@octopus_debug
@micropython.viper
def test3_viper():
    for i in range(10000000):
        xx=i



>>> test1()
=== function name:  test1
=== duration (sec.) ---> 41

...

>>> test2_native()
=== function name:  test2
=== duration (sec.) ---> 6

...

test3_viper()

Web server - IDE - jednoduché ovládání

Jsme vyčlenili samostatně - zatím zde: micropython-web-ide

Web server

ESP32 má dostatečný výkon, aby na něm mohl běžet jednoduchý webový server (server s jednoduchými "html" stránkami, k kterému se v lokální síti připojíme přes IP ESPčka)

>>> from utils.octopus_lib import w, web_server
>>> w()
>>> web_server()