Number of events over an interval of time Question

Just some logic that I'm working through in the below details on programming steps/thoughts:

The Setup Function would:

  1. Determine Random TotalTimeMin locked between 30 - 240 minutes
  2. Convert and Store TotalTimeMin in TotalTimeMs
  3. Subtract TotalTimeMin by 5 minutes to put in a delay (300,000ms)
  4. Take MOD((Total Time - 5) /2) to determine the total number of events
  5. Start event mills by finding StartMillis + 300,000 (5min) and store in EventMillis
  6. For the number of total events loop x and do the following

a. Store EventMillis in EventArray[x]
b. Random if event is active or not store in EventOnArray[x]
c. Random relay in event (20 sec-1m58 sec) in mills & store EventDurationArray[x]
d. Set EventMillis to EventMillis + 120,000 (2 min) for next event time

The LOOP Function would:

  1. Compare the first event from the array to see if it is => the stored value

a. If so, see if it is active from array, if not exit and wait for next event

I. Enable Relay until the millis of the duration is done and turn off

I haven't flown over your code, THX for posting it.

Your additional graph risks confusing things, as nice as it looks.

First, there is nothing complicated about cutting a large interval into smaller, identical length, intervals. Just forget about it...

All you have to do is put out the two minute events until you are at the point of having the next one run over your end limit, or what the hell, let one run over the end limit and then stop.

Later: I've missed my ride to the beach, she who must not be kept waiting would not. Wait for me to finish a little sketch. So now I get to ride my bicycle to the beach, and hope she will allow it to be tossed in the back of her vehicle for getting me home. (More up than down hill.)

I found an opening in your sketch posted in #19 where an independent event handler can be inserted.

Where you call display.display() is where I initiate or advance an event. I use a finite state machine to select timing and march out the event steps.

I could not build your sketch, so I stripped it of everything, including its pride, and ran it with the serial monitor.

All it is meant to do is show how an opportunity to advance a process can be inserted into the loop to more or less handle some other task. Watch it here, drag up the serial monitor window to see more lines of the output:


Wokwi_badge Event FSM Demo!


Because life is too short, I arranged for faster minutes and used shorter periods all over the place. Figure that out.

The only tricky trick is that the FSM is designed to be started externally, and it reports about whether it is finished or not:

  if (!eventHandler(0))  // servic kick some life into it.
    eventHandler(1);     // 

If calling the FSM for advancing the event progress says the machine is not doing an event just now, a second call immediately launches the next event.

The eventHandler() always takes the full event period. Sometimes it turns ON something for some part of that.

Run the sim and marvel just watch it sequencing. Then read the code. Then read the code. The event FSM is the last function. I larded it vigorously with print statements - I like to see why what is happening when.

// https://wokwi.com/projects/361178687373989889
// https://forum.arduino.cc/t/number-of-events-over-an-interval-of-time-question/1110643

const unsigned long ltsMinute = 30000;   // milliseconds per "life too short" minute

int LED2 = 6;
int SPEAKER = 7;


int PWMA = 2;
int PWMB = 3;

const int MaxRandomNunber = 120;  // minutes
const int MinRandomNumber = 30;   // minutes

const unsigned int shortLife = 4000;  // milliseconds
const unsigned int longLife = 7000;  // milliseconds

const unsigned long intervalPeriod = 10000;  // milliseconds (> short and long lifes)

unsigned long LockTimer;          // LockTime in Milliseconds
int MinuteTimer;                  // LockTime in Minutes

bool lockedQ = false;             // we locked? or not.

                                        // Relay2 Status Set to OFF
int MinutesRemaining = 0;

void setup() {                                                

  Serial.begin(115200);                                         // Enable 21st century serial logging

//  MinuteTimer = getRandom(MinRandomNumber, MaxRandomNunber);  // Generate Random Number based on MIN/MAX Assigned

  MinuteTimer = 8;                        // just testing ma'am.
  LockTimer = MinuteTimer * ltsMinute;                            // Set the duration from of lock from Minutes to Milliseconds

  Serial.print("Timer Amount:");
  Serial.println(MinuteTimer);
                              
  digitalWrite(LED2, HIGH);                                   // Set Locked LED Signal OFF

  lock();                                                     // Ater all settings, Start the Lock process
}

void loop() {

//  delay(222);  // just for now

  MinutesRemaining = ((LockTimer - millis())/ltsMinute+1);        // Calculate and store thre Minutes locked remaining

  if (lockedQ) {
    static int reported;

    if (MinutesRemaining != reported) {
      reported = MinutesRemaining;                                
      Serial.print("LOCKED, wait ");                                
      Serial.println(MinutesRemaining);
    }
  }


  if (!eventHandler(0))  // servic kick some life into it.
    eventHandler(1);     // 


  if ((MinutesRemaining == 1) && lockedQ)         
    FlashLED(300);
  
  if ((MinutesRemaining <= 5) && (MinutesRemaining > 1) && lockedQ)
    FlashLED(1000);
  
  if ((millis() >= LockTimer) && lockedQ) {
    unLock();
  }
}

void lock() {                                                  // FUNCTION to engage the lock(s)
  lockedQ = true;                                              // Set Locked Status to TRUE (1)
  Serial.println("LOCKED it ");
  return;
}

void unLock() {                                                // FUNCTION to unlock the lock(s)
  lockedQ = false;                                              // Set Locked Status to FALSE (0)

  Serial.println("UNLOCKED it");
  return;
}

void FlashLED(int x) {
  digitalWrite(LED2, (millis() / x ) % 2);

  if (MinutesRemaining <= 1) {
    if (((millis() / x ) % 2) == 0) {                     
      tone(SPEAKER, 349);
    } else noTone(SPEAKER);
  }

// flash reporting
  unsigned long now = millis();
  static unsigned long lastReport;
  if (now - lastReport > 1000) {
    lastReport = now;

    Serial.print("                yeah, LED is flashing "); 
    Serial.print(x);
    if (MinutesRemaining <= 1)
      Serial.print(" and BEEPING");

    Serial.println();
  }
}

enum {IDLE, StART, ACTIVE, PASSIVE, WAITING,};

// call with true to launch an event.
// call with false to move an event along its timeline
// returns true if the machine is busy with a previously launched event
unsigned char eventHandler(unsigned char command)
{
  static unsigned char state;
  static unsigned long eventStartTime;
  static unsigned int eventLivePeriod;

  unsigned long now = millis();

// newly event?
  if (command) {
    state = StART;
    return 1;   // we busy
  }

  switch (state) {
  case IDLE :
    break;

  case StART :
    eventStartTime = now;
    eventLivePeriod = random(shortLife, longLife);

    state = random(1000) > 500 ? ACTIVE : PASSIVE;

    Serial.print(millis());
    Serial.print(" launching a ");
    Serial.print(state == ACTIVE ? "ACTIVE" : "PASSIVE");
    Serial.print(" event ");

    if (state == ACTIVE) {
      Serial.print(eventLivePeriod / 1000);
      Serial.print(" seconds ON");
    }
    Serial.println();

    break;

  case ACTIVE :
    if (now - eventStartTime > eventLivePeriod) {
      Serial.print(millis());
      Serial.println(" done with active period of the active event.");
      state = WAITING;
    }
    break;

  case PASSIVE :
    if (now - eventStartTime > intervalPeriod) {
      Serial.print(millis());
      Serial.println(" done with passive event.");
      state = IDLE;
    }
    break;

  case WAITING :
    if (now - eventStartTime > intervalPeriod) {
      Serial.print(millis());
      Serial.println(" all done with active event.");
      state = IDLE;
    }
    break;

  }
  return state != IDLE;  // we busy?
}

HTH and L8R

a7

@alto777 My intention was never to have you require biking uphill or to upset the misses. Please ensure that family comes first over anything else especially coding. :slight_smile:

Let me have a look at the simulation and your code updates to see if they would fit within or a better way to skin a rattlesnake (people like cats, but not many like rattlesnakes). Sorry that the ansi art was not helpful. I thought it could tell the picture of what I was trying to accomplish over words; I guess not.

Thank you for your continued input and assistance. Not expecting you to code it, but as you have previously done, you have helped me to better understand how to break down the task into smaller chunks and perceive how to code such.

No worries. Even if you ran with my idea, there's still plenty to think about. And I assure you that three programmers would come up with three different solutions or approaches. I like to offer things at a level that seems appropriate. As far as I can see, only my use of switch/case might be a language feature you haven't come across or used. switch/case is not complicated and can be used here to express what otherwise might have been an unattractive chain of if/else if statments.

Your code is "friendly" in the sense that it does not block. So in theory at least you can add quite a bit of functionality before you run in to any real trouble. As long as no one blocks, either with delay() or with long loops that do not give back control to the loop() function.

After I stripped it down so I could run the logic, I just started hacking, as is my wont, to see if I could get some attention on a side task, to which side task I assigned the entire event mechanism.

You will or have seen that I added some printing here and there and used a few tricks to cut down on said printing.

There are some missing details - I did not code for the five minute startup no event period. I took no care at the end to stop events from happening.

There are no calls to delay(). I like to use just one call to millis(), at the top of the loop() function and use that everywhere, so everyone agrees on a standard time. Ppl will call this "currentMillis", I like the plainer now as a name for that variable.

I did not do that here either.

Just now I measured the loop() function and see it doing 20,000 loops per second.

Oh and don't worry about my family or my beach buddy. I know I don't. :wink:

a7

@alto777 Glad I did not ruin completely your time away from the computer. I'm familiar with the switch statement, but have not implemented it much in my previous code. Always a good reminder of the options and how to best use them for the need. Yes, I wanted to use all non-blocking events in the main loop along with millis so I can add other functions in the future. I love all the feedback. Question, is the simulation tool you used a free service or did you have to pay to use it? Seems like a great way to hack and test things out.

I did seem to make some progress based on our previous conversation and my initial thoughts with your input. I still like the idea of my wonderful ASCII art design to tell the story. :slight_smile: I spent some time yesterday working up code in the setup section that provides me with the expected arrays of data that I can poll throughout the main loop function and progress down the timeline. I will post that section below as well.

Below is some serial sample output from my testing to generate the correct data prior to getting into the main loop function. Timer Amount is the length the door will be locked. Noting I did reduce for testing the min/max door lock time. Number of Events is the total number of 2min events when you subtract out the 5 minute start delay. Start Time is the systems millis start variable. The following four pieces of row data consists of The Event Number : What millis the event is expected to start at : If the event is a true or false event (bool) : Last is the number of seconds (if the event is live) of the 2 minutes the light will go on.

So with the '0' element, the event starts at 300,077 millis which is StartTime + DelayTime (5 minutes). The event is false (nothing happens for the 2 minutes). However, the second element (1 of the array) starts at 420,077 millis, it a live event (True), so the light will go on for 22 consecutive second during the two minute event period. It was kind of fun as I also built in a Probability factor (variable) that allows one to modify the chance an event will be more or less frequent. That way the user has the ability to set to their preference up to 100% chance an event will be true to 1% it will. Ultimately, I would like to front end it with a web server (on the ESP8266) and allow input for the various items one can tweak via a page. I enjoy seeing things improving. Next will be some time to build the loop logic and track what event we are in and if we moved into a new event, etc. That will be a few functions I believe with the main loop calling to see where in the time line we are, update the event count, turn on/off the light, etc.

SERIAL OUTPUT:

Timer Amount:25
Number of Events: 10
Start Time: 77
0: 300077 : 0 : 0
1: 420077 : 1 : 22
2: 540077 : 0 : 0
3: 660077 : 1 : 36
4: 780077 : 1 : 56
5: 900077 : 0 : 0
6: 1020077 : 1 : 42
7: 1140077 : 0 : 0
8: 1260077 : 1 : 11
9: 1380077 : 0 : 0
LOCKED

Timer Amount:17
Number of Events: 6
Start Time: 76
0: 300076 : 0 : 0
1: 420076 : 0 : 0
2: 540076 : 1 : 21
3: 660076 : 0 : 0
4: 780076 : 1 : 22
5: 900076 : 1 : 8
LOCKED

Timer Amount:11
Number of Events: 3
Start Time: 75
0: 300075 : 1 : 17
1: 420075 : 0 : 0
2: 540075 : 0 : 0
LOCKED

Timer Amount:25
Number of Events: 10
Start Time: 75
0: 300075 : 0 : 0
1: 420075 : 0 : 0
2: 540075 : 0 : 0
3: 660075 : 0 : 0
4: 780075 : 1 : 55
5: 900075 : 1 : 17
6: 1020075 : 0 : 0
7: 1140075 : 1 : 9
8: 1260075 : 0 : 0
9: 1380075 : 1 : 46
LOCKED

alto777's code is completely non-blocking and as far as I understand it alto777's code does exactly what you want.

If you go on developing your own code do your potential helpers a favor and make the serial printing self-explaining ! REALLY self -explaining !
These ten minutes to add more serial printing is your thank-you for the help you receive.

best regards Stefan

1 Like

@StefanL38 never blocking! And yes, as best as I can tell, a certain high fraction of what the OP has in mind.

I've hacked it a little more to bring in the last details of what I think the specifications require, all trivial additions or changes/

@quellaman looks like "you got this". I have to wonder, however, why you need (or want) to calculate and store the entire run ahead of time. Perhaps it has something to do with your larger goals.

Speaking of which, if you could possibly separate the logic of the sketch from the low-level parts, it would make working with it in the wokwi simulator less hard. Easier.

The sketch I posted and linked is your logic stripped of everything we'd otherwise need to stand up beside the original in order to run it. So no LCD or SPI or whatever. I had to use a different Arduino board, which would still let me see the logic, with a few changes that might be obvious, like what's on which pins.

The wokwi is free. You can use it anonymously. If you register, it is still free, but you get some real benefits. They also have a club of paying subscribers. I am one but TBH I do not know what that gets me beyond what the free but registered ppl get. But it is something I can afford with discretionary funds, and something I have come to depend on. It is currently the best simulation envirnment for Arduino and, as you see, very handy for working with code and sharing such work.

If you use it to develop at least the logic of your further efforts, we can help better when things go wrong. On the other hand, if you grow this thing organically and it is always presented as something harder to actually run, you will reduce the attention it gets on these fora.

a7

1 Like

@StefanL38 Thank you for your comments and I will take your recommendation and advice going forward. I had not been outputting much to the Serial as the code was a bit simpler and was doing its job until I decided to expand on the capabilities. I am starting to do more logging for my debugging as the ESP8266 is not supported under the Arduino debugging. I often strip this out when the project is functional to save space if the code does not need it. In its full use, there will not be any serial reporting as it will be mounted in a case with the MCU and other modules.

I can say that the serial reporting that @alto777 has provided in his sample sketch was very helpful for me to review the program logic, etc. I'm still getting my head around the Interrupt aspects and have some more learning to do for that based on his sketch and my requirements.

Thank you again for your input.

@alto777 You are correct as I wanted to ensure there was no blocking if able to keep other potential features an option going forward. I wanted to have a few different type of events that could be occurring at the same time so tracking them using the millis function, etc was what others pointed me towards. The project started with the simple locking function and it was more a timer that did not rely on a simple delay as I wanted the flashing and updates to the display in the main loop. It would be simple to use blocking, but much less functional in the long run.

I took another look at the code in Wokwi and am starting to better understand the flow. However, I do have some reading and needed education on the interrupt section along with the case within the interrupt area. I really want to follow the flow to ensure that it could scale to the requirements of potentially adding in 2-4 total relays all triggering at different cycles within the process. It may be simply to scale the interrupt to manage more things, etc. That is what I really need to focus on and better understand. Again, the logging was good info and I liked the funny comments you added along the way. It is always nice to have a laugh while reading code. For me I may need to play with the time scale (which I can modify) to ensure that it is performing as expected. Not a big deal, but I did notice that the interrupt continues to fire even once the unlock happens, but I'm sure this is just some added logic to prevent that from happening.

With regard to the storage of the events, etc. This is how I see my ability to manage the events in a way that I can best do my comparison, etc. This could also expand based on the future needs. This is not throwing out the interrupt option, it is that I do not understand at this point how that fully works. I can better understand with my limited programing that storing such events and times in a table, I can easily apply multiple levels of logic to known objects. It is more concrete for me, and less ephemeral than not storing it. I guess often programming is using what you know and being safe?

Having the other objects and less serial output was due to the external display, and I can see with the simulation tool that not having these in (Rem out), it would make simulation testing easier. If I was starting from scratch and not from an existing project I would have gone this route.

Off to have another review of your interrupt code and try to wrap my brain around it on a Friday. Thank you again for all the input and ideas. I'm sure it is not easy to always take another's ideas and try to help solve things. I can say that the community has been good and the ideas have expanded my programming knowledge.

I am glad you are getting something out of the sketch I posted.

There is no interrupt section! interrupt has a special meaning in programming. You may want or need to know anything about them one day. You may never.

I rarely use them.

The serial printing during development can just be left there, at least on UNO and its ilk, if there is no one listening, it is perfectly fine to leave the calls in place. You do have to leave pins 0 and 1 free, howvere.

If you are at a point where that kind of informational chatter needs to be thrown out for space (or time), it may be a sign that a move to a more resourcelful board is in order, or that your code is def obtuse and needs to be put on a diet. So to speak.

The crudely drawn state diagram below shows how the FSM which is eventHandler() functions. It is called 1000s of times a second, most of those times will do nothing more than decide it isn;t time to do anything.

Calling it with1 (true) bumps it off the IDLE state.

StART selects at random between passive and active launch. Launching a passive event moves to PASSIVE. Launching an active event chooses the ON time and moves to ACTIVE.

PASSIVE just waits out the entire event. ACTIVE sees the active time pass and moves to WAIT, which there waits out the rest of the event slot. Both then transition to IDLE and the machine is, well, idle.

I have found that some timings will end you up with the beeper on and/or the LED on, so you may have to have a kill() function you can call to clean up things left dangling.

If you don't want events handled, just don't call it, and call it no more when the logic moves to unlock the locked thing.

This will def scale. If you find that you are scaling it by copy/paste/editing code that is nearly what you want, you may benefit from thinking about how array variables could be used so that one machine could handle as many event sequences (or whatever) as you want.

I am trying to see why this brilliant idea isn't doing what I want, yet:

// isolated millis so we can artifically speed the whole thing up
unsigned long ltsMillis()
{
  if (0) 
    return millis();
  else return micros() / 100;  // 10x speed
}

The idea is to replace all calls to millis() with a call to ltsMillis(), which makes the milliseconds go by way faster.

I do not enjoy the final final testing of things that play out over hours...

I'll post a correct version of that. Turning down the time constants all over works, but a global method would be better.

a7

Wow, I was not even aware of the Finite State Machine (FSM). I thought you were accomplishing this using some form of interrupt handler. My mistake. Starting to do some research as some places say that the ESP32 may support it but not the ESP8266. I have some reading/testing to do to ensure that FSM is supported on the platform I'm using. If I found that I was constrained, I would move up to the ESP32 (which appears to support it at first glance) and has more memory. I do not see processing being my constraint over memory and that is why I was looking to strip out all the unneeded Serial.print instructions from the code after tested the various functions. I'm going to have a look at any tutorial on FSM and get to know it better. Seems to be a really neat tool in the toolbox.

a finite state-machine or short state-machine is pure software and completely indepedant of hardware.

For any type of microcontroller that has more than 10 bytes of RAM a state-machine can be coded

one example how it works is here

@StefanL38 and @alto777 I have been doing my research along with the links provided on Finite State Machines (FSM) and how they work. It is quite an ingenuous was of doing non-blocking code and "multitasking" in near real-time (in comparison to human time. I can see now how breaking down the various states (idle, active event, relay during active event, etc.) as alto777 did in the diagram (Thank you as that helped my solidify things more) and then what events occur to move to the next state. Again, I like the non-blocking as I can then use the idle time to update the display with data about what is occurring. I have been trying to really think thought the various states and potential additional states that the project would benefit from. I had reviewed a few people that leverage the Traffic Light to show the different states it can be in and even better one person that used the example of calling an elevator via the press of a button.

With this new insight and learning, I will dive into the code example you produced alto777 with a much better understanding and idea of the applications flow. hank you both for continuing to take me under your wing and guide me not only though the process but take my project to the next level where the code will be more mature and malleable.

@quellaman again I am delighted to see your enthusiasm and progress.

I'm sure @StefanL38 will agree, there are few joys to compare with watching someone "get", however they do and at whatever pace, the ideas we both promote tirelessly.

Just in case it hadn't occurred to you, there is no reason to limit yourself to one FSM.

If none block, you can have as many as you need cranking away, subject of course to the limit of what can be computed in the time available.

And you may never need to, but an FSM could be subordinate to another - one FSM could start another, and poke it along all from one of its states.

As with all software, planning ahead and thinking about the states and how they relate to each other will make the coding go faster. I type kinda slow, but when I am typing code I am mostly on autopilot. I write word stuff the same way. By the time I am typing something, I've already thought it through pretty good.

Anyway, we here. Consulting many resources will show you the various ways ppl have chosen to implement FSMs. As I may have said here, I try to see just how deep the water is that I can toss a noob into… FSMs are more a concept to master, usually they can be done with very simple code. So maybe introduce the switch/case statement, but even that isn't needed - it just makes things look prettier, better here than if/else chains.

More sophisticated code makes it even slicker, but too often can be an overwhelming additional burden, and, TBH, sometimes def not clearer and more obvsly correct.

a7

2 Likes

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.