Finding a good search term for a lubrication schedule

The goal: A machine will provide one switch input to count operation cycles.  At different counts lubrication is required on certain points of the machine.  For example, pointA will be lubricated every 200 cycles while pointB will be lubricated every 550 cycles and so on.  One interval for all would be wasteful and even counterproductive.

Arduino will alarm and display on a 2x20 LCD which point's lube time has arrived. The point is lubed, alarm is reset and cycling continues.  I've searched for a 'scheduler' library many different ways but the search algorithm always focuses on scheduling in the context of timed events.  I've given up this wording.  I also tried the greenhouse control/plant watering and maintenance interval route with no joy.

I'd like to avoid reinventing the wheel and/or having to cannibalize a library to twist it into the shape of what I want.

So, does anyone know of a library that does such a thing or, maybe a better search term?

Thanks.

Post title edited 10/15/24

Do you really need a library to do that ?

Put the schedule and actions data in an array of structs and treat the whole thing as multiple BWoD timers

EDIT : no timers involved :grinning:

2 Likes

Here's a simulation of paul paulson's 4-leds 4-buttons array of structs to manage 4 interacting leds and buttons.

If you first count cycles with something like the Digital/StateChangeDetection built-in-example, you can feed the cycle count into this sort of system.

Instread of the millis() that this code is monitoring, think of using your cycle count as the trigger, your lubrication pointA as one of the LEDs, and the lubrication interval as the blink interval. You'd modify run() to initiate the lubrication and update the LCD, etc...

Your counter tracking is basic "state change detection" and your multiple lube points and associated intervals and counters are "array of struct".

…and store the counts in EEPROM in case of power failure.

I didn't think so but was having difficulties (the mental kind) setting up the struct and accessing an array of same.  I thought to save myself some work with a canned solution.

I will look into that. Thanks!

That's definitely a part of this.  Eventually the presets and some bit flags, too, will be runtime changeable and stored in EEPROM.

1 Like

I can share an old code I had written for something very similar

I just modified it a bit to suit your needs and published it on wokwi

You'll need two libraries for the lcd and button

Here is the simulation:

The LCD shows on the first line how many parts need maintenance / total number of parts and right adjusted the number of cycles
below that you have the list of parts needing maintenance

The red button increases the cycle count
the green button is to be pressed when you have done all the maintenance required.
The red led will blink if there is at least a part to maintain.

code is pretty straightforward, let me know if you have any question.
There is an empty function to save the state that I left empty. That's where you would want to archive stuff in EEPROM potentially. You would have also to modify the setup() to read the state from EEPROM after reboot.

click to see the code
/* ============================================
  code is placed under the MIT license
  Copyright (c) 2022 J-M-L
  For the Arduino Forum : https://forum.arduino.cc/u/j-m-l / https://forum.arduino.cc/t/finding-a-good-search-term/1311566?u=j-m-l

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  ===============================================
*/

#include <Wire.h>
#include <hd44780.h>                        // main hd44780 header
#include <hd44780ioClass/hd44780_I2Cexp.h>  // i2c expander i/o class header
#include <Toggle.h>

const uint8_t nbCols = 20;
const uint8_t nbRows = 4;
hd44780_I2Cexp lcd;

const byte cyclePin = 2;
const byte maintenanceDonePin = 3;
const byte alertPin = 4;

struct MaintenanceSchedule {
  const char * label;
  const uint16_t everyCycle;
  bool needsMaintenance;
};

MaintenanceSchedule parts[] = {
  {"Joint",  7, false},     // maintain every 7 cycles
  {"Gripper", 10, false},   // maintain every 10 cycles
  {"Vacuum",  5, false},    // maintain every 5 cycles
  {"Motor",  3, false},     // maintain every 3 cycles
};

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

const uint32_t partsCnt = sizeof parts / sizeof * parts;
uint32_t partsNeedingMaintenanceCnt = 0;

Toggle cycleButton;
Toggle maintenanceDoneButton;

uint32_t cycleCounter = 0;
char txtBuffer[3 * nbCols + 1];


void saveState() {

}

void blinkAlertIfNeeded() {
  static unsigned long lastBlink;
  if (partsNeedingMaintenanceCnt != 0) {
    if (millis() - lastBlink >= 200) {
      digitalWrite(alertPin, digitalRead(alertPin) == LOW ? HIGH : LOW);
      lastBlink = millis();
    }
  } else digitalWrite(alertPin, LOW);
}

void showCycles() {
  snprintf(txtBuffer, sizeof txtBuffer, "%08lu", cycleCounter);
  lcd.setCursor(nbCols - 8, 0);
  lcd.print(txtBuffer);
}

void showMaintenanceList() {
  partsNeedingMaintenanceCnt = 0;
  txtBuffer[0] = '\0';
  for (auto &p : parts ) {
    if (p.needsMaintenance) {
      partsNeedingMaintenanceCnt++;
      if (*txtBuffer != '\0') strlcat(txtBuffer, ", ", sizeof txtBuffer);
      strlcat(txtBuffer, p.label, sizeof txtBuffer);
    }
  }

  // erase
  for (byte r = 1; r < nbRows; r++) {
    lcd.setCursor(0, r);
    for (byte c = 0; c < nbCols; c++) lcd.write((' '));
  }

  // show the parts on the LCD. can span multiple lines. we have only nbRows-1 to play with
  int line = 1;
  int col = 0;

  lcd.setCursor(col, line);
  for (int i = 0; txtBuffer[i] != '\0'; ++i) {
    lcd.write(txtBuffer[i]);
    col++;

    // When reaching the maximum number of columns, move to the next line
    if (col >= nbCols) {
      col = 0;
      line++;
      if (line > nbRows - 1) {
        break;
      } else {
        lcd.setCursor(col, line);
      }
    }
  }


  snprintf(txtBuffer, sizeof txtBuffer, "%03lu/%lu", partsNeedingMaintenanceCnt, partsCnt);
  lcd.setCursor(0, 0);
  lcd.print(txtBuffer);
}

void setup() {
  pinMode(alertPin, OUTPUT);
  cycleButton.begin(cyclePin);
  maintenanceDoneButton.begin(maintenanceDonePin);

  Serial.begin(115200);

  int result = lcd.begin(nbCols, nbRows);
  if (result) {
    Serial.print("LCD initialization failed: ");
    Serial.println(result);
    hd44780::fatalError(result);
  }

  lcd.print("PARTS SAVER 1.0");
  delay(1000);
  lcd.clear();
  showCycles();
  showMaintenanceList();
}

void loop() {
  cycleButton.poll();
  if (cycleButton.onPress()) {
    cycleCounter++;
    saveState();
    showCycles();
    for (auto &p : parts ) {
      if (cycleCounter % p.everyCycle == 0) {
        p.needsMaintenance = true;
        showMaintenanceList();
      }
    }
  }

  maintenanceDoneButton.poll();
  if (maintenanceDoneButton.onPress()) {
    for (auto &p : parts ) p.needsMaintenance = false;
    showMaintenanceList();
    saveState();
  }

  blinkAlertIfNeeded();
}

1 Like

Personally I think that your time would be better spent learning about structs and how to use them in array. This knowledge will be useful in the future but knowledge of how to use a specific library is less likely to be generally applicable

Do you have some pointers on this?

This is one I've seen here:

But still, the docs on making arrays of structs is a bit difficult because the search terms do seem generic. You find structs, arrays, and structs containing arrays, but few combining them as arrays of structs.

Finally modulo shows up in this thread. :wink:

I was getting there - https://www.youtube.com/watch?v=Nfj2fntwS80. It isn't urgent but I'd like to get this going before the garage gets too cold. :cold_face:

What a gift!  Very close to what I envisioned. Thanks, @J-M-L !

I have a small enclosure with a 2x20 LCD for a cancelled project.  Adapting your code to the two-line display won't, it is hoped, be too difficult.  I also already have those two libraries on board.

In previous projects I've had a working EEPROM restore/save and runtime parameter change.  Not drop-in ready but the outline's there.

I'll update the thread when I have something substantial done.

if it's an I2C LCD then you only have to change

One challenge with a smaller screen is that the list of parts needing maintenance might not fit all on screen as I use the first line for information (number of clicks and number of parts needing attention) so with only 2 lines it gives you only 1 line to display the parts labels.

this is the part of the code that builds a text buffer containing a comma separated list of all the parts needing maintenance.

and then I print that string on screen character by character so that I can manage going to the next line when a line is full.

1 Like

Hopefully the examples posted in this topic will have given you some guidance. Let us know if you need anything more

Should work for parallel two-line display also, no?

I've disabled the first line info and start printing on line 0.  That gets me at least three parts labels (alarm names) fully printed.  Total counts is still briefly displayed on startup.

You would have to modify the lcd instance type

The code could show the count and when the parts list is too long then it would alternate between both displays (5s one, 5s the other or something like that)

You could also name the parts with one letter and have a piece of paper in the maintenance doc next to the machine that says part A is the engine, part B is XXX…

I'm thinking of a mathematical solution.  Counts per point aren't that critical, a few one way or the other isn't going to matter, so I'll go with presets with high Least Common Multiples.  That way there shouldn't be too many on screen at one time.

Bah! :grin:

How frequent are the cycles?

LCM(200, 550) = 2200

Yes, but, LCM(203, 549) = 111447

Like every seven to nine seconds.

Do you mean the "Greatest Common Divisor" - ie if something needs fixing every 200 cycles and the other is 300, then you fix everything every 100 cycles otherwise you'll end up fixing stuff too late...

Could be I'm confused.  In any case, rather than stare at another rabbit hole I'm just going to try to make some progress on the encoder/setting change addition.