[CA80][Raspberry] Log z Malin, monitorowanie CA80 przez Raspberry PI Zero

Kącik dla elektroniki retro - układy, urządzenia, podzespoły, literatura itp.
Awatar użytkownika
tasza
Geek
Geek
Posty: 1082
Rejestracja: czwartek 12 sty 2017, 10:24
Kontaktowanie:

[CA80][Raspberry] Log z Malin, monitorowanie CA80 przez Raspberry PI Zero

Postautor: tasza » czwartek 19 gru 2019, 17:28

♬ ☘ Moja muzyka do kodowania ♬ ♬ ♬ ☘
♫ ♩ ♪ Avantasia ⚡ ☘ ⚡ The Raven Child (z albumu Moonglow) ♪ ♩ ♫
https://www.youtube.com/watch?v=CTN2ZzpQRh4


intro

Temat wykluł się z nagła podczas korespondencji z gawłem odnośnie nie do końca działającego kodu, a mianowicie (i nadzieje mam, że tajemnic państwowych tu nie ujawniam): pewien programik dedykowany na sprzęt miał za zadanie wykonywać operacje na urządzeniu zgodnie z zaimplementowanym harmonogramem. Akcje włącz/wyłącz zaplanowane były w robocze dni tygodnia, nieco inne rzeczy miały się zadziać w weekend. No i jak łatwo zgadnąć coś nie pykło i program akurat w weekend zadziałał błędnie. Problem w tym taki, że niezależnie weryfikowany algorytm wydawał się poprawny, nawet na drugi i trzeci rzut oka, piszę – niezależnie, bo miałam przyjemność podłubać w tym kodzie robiąc eksperymenty na własną rękę. No ale guzik – wszystko niby pasowało, a jednak incydent miał miejsce. Utrudnieniem w tym wszystkim jest fakt, że aplikacja napędzana jest zegarem czasu rzeczywistego i fizyczne efekty działania można oczywiście obserwować, ale na przestrzeni dłuższych odcinków czasowych, godzin czy wręcz dni. Pewną metodą weryfikacji jest przyspieszenie zegara czyli podłożenie własnej implementacji naliczania czasu, tak aby program relatywnie szybko przeszedł przez krytyczne (i być może podejrzane) punkty, tak też zrobiłam. Tu sęk dla odmiany taki, że zmieniamy dziedzinę czasu ale tylko w programie, jego otoczenie pracuje dalej w rzeczywistym tempie, co może utrudnić odtworzenie przypadku.

No i tu dochodzimy do innej metody odszukania problemu – pozwólmy aplikacji pracować w zwykłych warunkach, ale monitorujmy jej działanie, a przy okazji w miarę możliwości – stan otoczenia. W dużych, smutnych systemach zagadnienie jest niejako naturalne – to logowania zdarzeń zachodzących w aplikacji użytkowej. Ale co zrobić w przypadku takiego na przykład CA80 czy innego, ubogiego w zasoby malca?

pomysł

No, więc to, co poniżej to nie tyle projekt ale bardziej drobne `know-how` jak w miarę komfortowo śledzić pracę programu, zbierać i archiwizować dane z realnego wykonania. Założenia są następujące:

- wykorzystanie drugiego kanału szeregowego CA80 do zbierania informacji, transmisja w jedną stronę
- dopuszczalny jest drobny, ale jednak konieczny narzut obliczeniowy na w/w transmisję
- logujemy nie tylko aktywność programu ale i stan otoczenia (wybrane linie we/wy)
- zbierane dane zapisujemy trwale w niezależnej lokalizacji
- czas monitorowania ograniczony tylko pojemnością magazynka na dane i ciągłością zasilania
- dostęp do zgromadzonych danych nie może zakłócać procesu ich pozyskiwania i powinien być relatywnie wygodny
- instalacja ma być łatwa do modyfikacji ad-hoc

Patrząc na powyższe naturalnym wyborem staje się przygotowanie zestawu logującego z wykorzystaniem komputerka Raspberry PI w wersji Zero z WiFi, szczególnie że akurat taki mam teraz pod ręką. Zalety ma takie, że w środku siedzi zwykły Linux więc z oprogramowaniem całości nie będzie problemów, karta SD o pojemności kilkanaście GB pomieści naprawdę sporą ilość danych archiwalnych. Dostęp do systemu mamy bezprzewodowy – to całościowo wydaje mi się bardziej komfortowe w użyciu niż podłączanie zwykłego laptopa (a taki pomysł padł w mejlach). No i takie PI możemy bezstresowo zostawić na przykład w szklarni czy w ogrodowej altanie, jak kuny nie zauważą - to nic się nie stanie.

realizacja

Po porcji wody - kolejna, ale pierwej schemat ideowy instalacji, paraliżującej wręcz prostoty.

00_schemat1.png


Ponieważ CA80 i PI to napięciowo dwa różne światy – zastosowałam mały, kupny konwerterek TTL/3V3,
:arrow: https://botland.com.pl/pl/konwertery-na ... -msx-.html jego cztery kanały robocze są w zupełności wystarczające. Najważniejsza jest oczywiście trasa TxD (CA80)-RxD(PI), tu ewentualnie wystarczyłby zwykły dzielnik napięcia (od którego w sumie zaczęłam eksperymenty). A robić dodatkowych druciaków już mi się nie uśmiechało i stąd konwerterek.

10_log_div.jpg

20_log_conv.jpg


Do PI na porty GPIO.20/21 wprowadzamy też stany logiczne z portów PA.0/1 systemu CA80, to będzie właśnie nasz ‘monitoring’ otoczenia, tu dodatkowo musimy wyobrazić sobie, że do nich podłączone są elementy wykonawcze, nie wiem...jakiś dzwonek czy lampka przywołująca pazia z wachlarzem, ewentualnie wentylator.

CA80

Aplikacja na CA80 kręci się w nieskończonej pętli obserwując zegar czasu rzeczywistego, a zadania ma dwa: co dziesięć sekund zmieniać stan logiczny wyjścia PA.0 na przeciwny oraz tak sterować wyjściem PA.1 aby miało stan H począwszy od 30 sekundy w minucie, aż do jej końca. Mniejsza o sens biznesowy – to przecież tylko demo, zamiast wzmiankowanego pazia, a ważne jest to, że będziemy monitorować, co aplikacja myślała że wystawiła na wyjścia portu oraz to co ona rzeczywiście wystawiła, co jest fizycznie dostępne na zewnątrz urządzenia.

I tu dygresja, ale z życia – podczas tej dłubaniny przytrafiła mi się pomyłka i linie monitorujące PA.0/1 zamieniłam z PA.2/3 w CA80, jak się zerknie na płytkę MIK89 to widać, że nietrudno się merdnąć i mi się właśnie zdarzyło. Efekt prosty – program pisze że wystawił dane, a próbnik - pokazuje że nie. I co wtenczas?

Kod dla CA80 z grubsza taki:

app1.c pisze:

Kod: Zaznacz cały

#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "../inc/ca80sys.h"
#include "../inc/sio.h"

#define DEBUG   1
//#undef DEBUG   

//---------------------------------------

void getSystemTime(struct tm *pT) {
    pT->tm_sec = *((unsigned char*)0xFFED);
    pT->tm_min = *((unsigned char*)0xFFEE);
    pT->tm_hour = *((unsigned char*)0xFFEF);       
    pT->tm_mday = *((unsigned char*)0xFFF1);
    pT->tm_mon = *((unsigned char*)0xFFF2);
    pT->tm_year = *((unsigned char*)0xFFF3);       
    pT->tm_hundredth = *((unsigned char*)0xFFEC);       
}

//---------------------------------------

struct tm currentTime;
unsigned char lastSec = 0;   

#ifdef DEBUG
char s[20];
#endif

void Every10Seconds( void ){   
   #ifdef DEBUG
      putStr( 1, "10 sec expired\n" );   
   #endif
   USER8255_PA ^= 1 << 0;   // toggle
   #ifdef DEBUG
   putStr( 1, "PA.0 state: " ); _uitoa( USER8255_PA, s, 16 ); strcat(s, "\n"); putStr( 1, s );   
   #endif
}


void main( void ) {


    sioInit( 1, B9600 );
       
    sioIrqEnable( 0, IRQ_NONE );   
    sioIrqEnable( 1, IRQ_NONE );           
               
   USER8255_CTRL = 0x80;
   USER8255_PA = 0x00;

    while( 1 ) {   
      sysCZAS();
      getSystemTime ( &currentTime );
      if ( lastSec != currentTime.tm_sec ) {
         lastSec = currentTime.tm_sec;
         #ifdef DEBUG
         // lokalny czas CA-80
         putStr( 1, "rtc: " );
         _uitoa( currentTime.tm_hour, s, 16 ); putStr( 1, s ); putStr( 1, ":" );
         _uitoa( currentTime.tm_min, s, 16 ); putStr( 1, s ); putStr( 1, ":" );
         _uitoa( currentTime.tm_sec, s, 16 ); putStr( 1, s ); putStr( 1, "\n" );      
         #endif
         if ( ( currentTime.tm_sec & 0x0F ) == 0 ) {         
            Every10Seconds();
         }
         if ( currentTime.tm_sec > 0x30 ) {
            #ifdef DEBUG
               putStr( 1, "PA.1 ON\n" );   
            #endif
              USER8255_PA |= 1 << 1;
         }
         else {
            #ifdef DEBUG
               putStr( 1, "PA.1 OFF\n" );   
            #endif
            USER8255_PA &= ~(1 << 1);
         }
      }
    }   
}
// fin


Zauważmy, że diagnostyka mimo iż objęta kompilacją warunkową jest dość inwazyjna – alokuje okruszek pamięci na tymczasowy tekst i korzysta z bibliotecznej funkcji itoa(), co oznacza dolinkowanie stosownej porcji kodu maszynowego. Sama transmisja jest na 9600, więc odmeldowanie linijki logu do PI odrobinę czasu kosztuje, niewiele ale należy o tym narzucie pamiętać. Oczywiście zablokowany mamy drugi kanał SIO... Do PI wrzucam to, co mi się akurat wydaje w danym momencie przydatne czy ciekawe i pozwoli ocenić co tak naprawdę wyprawia w środku program z CA80.

teraz Malinka

Programik do akwizycji logów nabazgrałam w Python, ot trening taki. Obsługa portu szeregowego jest tam banalna, podobnie jak wygodny dostęp do GPIO Maliny. Oba bajery uzyskujemy doinstalowując paczki systemowe odpowiednio: python3-rpi.gpio oraz pyserial. Świeżo spreparowany na karcie SD Debian ma tę przypadłość, że wystawia komunikaty systemowe właśnie na złącze szeregowe, stąd musimy tę przeszkadzajkę wyłączyć, aby port był dostępny dla użytkownika. Uruchamiamy raspi-config i dalej zgodnie z rysunkami:

30_pi_serial_enable.png


Programik w Python 3 poniżej, kolejny potworek ale chyba jest dobrym punktem startowym dla własnych zabaw. Tam jedna sztuczka jest, o której dwa słowa komentarza. A mianowicie – wiadomo że obsługa portu szeregowego musi uwzględniać timeout czyli aplikacja powinna jakoś poradzić sobie z nagłym zanikiem strumienia znaczków. Metoda readline() w przypadku wystąpienia długotrwałej (tu: 100 ms) ciszy zwraca pusty bufor, ale nic to – wtedy sprawdzam czas, kiedy ostatnio miała miejsce transmisja. I jeżeli minęło od tego momentu więcej niż przyjmijmy że np. sekunda – melduje na wyjście interesujące mnie stany logiczne linii GPIO. Oczywiście sprawdzam je także przy każdej nadesłanej linijce logu, ale zauważmy – CA80 może nic nie zalogować (bo tak!) a stan linii się zmieni i chcemy o tym wiedzieć. Tak wykorzystany mechanizm timeout daje nam gwarancje ciągłej obserwacji rzeczywistości.

logger.py pisze:

Kod: Zaznacz cały

import serial
import io
import datetime
import RPi.GPIO as GPIO
import time

def putTimestamp():
    print ( "{0:%Y-%m-%d %H:%M:%S.%f}".format( datetime.datetime.now())[:-3]+"|", end = "" )
   

def putIo( aState, bState ):
    print ( "{}|".format( "CHA" if aState == GPIO.HIGH else "---" ) , end = "" )
    print ( "{}|".format( "CHB" if bState == GPIO.HIGH else "---" ) , end = "" )   
   
def getTimeMilis():
    return int( round( time.time() * 1000) )

GPIO.setmode( GPIO.BCM )
GPIO.setup( 20, GPIO.IN )
GPIO.setup( 21, GPIO.IN )

serialPort = serial.Serial( "/dev/ttyS0", baudrate = 9600, timeout = 0.1 )
serialIO = io.TextIOWrapper( io.BufferedRWPair( serialPort, serialPort ) )

lastMs = getTimeMilis()

while True:
    rxBuffer = serialIO.readline()   
    chA=GPIO.input(20)
    chB=GPIO.input(21)
    if len( rxBuffer ) == 0:
        currMs = getTimeMilis()
        if currMs - lastMs > 1000:
            lastMs = currMs
            putTimestamp()
            print ( "A|", end = "" )
            putIo ( chA, chB )
            print ( " auto", flush=True )           
        continue
    putTimestamp()
    print ( "L|", end = "" )
    putIo ( chA, chB )
    print ( "", rxBuffer, end = "", flush = True  )   


Sam format logu produkowanego na konsolę (stdout) jest sprawą wtórną, dajemy to co potrzebujemy/lubimy i uważamy za wartościowe. U mnie jest znacznik czasu, stan obserwowanych linii I/O, wyróżnik czy linia jest logiem (L) czy dodana automatycznie (A), oczywiście przepisane na wprost to, co nadesłało CA80. Podział sekcji słomkami zapewnia łatwą segmentację danych np. logstashowym Grokiem czy inną dowolną analizę. Programik nie tworzy dedykowanego pliku, zawsze można przekierować standardowe wyjście do zbioru tekstowego, a owy obserwować z boku choćby poleceniem tail z opcją --follow.

Przykład logu dla aplikacji na CA80 mamy poniżej, w załączniku większa próbka obserwacji długodystansowej, no powiedzmy że kilkugodzinnej.

Kod: Zaznacz cały

2019-12-18 19:48:48.818|L|---|CHB| PA.1 ON
2019-12-18 19:48:49.628|A|---|CHB| auto
2019-12-18 19:48:49.731|L|---|CHB| rtc: 20:47:58
2019-12-18 19:48:49.834|L|---|CHB| PA.1 ON
2019-12-18 19:48:50.645|A|---|CHB| auto
2019-12-18 19:48:50.752|L|---|CHB| rtc: 20:47:59
2019-12-18 19:48:50.754|L|---|CHB| PA.1 ON
2019-12-18 19:48:51.666|A|---|CHB| auto
2019-12-18 19:48:51.768|L|CHA|---| rtc: 20:48:0
2019-12-18 19:48:51.770|L|CHA|---| 10 sec expired
2019-12-18 19:48:51.772|L|CHA|---| PA.0 state: 3
2019-12-18 19:48:51.774|L|CHA|---| PA.1 OFF
2019-12-18 19:48:52.685|A|CHA|---| auto
2019-12-18 19:48:52.788|L|CHA|---| rtc: 20:48:1
2019-12-18 19:48:52.790|L|CHA|---| PA.1 OFF
2019-12-18 19:48:53.701|A|CHA|---| auto
2019-12-18 19:48:53.804|L|CHA|---| rtc: 20:48:2


Na zakończenie oczywiście garstka spostrzeżeń, po pierwsze - widzimy, że zupełnie niewielkim kosztem (w sumie kilkadziesiąt złotych) możemy zbudować sobie całkiem sprawne (i rozwojowe) narzędzie diagnostyczne dla własnych projektów. Od strony programowej też nie jest to skomplikowane, biblioteki do GPIO i RS-a dla Pythona są, a i sam język do takich pisanek na kolanie jest wręcz idealny, dłubie się szybko i efektywnie i co najważniejsze - bezpośrednio na Raspberry.
Wieczorne uruchomienie całości (a działało do rana, jakieś 10 godzin) skutkowało ~5MB plikiem tekstowym logu. Drugi wniosek zatem taki, że możemy spokojnie zwiększyć gadatliwość obserwowanego systemu, dodać do logu więcej informacji czy kontrolnych wpisów. Zagadnienie wysycenia się systemu plików Maliny jest przy zachowaniu zdrowego rozsądku raczej odległe.

Tradycyjny filmik z desktopu na żywo:

https://youtu.be/sBYTxb4ZyjQ

oraz pracująca wieczorową porą pobłyskująca instalacja:

https://youtu.be/I5rZFd80hoE

#slowanawiatr
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
______________________________________________ ____ ___ __ _ _ _ _
Kończysz tworzyć dopiero, gdy umierasz. (Marina Abramović)

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [CA80][Raspberry] Log z Malin, monitorowanie CA80 przez Raspberry PI Zero

Postautor: gaweł » czwartek 19 gru 2019, 18:20

tasza pisze:a mianowicie (i nadzieje mam, że tajemnic państwowych tu nie ujawniam): pewien programik dedykowany na sprzęt miał za zadanie wykonywać operacje na urządzeniu zgodnie z zaimplementowanym harmonogramem.

Ja nie mam żadnych tajemnic.
Cóż rzec... proste i skuteczne. Czasami na proste rozwiązania jest najtrudniej wpaść. Prawdę mówiąc mi po głowie chodziły jakieś pomysły tej klasy, ale, teraz z perspektywy czasu, to były jedynie komplikacje w myśleniu i rozumowaniu. Żeby nie było, że zmyślam, to do podobnych celów kiedyś wymyśliłem "pintool", ale ta realizacje jest jeszcze nie dokończona i będzie wymagała trochę zachodu.
Niemniej dziękuję za poświęcony czas, a owe know-how będzie komuś przydatne, nawet może "życie uratuje", bo stojąc przed ścianą i nie bardzo wiedząc co dalej zrobić, może z logów nasunie się jakiś właściwy wniosek, który zaowocuje krokiem w dobrą stronę.
A sterownik, o którym wspomniała tasza, poza jednym "wybrykiem" wrócił na właściwe tory i robi co do niego należy.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse


Wróć do „Retro”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 9 gości