Arduino: Klikimouse3

logo Klikimouse3 aneb klikáme se stavovým automatem objektově

protože tu už dlouho nebyl žádný Klikimouse ...

th_klikimouse

klikimouse

... tak tu máme pokračování Klikimouse2 v pozměněném hávu.

Stejný HW (tedy USB arduino - micro), stejné zapojení (3 tlačítka proti zemi (A,B, a C) a jedna LED s odporem pro indikaci), liší se jen funkce a přístup.

Máme 3 režimy:

  • Základní učení
  • Pokročilé učení
  • Boj

Tlačítko A spouští hlavní akci, tlačítko B spouští vedlejší akci, tlačítko C ruší probíhající akci (přičemž ji nechá doběhnout do rozumného stavu), nebo, pokud nic neprobíhá, tak přepíná režim.

Protože se spousta věcí opakuje, tak jsem to zabalil do tříd.

Ta nejzákladnější se nápaditě jmenuje Base, má dvě hlavní metody start() a stop(), které jí říkají, kdy má začít a kdy to nějak v poklidu ukončit a dvě funkce pause(x), která vrací jak dlouho se ve stavu x čeká, než se přejde dál a next(x), která říká, kam se má přejít.

Taky má jednu vnitřní proměnnou stop_me se kterou se moc nechlubí, ale v ní si uchovává, jestli už byla požádána o ukončení, nebo ještě ne.

Její implementace je jednoduchá, na vnější popud se rozběhne či zastaví, nedělá nic, takže vnitřní stavy moc nemá a zastaví se i sama, tak rychle, jak to jen jde. Macro UNUSED zajistí, že nepoužitá hodnota stavu se ve funkci "použije" a tak překladač nemá nevhodné poznámky (a my víme, že to autor takto zamýšlel).

  • Základní učení je stejné jako minule - A sesílá opakovaně kouzla, B kliká a mačká Y

To be continued ...


Jak jsem napsal již dříve, v Drobničky 006 - Klikimouse aneb volím si tebe, Klikaču!, více myší se chová jako myš jedna a toho se dá využít v různých hrách (i jinde).

Tentokrát ovšem hraju DAGGERFALL (prastarou DOSovku) a pro procvičování kouzel (a tedy zlepšování skilu) je šikovné mít kouzlo s 'distant range' efektem a schopnost absorbovat magii. Pak už si stačí stoupnout nosem ke zdi, seslat kouzlo a absorbovat magii, co mě zasáhne, takže co jsem vydal se mi zase vrátí. Tedy stisknout (a pustit) klávesu C a pak kliknout myší přímo před sebe.

Problém je, že pro získání dalšího stupně skilu je potřeba seslat to kouzlou hodněkrát (na nižších úrovních tak 20x, na vyšších ani 40x nestačí) a oblastí magie je 6 a potřebuju skil zvednout rekněmě tak z 5 na 100 - počet potřebných kliknutí si spočtěte sami (je to dost a pro získání každé úrovně je potřeba se vyspat a pak se počítá znovu od nuly, nadměrná seslání prostě propadají). Navíc seslání nějakou chvíli trvá, pokud klikáme moc rychle, tak se kliknutí/tlačítka ignorují, protože grafika si hraje s vykreslováním efektů a tak rychle se zase kouzlit nedá.

Pak je tady ještě další trik, kde naopak potřebuju kliknout myší na tlačítko a odsouhlasit dialog, ale to jde naštěstí dělat mnohem rychleji.

No a protože monstra mají občas blbé nápady se znenadání zjevit a zaútočit, tak je to potřeba mít možnost taky přerušit ve správnou chvíli (tedy po odsouhlasení dialogu, nebo po seslání kouzla, ale ne někde uprostřed sekvence). Navíc je jasné, že oba triky nelze používat paralelně, vždy jeden, nebo druhý, jinak by to nefungovalo.

Takže jako vždycky - zadání je jasné, nechme dřinu strojům.

Arduino je potřeba nějaké s USB pinama, tedy micro, Leonardo a tak, nikoli UNO a jemu podobná, která USB používají přez převodník.

Zapojení stejné jako u předchozího modelu - 3 tlačítka na INPUT_PULLUP pinech a nějaká LEDka na OUTPUT pinu (micro vestavěnou nemá) přez odpor na zem, ať taky vidíme, že to něco dělá :)

Stejně jako u předchozího modelu potřebujeme číst tlačítka i ve chvíli, kdy něco děláme, potřebujeme mít ošetřené zákmity (uznávám, že kdybych míst nejlacinějšího fejkového breadboardu z číny použil poctivý PCB a všechno připájel, tak by se to tolik nevaklalo a zákmity by byly menší, ale dokud je nejpomalejším článkem člověk, netřeba zrychlovat zbytek) a potřebujeme zároveň čekat mezi jednotlivými simulovanými kliknutími.

Tentokrát už tu jsou složitější sekvence, tak použijeme pro ovládání výstupu jednoduchý stavový automat. (To je vlastně ta část ve switch.)

if (state){
      switch (state) {  //  state automat
              case 0: break; // trochu nadbytečné

              case START_A +0: ++state;Keyboard.press('c');break;
              case START_A +1: ++state;Keyboard.release('c');break;

Mám proměnnou state která obsahuje číslo stavu, který má automat provést. Podle její hodnoty automat udělá nějakou akci (třeba stiskne či pustí myš/klávesu) a rozhodne o dalším stavu (tady je rozhodnutí jednoduché - přejdeme na další krok v sekvenci, po posledním kroku nastavíme hodnotu na 0 (jakože ze nemá dělat nic). Tímto způsobem může automat po jednotlivých krocích dělat vše co je potřeba a čekáním se nezabývat. Čekání obstarává ta podmínka okolo něj, která používá známý trik s currentMillis, aby se vyhla blokujícímu delay().

if ((currentMillis-milsState) >= responseDelay){
       milsState = currentMillis;  // next step

Stejný trik použijeme po stisku/puštění tlačítka, kdy událost zpracujeme hned, ale s pomocí currentMillis pak chvilku ignorujeme zákmity.

Takže loop() obsahuje dvě části - vstupní a výstupní, ale většinu času se jen tak točí dokola a kouká, zda nebylo něco stisknuto, nebo zda nedoběhlo nějaké čekání, ale klidně by mohla dělat ještě spoustu další užitečné činnosti, kdyby tu nějaká byla. Třeba mít i víc nezávislých stavových automatů a ošetřovat je všechny, nebo ošetřovat zákmity pro každé tlačítko zvlášť, nezávisle blikat více ledkama a tak podobně.

Pokud automat doběhne do konce (status == 0), tak se podívám, jestli chci dělat ještě něco ( pokud mám cycles_left nulové, tak jsem skončil, jinak ho snížím o jedna a pokračuju dalším stavem uloženým v restart_state s délkou pauzy uloženou v next_responseDelay )

if (cycles_left == 0) {
  restart_state = 0;  // stop it
  responseDelay = DEFAULT_DELAY;
} else {  // {{{ new cycle
  state = restart_state;
  responseDelay = next_responseDelay;
  --cycles_left;
};  // }}}

No a to, čím se má pokračovat nastavuju při stisknutí tlačítka, přičemž A má největší priritu (pokud jich stisknu víc naráz), proto je jako poslední a přepíše případné předchozí hodnoty. Také pokud běží nějaký cyklus, tak se tím nastaví, co se má dělat, až doběhne, ale samotný cyklus se tím neruší.

if (act_A) {  // act_A high priority
  restart_state = START_A;
  cycles_left = CYCLES_A;
  next_responseDelay = DELAY_A;
};

Ještě bych upozornil na to, že jsem pojmenoval jen počáteční stavy ( START_A a spol. ) a další beru jako čísla o 1,2,3... větší ( START_A +0, START_A +1 ...), aby se mi to snadno počítalo ( ++status ), ale mohl bych si je pojmenovat taky a volit i složitější cesty.

A pojmenování START_A +0 a ++status na začátku je taky jen aby to vypadalo úhledněji.

A ++status; jako samostatný příkaz znamená zvětši status o jedna, pak tu vem tu hodnotu a tu pak zapomeň, což mi přijde jako přiměřenější mému záměru, než status++;, které znamená zapamatuj si hodnotu statusu, pak ho zvětši o jedna, pak vezmi jeho původní (nezvětšenou) hodnotu a tu pak zapomeň.

A komentáře Cčko ignoruje, tak si tam můžu psát co chci, takže když tam dám {{{ a }}} tak se nic zajímavého s překladem nestane, ale ve vimu (což je můj oblíbený editor) se blok mezi těmito značkami může stáhnout na jednu řádku a zase roztáhnout na původní tver dle potřeby (foldování) - čímž se mi na obrazovku přehledně zobrazí i velmi dlouhý program a můžu si ho po částech robalovat a sbalovat tak, abych vždy viděl to, co potřebuju, a přitom většina zbytku byla zabalená - takže se laskavý čtenář smíří s tím, že tyto značky v komentářích mám a ostatní čtenáři mají prostě smůlu :)

Stejně tak používám dle libosti if (status) , if (status != 0) nebo if (status == true ) neb je to jedno a to samé.


Licence

Ukázkový program je pod GPL v2.0, takže není problém ho měnit a použít jako základ něčeho dalšího. (Ale vzhledem k tomu, jak je jednoduchý, není ani problém napsat zbrusu nový pod jinou licencí "na zelené louce").