Koncept state machine

programski jeziki in programiranje

Moderator: tilz0R

Koncept state machine

OdgovorNapisal/-a s54mtb » 27 Jan 2019, 23:53

Sem softverski samouk, zato delam po lastnem občutku. Bi me pa zanimalo, ali je način, ki sem ga izbral OK, ali bi se dalo tudi bolje.

Imam senzorski vmesnik, na katerenga imam priključenih SEN_NUM senzorjev. Vsak je lahko priključen, lahko tudi ne. To se lahko spreminja tudi sproti (recimo, da senzor odpove ali pa ga priklopim/odklopim kadar hočem). Prav tako lahko za nek senzor vnaprej določim, da ga sploh ne bom uporabljal.

Vsak senzor se lahko (ali pa ne) vklaplja in izklaplja.
Nekateri senzorji imajo lahko warmup cikel, ko je treba po vklopu počakati pred pričetkom odčitavanja.

Odčitki vsakega senzorja se odčitavajo v vnaprej definirano dolg buffer, po končanem odčitavanju se naredi neka matematika (odstrani outlayerje, naredi povprečje in še kaj) in izračuna končni rezultat. Ta se pošlje v LoRa omrežje, potem lahko (ni pa nujno) sledi še odmor do naslednjega cikla za ta senzor.

Vse skupaj sem si zamislil kot state machine. Vse podatke sem dal v tabelo, v kateri so vsi potrebni podatki za izvajanje merilnih ciklov.

Najprej sem si zamislil strukturo, s katero vse to opišem:
Koda: Izberi vse
/** Sensor Scheduler structure for state machine etc... */
typedef struct
{
   uint16_t current_sec;          // current second within cycle
   uint16_t current_cycle;        // current measurement cycle
   uint16_t cycle_duration;       // duration of single cycle (seconds per cycle)
   uint16_t warm_cycles;          // number of warmup cycles
   uint16_t cycle_period;         // number of total measurement cycles
   SEN_status_t Current_State;    // Stae machine for sensor
   sensor_scheduler_callbacks_t callbacks; // sensor data operation callbacks
   void *readout;                 // pointer to last readout
   uint16_t readout_size;         // Size of last readout buffer
   uint8_t LoraWanPort;           // LoraWan data port
} sensor_scheduler_t;


Pri tem so še callback funkcije:

Koda: Izberi vse
/** Sensor scheduler callbacks */
typedef struct
{
/*!
 * @brief Power control of the Sensor
 * @param [IN] power: 0 = off, >0 = on
 */
    void ( *SensorPower )( uint8_t power );
/*!
 * \brief Measure and store readout to buffer
 * returns: free buffer (1 = one left, 0 = buffer full)
 */
  uint16_t ( *StoreReadout)( void );
   
/*!
 * \brief Calculate average of the buffer
 */   
   void ( *CalculateAverage ) (void *readout);

/*!
 * \brief Check if sensor is present
 * returns: 0 = not present, > 0 = sensor present
 */   
   uint8_t ( *SensorPresent ) (void);
   
/*!
 * \brief Send data via LoRa network
 */   
   void ( *SendData ) (uint8_t LoraWanPort, void *data, uint16_t readout_size);

/*!
 * \brief Sensor Init
 */   
   HAL_StatusTypeDef ( *SensorInit ) (void);
      
} sensor_scheduler_callbacks_t;





Array s podatki tako preprosto kar inicializiram (je tudi enostavno za kasnejše spreminjanje parametrov ali dodajanje novih senzorjev):
Koda: Izberi vse
/**
  Sensor scheduler structure for all sensors....
*/
sensor_scheduler_t  sensor_s[SEN_NUM] =
{
   /*-------  HPM Sensor -------------------------------- */
   { 0,0,      // current second, current cycle -- just init
     2,        // duration of single cycle (seconds per cycle)
     5,        // number of warmup cycles
     2*HPM_SENSOR_READOUTSNUM,      // number of total measurement cycles approx 2x buffer length
     SEN_init, // init state
     {
         &HPM_Power,
         &HPM_StoreReadout,
         &HPM_CalculateAverage,
         &HPM_Present,
         &SENSOR_SendData,
         &HPM_Init,
      },
      &HPM_LastAVGReadout,   // pointer to last readout
      sizeof(HPM_LastAVGReadout),  // size of readout
      0x21,         // LoraWan data port
   },
   
   /*-------    TMP75 Sensor -------------------------------- */
   { 0,0,      // current second, current cycle -- just init
     1,        // duration of single cycle (seconds per cycle)
     0,        // number of warmup cycles
     20,       // number of total measurement cycles
     SEN_init, // init state
     {         // Callbacks
         NULL,   // Power handling
         NULL,   // Store readout
         NULL,   // Calculate average
         NULL,   // Sensor present
         NULL,   // Send data
         NULL,   // Sensor init
      },
      NULL,     // pointer to last readout
      0,    // size of readout 
      0x22  // LoraWan data port
   },

   /*-------    SHT31 Sensor -------------------------------- */
   { 0,0,      // current second, current cycle -- just init
     1,        // duration of single cycle (seconds per cycle)
     2,        // number of warmup cycles
     SHT_SENSOR_READOUTSNUM+5,      // number of total measurement cycles
     SEN_init, // init state
     {         // Callbacks
         NULL,   // Power handling
         SHT_StoreReadout,   // Store readout
         SHT_CalculateAverage,   // Calculate average
         SHT_Present,   // Sensor present
         SENSOR_SendData,   // Send data
         SHT_Init,   // Sensor init
      },
      NULL,     // pointer to last readout
      0,    // size of readout 
      0x24  // LoraWan data port
   },


V gornjem primeru uporabljam 3 senzorje, od tega sta dva konfigurirana, enega (TMP75) pa ne mislim uporabljati.

State machine ima 5 stanj:
Koda: Izberi vse
/** Sensor scheduler state machine states */
typedef enum
{
   SEN_init,
   SEN_warmup,
   SEN_dataread,
   SEN_dataready,
   SEN_wait_to_end,
} SEN_status_t;


Sam state machine je potem narejen takole (upam, da je dovolj komentarjev):
Koda: Izberi vse
/** Sensors scheduler central State Machine
  * This function is called every second to update the timers and
  * read sensors when needed.
  */
void Sensors_Update(void)
{
   sen_types_t n;
   uint16_t current_sec;          // current second within cycle
   uint16_t current_cycle;        // current measurement cycle
   uint16_t cycle_duration;       // duration of single cycle (seconds per cycle)
   uint16_t warm_cycles;          // number of warmup cycles
   uint16_t cycle_period;         // number of total measurement cycles
   SEN_status_t Current_State;    // Stae machine for sensor
   for (n=SEN_HPM; n<SEN_NUM; n++)
   {
      /** Get parameters for current sensor **/
      current_sec     = sensor_s[n].current_sec;
      current_cycle   = sensor_s[n].current_cycle;
      cycle_duration  = sensor_s[n].cycle_duration;
      warm_cycles     = sensor_s[n].warm_cycles;
      cycle_period    = sensor_s[n].cycle_period;
      Current_State   = sensor_s[n].Current_State;
      
      /** Check if cycle period has passed */
      if ((current_sec < cycle_duration) & (cycle_duration > 0))
      {
         /** Increase current seconds */
         current_sec++;
      }
      else
      {
         /** New cycle, let's rock */
         current_sec = 0;
         current_cycle++;
               
         switch (Current_State)
         {
            case SEN_init: /** Init state */
              /** Init sensor */
              if (sensor_s[n].callbacks.SensorInit != NULL)
               {
                  sensor_s[n].callbacks.SensorInit();
               }
               /** Handle power switching (if available) */
               if (sensor_s[n].callbacks.SensorPower != NULL)
               {
                  sensor_s[n].callbacks.SensorPower(1); // turn on the sensor
               }

               /** Check presence */
               if (sensor_s[n].callbacks.SensorPresent == NULL)
               {
                  current_cycle = 0;
                  break;  // Sensor not configured
               }
                  
               /** Continue to warmup cycling if needed */
               if (warm_cycles > 0)
               {
                  Current_State = SEN_warmup;
               }
          else
          {
                  /** Or directly start readings */
            Current_State = SEN_dataread;
               }
            break;

            case SEN_warmup:
               if (sensor_s[n].callbacks.SensorPresent != NULL)
               {
                  if (sensor_s[n].callbacks.SensorPresent() == 0)
                  {
                     Current_State = SEN_init;  // restart
                     current_cycle = 0;
                     break;  // sensor unplugged or not present or error etc...
                  }
               }

              /** Wait for warmup period (if needed) */
               if (current_cycle >= warm_cycles)
               {
                  Current_State = SEN_dataread;
               }
            break;

            case SEN_dataread:
               /** Data read cycles, repeat until buffer full */
              if (sensor_s[n].callbacks.StoreReadout != NULL )
               {
                  if (sensor_s[n].callbacks.StoreReadout() == 0)
                  {
                     Current_State = SEN_dataready;
                  }
               }
            break;

            case SEN_dataready:
               /** Readout buffer ready, calculate averages */
               if (sensor_s[n].callbacks.CalculateAverage!=NULL)
               {
                  sensor_s[n].callbacks.CalculateAverage(sensor_s[n].readout);
               }
               /** power off (if available) */
              if (sensor_s[n].callbacks.SensorPower != NULL)
               {
                  sensor_s[n].callbacks.SensorPower(0);
               }
               /** Send out the calculated data */
               if (sensor_s[n].callbacks.SendData != NULL)
               {
                  sensor_s[n].callbacks.SendData(
                       sensor_s[n].LoraWanPort,
                 sensor_s[n].readout,
                 sensor_s[n].readout_size                  
                  );
               }
              Current_State = SEN_wait_to_end;
            break;

            case SEN_wait_to_end:
               /** sleep to the end of the readout period */
               if (current_cycle >= cycle_period)
               {
                  current_cycle = 0;
                  Current_State = SEN_init;
               }
            break;
         }
      }

      sensor_s[n].current_sec    = current_sec;
      sensor_s[n].current_cycle  = current_cycle;
      sensor_s[n].Current_State  = Current_State;
            

   }
   
   


Naj še povem, da se state machine kliče iz main-a, ko ISR za RTC nastavi signal, da je minila 1 sekunda. Vmes je v deep sleep. Majhna poraba je tudi eden od pomembnih ciljev.

Tiste callback funkcije (branje iz senzorja, init senzorja, računanje na koncu, ko je zajem končan itd...) pa imam implementirano ločeno in je seveda odvisno od posameznega senzorja. Zanima me, ali je tak koncept OK.

Kakršenkoli komentar je dobrodošel.

Zadeva sicer lepo dela :) O zanesljivosti pa ne vem nič, ker še ni dovolj dolgo vklopljeno.
loradata.JPG
Namesto "Zahvali se" sprejemam tudi šalco kofeta: https://www.buymeacoffee.com/s54mtb
Uporabniški avatar
s54mtb
 
Prispevkov: 9879
Pridružen: 15 Jan 2015, 01:10
Zahvalil se je: 1349 krat
Prejel zahvalo: 3330 krat
Uporabnika povabil: Vrtni palček
Število neizkoriščenih povabil: 203

Re: Koncept state machine

OdgovorNapisal/-a VolkD » 28 Jan 2019, 00:10

s54mtb je napisal/-a:Sem softverski samouk, zato delam po lastnem občutku. Bi me pa zanimalo, ali je način, ki sem ga izbral OK, ali bi se dalo tudi bolje.
Ne razmišljaj preveč o tem ali je pravo verno ali ne. Če deluje po pričakovanjih ali celo bolje je gotovo prav.
Če narediš nekaj izven znanega kalupa imaš možnost nekaj izboljšati. Če se držiš ustaljenega kalupa pač ne. Malo po svoje narejene stvari vodijo do novih spoznanj.
Dokler bodo ljudje mislili, da živali ne čutijo bolečine, bodo živali čutile, da ljudje ne mislijowww.S5tech.net
Uporabniški avatar
VolkD
Administratorji strani
 
Prispevkov: 34838
Pridružen: 29 Dec 2014, 20:49
Kraj: Kačiče (Divača)
Zahvalil se je: 6945 krat
Prejel zahvalo: 4296 krat
Uporabnika povabil: Vrtni palček
Število neizkoriščenih povabil: 255

Re: Koncept state machine

OdgovorNapisal/-a s54mtb » 28 Jan 2019, 00:12

Nima to veze z vero :)

Sicer pa kaj sem sploh pričakoval. Spet bo očitno ena vojna za-proti, ki nima blage veze z vsebino :mrgreen: :_banghead
Namesto "Zahvali se" sprejemam tudi šalco kofeta: https://www.buymeacoffee.com/s54mtb
Uporabniški avatar
s54mtb
 
Prispevkov: 9879
Pridružen: 15 Jan 2015, 01:10
Zahvalil se je: 1349 krat
Prejel zahvalo: 3330 krat
Uporabnika povabil: Vrtni palček
Število neizkoriščenih povabil: 203

Re: Koncept state machine

OdgovorNapisal/-a s54mtb » 28 Jan 2019, 00:16

Še podvprašanje...

ali tole pije vodo: če vzamem za priodo merilnih ciklov praštevilo trajanja v sekundah (za vsak cikel drugo), bo časovna razporeditev pričetkov (ali koncev) ciklov v zelo dolgem časovnem obdobju relativno enakomerna.
Namesto "Zahvali se" sprejemam tudi šalco kofeta: https://www.buymeacoffee.com/s54mtb
Uporabniški avatar
s54mtb
 
Prispevkov: 9879
Pridružen: 15 Jan 2015, 01:10
Zahvalil se je: 1349 krat
Prejel zahvalo: 3330 krat
Uporabnika povabil: Vrtni palček
Število neizkoriščenih povabil: 203

Re: Koncept state machine

OdgovorNapisal/-a jmivsek » 28 Jan 2019, 00:52

Končni avtomati (Finite State Machines - FSM) so moja 'omiljena' tema in jih uporabljam vsakič, kadar problem postane prekompleksen za običajen program. Kar opaziš po preveč in pregloboko gnezdenih IF stavkih.

Končni avtomat sestavljajo:
    1. dogodki,
    2. akcije,
    3. stanja.
Ko se nek dogodek pripeti, se lahko izvede ena ali več akcij in avtomat lahko preide v novo stanje. Ali pa tudi ne. Nujno je najprej narisati diagram prehajanja stanj (State Transition Diagram - STD), ker sicer se boš hitro izgubil, predvsem pa zgrešil kako od kombinacij dogodek/stanje. Primer STD :

STD_HotelReservation.jpg


Da ne zgrešiš kake kombinacije, je koristno narediti tabelo z dogodki v stolpcih in stanjih v vrsticah. Nato preveriš vsako kombinacijo in v polje vpišeš akcije ter pod črtkano črto novo stanje. Primer tabele, sicer brez akcij, samo z novimi stanji:

STtable.gif
Vem, da nič ne vem (Sokrat)
Uporabniški avatar
jmivsek
 
Prispevkov: 956
Pridružen: 18 Jan 2015, 01:20
Kraj: Ajdovščina
Zahvalil se je: 1369 krat
Prejel zahvalo: 311 krat
Uporabnika povabil: S52O
Število neizkoriščenih povabil: 54

Re: Koncept state machine

OdgovorNapisal/-a s54mtb » 28 Jan 2019, 01:17

Hvala za koristni primer iz prakse.

To, kar sem zgoraj opisal je v bistvu "N" relativno preprostih FSM. STD je bolj sekvenčni diagram, kjer je prehajanje pogojeno izključno s časom, dogodkov pa razen 1 signal na sekundo ni.

Je za tak primer morda boljši kakšen drug prostop kot FSM ?
Namesto "Zahvali se" sprejemam tudi šalco kofeta: https://www.buymeacoffee.com/s54mtb
Uporabniški avatar
s54mtb
 
Prispevkov: 9879
Pridružen: 15 Jan 2015, 01:10
Zahvalil se je: 1349 krat
Prejel zahvalo: 3330 krat
Uporabnika povabil: Vrtni palček
Število neizkoriščenih povabil: 203

Re: Koncept state machine

OdgovorNapisal/-a tilz0R » 28 Jan 2019, 09:51

Tukaj bi dopolnil jmivsek, da je najbolj elegantno narediti tabelo pointerjev na funkcije, ki zgleda točno tako, kot je na sliki, le da so za tekst, kaj narediti, pointerji na funkcije za naslednjo akcijo. Tako rešiš problem ogromno kode za vsak case in vsak action v njem.

Imaš le nekaj v smislu: next_action = action_table[current_action][current_event].
Primer: Če je trenutno v cooking načinu in se zgodi doorOpened, je naslednja funkcija "Cooking interrupted". Le-ta izklopi mikrovalovko (oz. karkoli) in postavi status v naslednje stanje.
Knowledge sharing is people' caring., T. MAJERLE
Uporabniški avatar
tilz0R
 
Prispevkov: 1692
Pridružen: 18 Jan 2015, 00:12
Kraj: Črnomelj
Zahvalil se je: 221 krat
Prejel zahvalo: 442 krat
Uporabnika povabil: s56rga
Število neizkoriščenih povabil: 255

Re: Koncept state machine

OdgovorNapisal/-a Kroko » 28 Jan 2019, 10:59

V C++, kjer se pointerjev poskušamo izogibati, narediš to precej elegantno z razredi.

V C++20 bo new deprechiated, v C++23 pa bo ukinjen :-)
http://www.modernescpp.com/index.php/no-new-new
(več pove tudi prvi komentar pod zgornjim linkom)
http://www.planet-cnc.com poskakuješ na eni nogi in žvižgaš alpske podoknice Kroko was here!
Uporabniški avatar
Kroko
 
Prispevkov: 4375
Pridružen: 14 Jan 2015, 12:12
Kraj: Ljubljana
Zahvalil se je: 635 krat
Prejel zahvalo: 1424 krat
Uporabnika povabil: Vrtni palček
Število neizkoriščenih povabil: 255

Re: Koncept state machine

OdgovorNapisal/-a s54mtb » 29 Jan 2019, 19:21

Sem poiskal knjigo, ki me je prepričala, da se splača poglobiti v c++

https://www.springer.com/la/book/9783662567173
Namesto "Zahvali se" sprejemam tudi šalco kofeta: https://www.buymeacoffee.com/s54mtb
Uporabniški avatar
s54mtb
 
Prispevkov: 9879
Pridružen: 15 Jan 2015, 01:10
Zahvalil se je: 1349 krat
Prejel zahvalo: 3330 krat
Uporabnika povabil: Vrtni palček
Število neizkoriščenih povabil: 203


Vrni se na Software

Kdo je na strani

Po forumu brska: 0 registriranih uporabnikov in 1 gost