Szeregowanie kooperacyjne w Micropythonie na ESP8266.

Ku mojemu zdziwieniu Micropython na esp8266 nie obsługuje wątków, a potrzebna mi była funkcja „sleep()”, która będzie się wykonywać równolegle do głównego wątku. Dziwne to, bo z FreeRTOS SDK wątki ponoć działają, choć całkiem możliwe, że kosztują zbyt wiele cennych zasobów potrzebnych Micropythonowi do życia. Znalazłem też info, że wynika to z ograniczeń zestawu instrukcji procesora. Cóż, FreeRTOS działa super na esp32 i niech tak zostanie. Niestety bez wątków funkcja zawłaszcza procesor licząc czas w pętli i jak chciałoby się wcześniej zasterować podłączonymi do esp8266 ustrojstwami, to trzeba by to zrobić manualnie ;). Na szczęście dostępna ilość pamięci pozwala na użycie modułu uasyncio i nieblokujących się socketów, bo to też istotne jeśli chcemy obsługiwać sprzęt przez requesty http typu <adres_ip>/onoff czy <adres_ip>/sleep=czas. Jedyny minus, to upip, którym nie zainstalujemy uasyncio ze względu na zbyt małą ilość pamięci. Trzeba to zrobić ręcznie.

import network
import usocket as socket
import errno

sta_if = network.WLAN(network.STA_IF)
addr = socket.getaddrinfo(sta_if.ifconfig()[0], 8000)[0][-1]
s = socket.socket()
s.setblocking(False)
s.bind(addr)
s.listen(1)
print('listening on', addr)

import machine
pin = machine.Pin(5, machine.Pin.OUT)
status = True; timeout = None
pin.value(status)

import uasyncio as asyncio
loop = asyncio.get_event_loop()

async def sleep(newtm):
    global status, timeout
    if timeout == newtm:
        return
    if timeout != None:
        timeout = newtm
        return
    else:
        timeout = newtm
    while timeout > 0:
        await asyncio.sleep(60)
        timeout = timeout - 60
    status = False; timeout = None
    pin.value(status)

async def worker():
    global status
    while True:
        line = None
        try:
            cl, addr = s.accept()
            print('client connected from', addr)
            cl_file = cl.makefile('rwb', 0)
            while not line:
                line = cl_file.readline()
        except OSError as exc:
            if exc.args[0] in [errno.ETIMEDOUT, errno.EAGAIN]:
                await asyncio.sleep(0)
                continue
        print(line, status)
        if 'onoff' in line:
            status = not status
            pin.value(status)
        if 'sleep' in line:
            s1 = line.find(b'sleep')+6
            s2 = line.find(b'H')-1
            try:
                timeout = int(line[s1:s2])
                loop.create_task(sleep(timeout*60))
            except ValueError:
                print('/sleep needs a numeric timeout')
        cl.close()

loop.create_task(worker())
loop.run_forever()

# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

Ten sam skrypt na esp32 z użyciem _thread. Esp32 to już jednak potężna bestia, z ogromną ilością pamięci, zwłaszcza z PSRAM na pokładzie, oraz sporym zapasem mocy procesora dual-core ;).

JS / Ecmascript w QtQuick.

Od lat krążę wokół tematu ale trafiłem ostatnio na fajną książkę o ES6 (Learning JS z O’Reilly’ego) i sporo rzeczy w tej wersji standardu mi się podoba. Na tyle to fajne, że postanowiłem trochę poeksperymentować. Nieprzypadkowo akurat z ES6, bo Qt5.12 ma obsługiwać Ecmascript w wersji 6-tej. Fajne toto i szybkie, a z Qt nie musi być przywiązane do backendu, czy przeglądarki:

import QtQuick 2.11
import QtQuick.Window 2.11

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("JS fun 2")

    Rectangle {
        id: rect
        width: parent.width-100; height: parent.height-100
        border.color: 'red'
        border.width: 10
        anchors.centerIn: parent

        function get_fibo(n) {
            function fibo(n) {
                if (n < 1)
                    return 0;
                if (n <= 2)
                    return 1;
               return fibo(n-1) + fibo(n-2);
            }
            var ret = '';
            for (var i=0; i<n; i++)
                ret += fibo(i) + ' ';
            return ret;
        }

        Text {
            text: parent.get_fibo(15)
            anchors.centerIn: parent
        }
    }
}
fib_js
JS w QML nie wymaga magicznych sztuczek. Można go sobie ot tak używać, jak widać w powyższym przykładzie.
Nieco dziwny jest scoping w QML, bo jeśli obiekt który wywołujemy znajduje się w obiekcie nadrzędnym, trzeba się do niego dostać np. przez parent albo id. Jeśli nie, trzeba obiekt, np. funkcję wrzucić do obiektu najgłówniejszego ;). Może ma to jakieś głębsze uzasadnienie, a może to się zmieni w implementacji ES6.

Zyn-fusion, Helm.

Ostatnio eksperymentuje trochę z soft-syntezatorami na Linuksie i zupełnie przy okazji odkryłem Unfę:

Tobiasz jest żywym dowodem na to, że da się na Linuksie produkować muzykę, a wszystkie dźwięki na tej płycie powstały w zyn-fusion. Dla mnie to jest rewelacyjna zabawa w dźwięki, poznawanie skal i interwałów, których na gitarze nigdy nie mogłem ogarnąć ;), ale zarówno zyn-fusion jak i helm są po prostu świetne.

Minimalizm, czyli i3-wm + connman.

Od kilku lat używałem Plasma Desktop, ale mój sprzęt nie jest bardzo szybki i czas startu tego środowiska zaczął być uciążliwy. Nie byłem przekonany co do tzw. tiling window managerów, jednak powoli zmieniam zdanie.
Zmiany środowiska zwykle generują problemy, które rozsądnie byłoby szybko rozwiązać. U mnie problemem okazał się Network Manager, którego szczerze mówiąc nigdy nie lubiłem. Wszystkie te dodatkowe warstwy (polkity i inne wynalazki) czasem po prostu wchodzą w drogę, jednak connman, jako alternatywa dla Network Managera sprawdził się doskonale i raczej na pewno zostanie u mnie na dłużej.

i3wm najbardziej trafnie określa powyższy zrzut ekranu. Po raz kolejny zdałem sobie sprawę, że tak na prawdę do pełni szczęścia potrzebny mi dobrze działający dostęp do sieci i programy typu dmenu i rofi jako wygodna alternatywa tzw. menu „Start”.

Szybkość i wygoda jest na tyle duża, że oba moje laptopy działają od dziś z i3-wm.

Jackd i PulseAudio.

Jackd + Chromium albo Firefox = problem. Tak się jakoś porobiło w Linuksie, że PulseAudio stało się standardem w obsłudze audio i chyba nie ma w tym nic złego. Mimo tego, że Firefox w Archlinuksie  skompilowany jest z obsługą jackd, to wyjście jackd w FF nie działa. Na szczęście nie jestem z obozu ultra-tradycjonalistów i udało mi się szybko i prosto pożenić jackd z Pulseaudio.

Potrzebny będzie pakiet pulseaudio-jack zawierający module-jack-sink.so i module-jack-source.so​.  Do /etc/pulse/default.pa trzeba dodać te moduły:

load-module module-jack-sink
load-module module-jack-source

Każdorazowo po odpaleniu jackd pulseaudio musi zostać przeładowane: pulseaudio -k, a potem: pactl set-default-sink jack_out. Dzięki temu pulseaudio przekieruje audio systemowe na jackd, co pozwala jak w moim przypadku bębnić w realtime do kawałków na Youtube. Pozytywne jest to, że po ubiciu jackd, PulseAudio przywraca odtwarzanie na defaultowej karcie dźwiękowej „w locie”.

Przykre, że jackd nie jest traktowane jak należy, a użytkowników w niszy audio na Linuksie chyba nie ma wielu i od przynajmniej dekady nic się w temacie nie robi, żeby im ułatwić życie.

FreeRTOS tasks.

RTOS w Esp-idf to jest to, co tygryski lubią najbardziej, ale nikt mi nie powie, że Arduino-IDE nie jest fajne? Ani pół include-a i działa :).

//Isn't it cool? 

void setup() {

  Serial.begin(115200);
  delay(1000);

  xTaskCreate(
                taskOne,          /* Task function. */
                "TaskOne",        /* String with name of task. */
                2000 ,            /* Stack size in words. */
                NULL,             /* Parameter passed as input of the task */
                1,                /* Priority of the task. */
                NULL);            /* Task handle. */

  xTaskCreate(
                taskTwo,          /* Task function. */
                "TaskTwo",        /* String with name of task. */
                2000,             /* Stack size in words. */
                NULL,             /* Parameter passed as input of the task */
                1,                /* Priority of the task. */
                NULL);            /* Task handle. */
}

void loop() {
  delay(1000);
}

void taskOne( void * parameter )
{
    for( int i = 0;i<10;i++ ){

        Serial.println("Hello from task 1");
        delay(1000);
    }
    Serial.println("Ending task 1");
    vTaskDelete( NULL );
}

void taskTwo( void * parameter)
{
    for( int i = 0;i<10;i++ ) {
        Serial.println("Hello from task 2");
        delay(1000);
    }
    Serial.println("Ending task 2");
    vTaskDelete( NULL );
}

Swoją drogą fajny opis, świetna książka, a ten opis SMP i przeportowanych funkcji RTOS v9 poprostu trzeba przeczytać 😉

Kompletnie nie rozumiem, dlaczego ludzie na Arduino-IDE tak psy wieszają? Dla początkujących jest świetne, geek ugotuje w nim coś na szybko, a SDK nie zając :).

 

Esp8266ex.

Po raz kolejny odkrywam Amerykę jakieś 2 lata za późno ;). Esp8266ex zafascynował mnie swoją prostotą w połączeniu z Arduino-IDE, a dostosowanie przykładu serwera Wifi do swoich potrzeb to kilka minut (potrzebuje sterować przekaźnikiem tranzystorowym z komórki).

Wemos D1 mini Pro ma chyba wszystko czego mi trzeba, ale do kompletu potestuje jeszcze Wemos Lolin32 ;). Eksperymenty z układami Esp będę wrzucał tutaj.

Oswajanie asm.

Ostatnie moje pomysły skłoniły mnie do dalszego oswajania madsa 🙂

adres equ $80

      org $600
      print_line #napis1
      print_line #napis2
      print_line #napis3
      jmp *

print_line .proc (.word adres) .var
      ldy #0
@     phr
      lda (adres),y
      beq @+
      print_char
      plr
      iny
      jmp @-
@     plr
      rts
.endp

print_char .proc
      tax
      lda $0347 ;ICPTL/H from IOCB #0
      pha
      lda $0346
      pha
      txa
      rts
.endp

napis1 .by 'Atari',$9b,0
napis2 .by 'rulez',$9b,0
napis3 .by '!!!!!!!!!!',$9b,0

Spodobała mi się sztuczka z wypchaniem na stos wektora i rts, co  powoduje „powrót” pod adres ze stosu (zamiast jsr). Druga sztuczka, której zastosowania jeszcze przede mną do odkrycia, to magiczne gwiazdki:

      org $600
      ldy #0
      sty test
test  equ *+1
      lda #111
      sta 710
      jmp *

*+1 to bieżące położenie w pamięci + 1 bajt, czyli wartością spod etykiety można nadpisać operand. Takie programowanie generyczne sprzed trzech dekad :). No i jmp * sam sobie wykoncypowałem.