Alarms on an ESP32 - microcontroller

I am using an ESP32 connected to Google Cloud IoT Core to control lights with a raspberry pi as a hub to send messages to the esp32 through IoT core to turn them on and off at set times of the day.
What I want to do now is just eliminate the raspberry pi and have the esp32 manage when it needs to turn them on or off but I cant see to find a way to set alarms or something like alarms that trigger at specific times of days.
The time value coming in is in milliseconds from UTC
Does something like that exists or how could I accomplish alarms on the esp32?

I don't know if you are using the ESP IDF or some sort of Arduino framework for the ESP32 (with which I am not familiar). This answer is for the ESP IDF. If you are not using the ESP-IDF and don't have access to the relevant header files and libraries, some of the ideas might still be relevant.
Activate SNTP
Ensure that the clock on the controller is updated by following the SNTP example at https://github.com/espressif/esp-idf/tree/master/examples/protocols/sntp .
Use localtime()
If you are using the ESP-IDF you will have access to the localtime_r() function. Pass this (a pointer to) the number of 'seconds since the Epoch' - as returned by time(NULL) - and it will fill a struct tm with the day of the week, day of the month, hour, minute and second.
The general procedure is (error handling ommitted) ...
#include <time.h>
// ...
struct tm now = {};
time_t tnow = time(NULL);
localtime_r(&tnow, &now);
// Compare current day, hour, minute, etc. against pre-defined alarms.
Note: You can use gmtime_r() instead of localtime_r() if you prefer to use UTC.
You can invoke this periodically from your main while() loop as outlined below.
Alarm structures
Define a structure for your alarm information. You could use something like the following - tweaking according to the level of granularity you need. Note that it includes a callback member - to specify what the program should actually do when the alarm is triggered.
typedef struct {
int week_day; // -1 for every day of week
int hour; // -1 for every hour
int minute; // -1 for every minute
void (*callback)(void);
void *callback_arg;
} alarm_t;
Define alarms and callbacks
Define your array of alarms, callback functions and arguments.
static void trigger_relay(const void *arg) {
uint32_t num = *(uint32_t *)arg;
printf("Activating relay %u ...\n", num);
// Handle GPIO etc.
}
static uint32_t relay_1 = 0;
static uint32_t relay_2 = 1;
static alarm_t alarms[] = {
// Trigger relay 1 at 9:00 on Sunday
{0, 9, 0, trigger_relay, (void *)&relay_1},
// Relay 2 at 7:30 every day
{-1, 7, 30, trigger_relay, (void *)&relay_2},
};
Periodic scan
Define a check_alarms() routine which loads a struct tm structure with the current time and compares this with each of the alarms you have defined. If the current time matches that of the alarm, the callback is invoked.
static void check_alarms(void) {
time_t tnow = time(NULL);
struct tm now = {};
localtime_r(&tnow, &now);
ESP_LOGI(TAG, "Time: %u:%u:%u", now.tm_hour, now.tm_min, now.tm_sec);
size_t n_alarms = sizeof(alarms) / sizeof(alarms[0]);
for (int i = 0; i < n_alarms; i++) {
alarm_t *alrm = &alarms[i];
if (alrm->week_day != -1 && alrm->week_day != now.tm_wday) {
continue;
}
if (alrm->hour != -1 && alrm->hour != now.tm_hour) {
continue;
}
if (alrm->minute != -1 && alrm->minute != now.tm_min) {
continue;
}
alrm->callback(alrm->callback_arg);
}
}
This would then be called from your main while() loop with a frequency depending on the granularity of your alarm. Since the alarm_t defined above has a granularity of 1 minute, that is how often I will call check_alarms(). This example is for FreeRTOS but could be adapted as needs be.
while (1) {
TickType_t tick = xTaskGetTickCount();
if (tick - g_time_last_check > pdMS_TO_TICKS(60000)) {
g_time_last_check = tick;
check_alarms();
}
}
Efficiency
The above is not very efficient if you have a lot of alarms and / or each alarm should be triggered at high frequency. In such a case, you might consider algorithms which sort the alarms based on time until elapsed, and - instead of iterating over the entire array every time - instead employ a one-shot software timer to invoke the relevant callback. It probably isn't the worth the hassle unless you have a very large number of timers though.
Alternatives
Both FreeRTOS and the ESP-IDF include APIs for software timers - but these can not be (easily) used for alarms at a specific time of the day - which you state as a requirement. On the other hand - as you may already know - the ESP32 system-on-a-chip does have a built-in RTC, but I think it is best to use a higher-level interface than communicating with it directly.

A real-time clock is what you are looking for. I've never worked with the ESP32 but apparently it has one and it is bad. However, 5% error might be good enough for you. If it's an option, I would use an external one.

Related

Why getLocalTime implementation needs delay

I noticed a weird behavior of my application on an ESP32.
After some "debugging" I think the issue is due to this function:
bool getLocalTime(struct tm * info, uint32_t ms)
{
uint32_t start = millis();
time_t now;
while((millis()-start) <= ms) {
time(&now);
localtime_r(&now, info);
if(info->tm_year > (2016 - 1900)){
return true;
}
delay(10);
}
return false;
}
By the way, ms defaults to 5000.
In my code I use getLocalTime in a finite-state-machine in order to execute actions at specific time, i.e.:
void Irrigation::fsm()
{
time_t now = timing.getTimestamp();
switch (state)
{
case Running:
if (now - lastExecution)
{
// do something
}
break;
}
}
where:
time_t Timing::getTimestamp()
{
struct tm tm;
getLocalTime(&tm);
return mktime(&tm);
}
It seemed to me the application hangs (the fsm is called every second).
Actually, looking at the getLocalTime implementation I don't understand what it does. Why it needs a while cycle and a delay of 10 ms every cycle, just to retrieve the current time?
I'm looking for the epoch time in second. Is my approach wrong?
Thank you for raising this. I just found the same issue when using the ESP32Time by fbiego. Which as you have identified (Thanks) is that delay when fetching the time.
Looking the code, it is always having that 5000ms delay while the time is set before 2016.
I fixed mine by setting the rtc to the start of 2022.
This gets rid of the delay for me until the GPS I am using gets a lock and gives me the correct time.
How to fix that:
Just pass the 2nd parameter to the function. It will limit the delay:
getLocalTime(&timeinfo, 5);
It will finish in 10ms max (if current year is < 2016).
Why it is implemented that way:
Just sharing my guess:
Function returns immediately if the evaluated year is > 2016. And in this case it returns true, i.e. success.
In case if the evaluated year is < 2016, then such result is considered as failed result. So, function waits 10ms and does another attempt to calculate the time.
It runs in that loops for 5 sec (5000 ms).
I think, the assumption here is that the correct date/time might be retrieved from NTP during this delay.
(surely it won't happen automatically. But technically there might be started request to the NTP server, running on the other core)

Arduino - Gyro sensor - ISR - TimeStamp

I currently working to recreate a quad-copter controller.
I am working on getting data from my gyro sensor, and to do that, I'm using
an ISR with an interuption.
My problem is, when I call my function "gyro.getX" on the main program, it work.
But, when I call this function from my ISR, it doesn't work.
I thing that I find the reson of the bug, the function I'm using is provided by the "Adafruit_LSM9DS0" library (from ST), and it used a "timestamp".
I thing that the current time from my ISR is different that the current time from my Main program, but i don't know how to fi it.
Here a shortcut of my program:
void loop(){
/*main prog*/
}
/*
*Reserve interrupt routine service (ISR) by Arduino
*/
ISR(TIMER2_OVF_vect)
{
TCNT2 = 256 - 250; // 250 x 16 µS = 4 ms
if (varCompteur++ > 25)// 25 * 4 ms = 100 ms (half-period)
{
varCompteur = 0;
SensorGet(pX, pY);//Feed gyro circular buffers
}
}
void SensorGet(float * pRollX, float * pPitchY)
{
lsm.getEvent(&accel, &mag, &gyro, &temp);
GiroX_Feed(pX, gyro.gyro.x);
GiroY_Feed(pPitchY, gyro.gyro.y);
}
bool Adafruit_LSM9DS0::getEvent(sensors_event_t *accelEvent,
sensors_event_t *magEvent,
sensors_event_t *gyroEvent,
sensors_event_t *tempEvent)
{
/* Grab new sensor reading and timestamp. */
read();
uint32_t timestamp = millis();
/* Update appropriate sensor events. */
if (accelEvent) getAccelEvent(accelEvent, timestamp);
if (magEvent) getMagEvent(magEvent, timestamp);
if (gyroEvent) getGyroEvent(gyroEvent, timestamp);
if (tempEvent) getTempEvent(tempEvent, timestamp);
return true;
}
The problem isn't the time. The problem is likely that your sensor uses the I2C and it is disabled during an interrupt routine, or it's some other communication protocol that relies on interrupts to function and is therefore disabled during your ISR.
You are really abusing the interrupt. This is not the kind of thing interrupts are for. Interrupt should be super fast, no time for communications there. So the real question is why do you think you need an interrupt for this?

Arduino Function Execution Time

I have an Arduino and an APC220 wireless transceiver. I'm writing a library that reads in data from the APC using the SoftwareSerial class. I originally started with the (incorrect) code below that was causing a seg fault because the i variable is incremented even when there is no data available to read. In cases where it happened to work (by chance when the data was immediately available), this function took approximately 6 milliseconds to execute. When I put the i++; statement in its proper place (above the closing brace immediately above it), the function takes over 270 ms to run. Speed is crucial for this function, so I'm wondering what it is about that statement's placement that causes such a dramatic increase in time.
For the code below, buff is declared as char buff[10]; and sSerial is an instance of SoftwareSerial
unsigned long updateLocation(Marker* marker) {
this->sSerial->print('~');
//initiate request from vision system
this->sSerial->flush();
this->sSerial->print('#');
this->sSerial->print(marker->num);
this->sSerial->print('*');
this->sSerial->flush();
unsigned long start = millis();
int state = 0, i = 0;
while((millis() - start) < 600) {
if(this->sSerial->available()) {
buff[i] = this->sSerial->read();
if(buff[i] == ',') {
buff[i] = 0;
switch(state) {
case 0:
i = -1;
state++;
break;
case 1:
marker->x = atof(buff);
i = -1;
state++;
break;
case 2:
marker->y = atof(buff);
i = -1;
state++;
break;
case 3:
marker->theta = atof(buff);
i = -1;
return (millis() - start);
break;
default:
return 0;
break;
}
}
// Correct location for i++; takes 270 ms to execute
}
// Incorrect location for i++; Takes 6 ms to execute
i++;
}
this->sSerial->print('~');
this->sSerial->flush();
return 0;
}
Assuming that there's data ready and waiting from sSerial, there's no effective difference in the placement of i++.
You said yourself, in most cases the data isn't ready. With the incorrect placement of i++, i quickly grows to greater than the size of buff which causes the segfault.
With the correct placement, your code blocks for up to 600ms waiting for enough data to come in to reach case 3 and return. On average, you're seeing that it takes 270ms for that to happen.
You can test this theory yourself by timing the same function operating directly on a string rather than reading in from serial.
You may want to a) increase your baud rate b) check to see if there's a more efficient software serial implementation you can use c) switch to hardware serial. If you are only using hardware serial currently for debugging output, you can switch that around. Use an FTDI adapter ($6-10 on eBay) to pipe software serial to USB and reserve the hardware serial for your time sensitive function.
You might also be able to reconfigure your code so it doesn't block the whole time waiting. You can read in what's available, store it in a global and go back to your main loop to do something else until there's data available again.
edit: I see now that the APC220 is 9600 baud max. That's pretty slow, so the bottle neck may not be software serial (but you should test it). If the bottle neck is simply the baud rate, you will need to look at optimizing your code not to block waiting for input if there are other things your system can work on while it's waiting.

Begin Transmission and Receiving Byte using I2C, PSOC

I'm new to the PSoC board and I'm trying to read the x,y,z values from a Digital Compass but I'm having a problem in beginning the Transmission with the compass itself.
I found some Arduino tutorial online here but since PSoC doesn't have the library I can't duplicate the code.
Also I was reading the HMC5883L datasheet here and I'm suppose to write bytes to the compass and obtain the values but I was unable to receive anything. All the values I received are zero which might be caused by reading values from wrong address.
Hoping for your answer soon.
PSoC is sorta tricky when you are first starting out with it. You need to read over the documentation carefully of both the device you want to talk to and the i2c module itself.
The datasheet for the device you linked states this on page 18:
All bus transactions begin with the master device issuing the start sequence followed by the slave address byte. The
address byte contains the slave address; the upper 7 bits (bits7-1), and the Least Significant bit (LSb). The LSb of the
address byte designates if the operation is a read (LSb=1) or a write (LSb=0). At the 9
th clock pulse, the receiving slave
device will issue the ACK (or NACK). Following these bus events, the master will send data bytes for a write operation, or
the slave will clock out data with a read operation. All bus transactions are terminated with the master issuing a stop
sequence.
If you use the I2C_MasterWriteBuf function, it wraps all that stuff the HMC's datasheet states above. The start command, dealing with that ack, the data handling, etc. The only thing you need to specify is how to transmit it.
If you refer to PSoC's I2C module datasheet, the MasterWriteBuf function takes in the device address, a pointer to the data you want to send, how many bytes you want to send, and a "mode". It shows what the various transfer modes in the docs.
I2C_MODE_COMPLETE_XFER Perform complete transfer from Start to Stop.
I2C_MODE_REPEAT_START Send Repeat Start instead of Start.
I2C_MODE_NO_STOP Execute transfer without a Stop
The MODE_COMPLETE_XFRE transfer will send the start and stop command for you if I'm not mistaken.
You can "bit-bang" this also if you want but calling directly on the I2C_MasterSendStart, WriteByte, SendStop, etc. But it's just easier to call on their writebuf functions.
Pretty much you need to write your code like follows:
// fill in your data or pass in the buffer of data you want to write
// if this is contained in a function call. I'm basing this off of HMC's docs
uint8 writeBuffer[3];
uint8 readBuffer[6];
writeBuffer[0] = 0x3C;
writeBuffer[1] = 0x00;
writeBuffer[2] = 0x70;
I2C_MasterWriteBuf(HMC_SLAVE_ADDRESS, &writeBuffer, 3, I2C_MODE_COMPLETE_XFER);
while((I2C_MasterStatus() & I2C_MSTAT_WR_CMPLT) == 0u)
{
// wait for operation to finish
}
writeBuffer[1] = 0x01;
writeBuffer[2] = 0xA0;
I2C_MasterWriteBuf(HMC_SLAVE_ADDRESS, &writeBuffer, 3, I2C_MODE_COMPLETE_XFER);
// wait for operation to finish
writeBuffer[1] = 0x02;
writeBuffer[2] = 0x00;
I2C_MasterWriteBuf(HMC_SLAVE_ADDRESS, &writeBuffer, 3, I2C_MODE_COMPLETE_XFER);
// wait for operation to finish
CyDelay(6); // docs state 6ms delay before you can start looping around to read
for(;;)
{
writeBuffer[0] = 0x3D;
writeBuffer[1] = 0x06;
I2C_MasterWriteBuf(HMC_SLAVE_ADDRESS, &writeBuffer, 2, I2C_MODE_COMPLETE_XFER);
// wait for operation to finish
// Docs don't state any different sort of bus transactions for reads.
// I'm assuming it'll be the same as a write
I2C_MasterReadBuf(HMC_SLAVE_ADDRESS, readBuffer, 6, I2C_MODE_COMPLETE_XFER);
// wait for operation to finish, wait on I2C_MSTAT_RD_CMPLT instead of WR_COMPLT
// You should have something in readBuffer to work with
CyDelay(67); // docs state to wait 67ms before reading again
}
I just sorta wrote that off the top of my head. I have no idea if that'll work or not, but I think that should be a good place to start and try. They have I2C example projects to look at also I think.
Another thing to look at so the WriteBuf function doesn't just seem like some magical command, if you right-click on the MasterWriteBuf function and click on "Find Definition" (after you build the project) it'll show you what it's doing.
Following are the samples for I2C read and write operation on PSoC,
simple Write operation:
//Dumpy data values to write
uint8 writebuffer[3]
writebuffer[0] = 0x23
writebuffer[1] = 0xEF
writebuffer[2] = 0x0F
uint8 I2C_MasterWrite(uint8 slaveAddr, uint8 nbytes)
{
uint8 volatile status;
status = I2C_MasterClearStatus();
if(!(status & I2C_MSTAT_ERR_XFER))
{
status = I2C_MasterWriteBuf(slaveAddr, (uint8 *)&writebuffer, nbytes, I2C_MODE_COMPLETE_XFER);
if(status == I2C_MSTR_NO_ERROR)
{
/* wait for write complete and no error */
do
{
status = I2C_MasterStatus();
} while((status & (I2C_MSTAT_WR_CMPLT | I2C_MSTAT_ERR_XFER)) == 0u);
}
else
{
/* translate from I2CM_MasterWriteBuf() error output to
* I2C_MasterStatus() error output */
status = I2C_MSTAT_ERR_XFER;
}
}
return status;
}
Read Operation:
void I2C_MasterRead(uint8 slaveaddress, uint8 nbytes)
{
uint8 volatile status;
status = I2C_MasterClearStatus();
if(!(status & I2C_MSTAT_ERR_XFER))
{
/* Then do the read */
status = I2C_MasterClearStatus();
if(!(status & I2C_MSTAT_ERR_XFER))
{
status = I2C_MasterReadBuf(slaveaddress,
(uint8 *)&(readbuffer),
nbytes, I2C_MODE_COMPLETE_XFER);
if(status == I2C_MSTR_NO_ERROR)
{
/* wait for reading complete and no error */
do
{
status = I2C_MasterStatus();
} while((status & (I2C_MSTAT_RD_CMPLT | I2C_MSTAT_ERR_XFER)) == 0u);
if(!(status & I2C_MSTAT_ERR_XFER))
{
/* Decrement all RW bytes in the EZI2C buffer, by different values */
for(uint8 i = 0u; i < nbytes; i++)
{
readbuffer[i] -= (i + 1);
}
}
}
else
{
/* translate from I2C_MasterReadBuf() error output to
* I2C_MasterStatus() error output */
status = I2C_MSTAT_ERR_XFER;
}
}
}
if(status & I2C_MSTAT_ERR_XFER)
{
/* add error handler code here */
}
}

Calculating the average of Sensor Data (Capacitive Sensor)

So I am starting to mess around with Capacitive sensors and all because its some pretty cool stuff.
I have followed some tutorials online about how to set it up and use the CapSense library for Arduino and I just had a quick question about this code i wrote here to get the average for that data.
void loop() {
long AvrNum;
int counter = 0;
AvrNum += cs_4_2.capacitiveSensor(30);
counter++;
if (counter = 10) {
long AvrCap = AvrNum/10;
Serial.println(AvrCap);
counter = 0;
}
}
This is my loop statement and in the Serial it seems like its working but the numbers just look suspiciously low to me. I'm using a 10M resistor (brown, black, black, green, brown) and am touching a piece of foil that both the send and receive pins are attached to (electrical tape) and am getting numbers around about 650, give or take 30.
Basically I'm asking if this code looks right and if these numbers make sense...?
The language used in the Arduino environment is really just an unenforced subset of C++ with the main() function hidden inside the framework code supplied by the IDE. Your code is a module that will be compiled and linked to the framework. When the framework starts running it first initializes itself then your module by calling the function setup(). Once initialized, the framework enters an infinite loop, calling your modules function loop() on each iteration.
Your code is using local variables in loop() and expecting that they will hold their values from call to call. While this might happen in practice (and likely does since that part of framework's main() is probably just while(1) loop();), this is invoking the demons of Undefined Behavior. C++ does not make any promises about the value of an uninitialized variable, and even reading it can cause anything to happen. Even apparently working.
To fix this, the accumulator AvrNum and the counter must be stored somewhere other than on loop()'s stack. They could be declared static, or moved to the module outside. Outside is better IMHO, especially in the constrained Arduino environment.
You also need to clear the accumulator after you finish an average. This is the simplest form of an averaging filter, where you sum up fixed length blocks of N samples, and then use that average each Nth sample.
I believe this fragment (untested) will work for you:
long AvrNum;
int counter;
void setup() {
AvrNum = 0;
counter = 0;
}
void loop() {
AvrNum += cs_4_2.capacitiveSensor(30);
counter++;
if (counter == 10) {
long AvrCap = AvrNum/10;
Serial.println(AvrCap);
counter = 0;
AvrNum = 0;
}
}
I provided a setup(), although it is redundant with the C++ language's guarantee that the global variables begin life initialized to 0.
your line if (counter = 10) is invalid. It should be if (counter == 10)
The first sets counter to 10 and will (of course) evaluate to true.
The second tests for counter equal to 10 and will not evaluate to true until counter is, indeed, equal to 10.
Also, kaylum mentions the other problem, no initialization of AvrNum
This is What I ended up coming up with after spending some more time on it. After some manual calc it gets all the data.
long AvrArray [9];
for(int x = 0; x <= 10; x++){
if(x == 10){
long AvrMes = (AvrArray[0] + AvrArray[1] + AvrArray[2] + AvrArray[3] + AvrArray[4] + AvrArray[5] + AvrArray[6] + AvrArray[7] + AvrArray[8] + AvrArray[9]);
long AvrCap = AvrMes/x;
Serial.print("\t");
Serial.println(AvrCap);
x = 0;
}
AvrArray[x] = cs_4_2.capacitiveSensor(30);
Serial.println(AvrArray[x]);
delay(500);

Resources