Struct variables change randomly when sensor updates

Hello everyone, I've posted this in 'programming' as I suspect it's a quirk of the language I'm not understanding that is causing my problem - but it may ultimately belong in 'sensors', as a Dallas DS18b20 being pulled out while the problem is in full swing seems to stop it.

I have a small network of microcontollers:

  • An UNO (strictly a bare Atmel 328) that acts as the central controller for my heating system.
  • An ESP01 (ESP8266 based) hard-wired to the UNO to send and receive data over software serial, and wirelessly connected to the below for sending and receiving over the ESP-NOW protocol.
  • A standalone Wemos D1 mini (ESP8266 based) acting as a remote thermostatic control, wirelessly connected to the ESP01 sending/receiving over ESP-NOW protocol.

The UNO and ESP-01 are talking to each other perfectly, sending, receiving, & validating, with the UNO sending out the status of the central heating boiler and the current time, and the ESP-01 forwarding the sensed temperature, the desired temperature, the zone ref. and whether there is a heat demand.
The Wemos is transmitting to the ESP-01 flawlessly, allowing the ESP to forward the above data to the UNO.

The problem I have is with the Wemos receiving data. Sort of. The data comes in good, as confirmed in the serial monitor.
Data that comes in over ESP-NOW fires off a callback OnDataRecv(), which uses memcpy() to fill a struct that is set up identically to the one that sent the data.

Printing to serial inside the callback proves that the data came in just fine, so it isn't the transmission that is the problem.

Printing the data that has been memcpy()'d to the struct over serial in the loop, however, shows that it has been corrupted. The data also changes to seemingly random byte values... at the same frequency as the DS18b20 is requesting temperatures.
And pulling the DS18b20 out while this is happening stops the variables from changing.
Very odd. However, leaving it disconnected and rebooting doesn't result in the correct data going to the variables, just stops the random updates every time the temperature sensor is polled.
(worth noting that the sensor is performing perfectly).

Every time new data is received, the memcpy() works, ans the serial monitor shows several loops with the correct values, but they quickly stray away from that without any apparent cause.

There isn't anything in the sketch that writes to the boilerStatus struct except for the OnDataRecv() callback, and it only runs on receipt of new data, so I'm clueless as to how the members of the struct can be changing in between calls of the callback function?

My hacky way around the problem is that inside the OnDataRecv() callback I copy the member variables of the struct to separate global vars. But I would like to know why it happens, as I believe I should be doing as little as possible in an interrupt function - setting the value of four variables isn't much, but it's something.
(I'm also not sure why this works, as I had previously tried creating a separate global struct, and inside the OnDataRecv() callback, I assigned the incoming struct members to the new struct members. It didn't fix it.)

One more thing which might prove useful - each time the DS18b20 polls, the LED display I am using flickers, suggesting a power issue. Tried decoupling on the sensor, decoupling on the 595s driving the display, all sorts of caps and values in lots of locations, and none if it helped.

The full sketch is below:

/*
Sketch for WEMOS D1 (board "Lolin(Wemos) D1 Mini (Clone)")
DS18b20, encoder control for set temp up & down. A display showing the current temp and set temp. 

Sends the sensed temp, set temp, heat demand status and zone reference to an ESP01 over ESP-NOW
Reveives the boiler status & override status from the ESP01 over ESP-NOW

CMcK 2022

*/
#include <ESP8266WiFi.h>
#include <espnow.h>
#include <EEPROM.h>
#include <ESPRotary.h>

#include <CMcK_Led.h>
#include <CMcK_Button.h>

// *REDACTED for privacy, sketch includes correct MAC*
uint8_t kitchenEspMacAddress[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

// for Dallas DS18b20 temp sensor
#include <SPI.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS D1 // temp sensor Data wire is plugged into this port on the WEMOS D1

/*
/// PARASITE POWER
#define PARASITE_POWER_PIN D2 // wemos:D2 if sensor is DS18b20PAR, connect this pin to the data line as well, it will be used to provide power during conversion

bool parasite_power = false; // set this false if normal DS18b20 with Vcc, or true if parasite power type (PAR) NOTE!!! you never got this working well on ESP01, maybe power wasn't great, seems better on WEMOS so far 
*/

// pins for the 595 shift register
#define LATCH_PIN D2 // wemos:D2
#define CLOCK_PIN D3 // wemos:D3
#define DATA_PIN D7 // wemos:D7


#define SEG_9_PIN D0 // wemos:D0
#define NUM_DISPLAY_SEGMENTS 9

const int screenSleepDelay = 30000;

byte Segment[NUM_DISPLAY_SEGMENTS];	// array of the characters to display each loop
byte displaySegmentCount = 0;		// to track which segment to display each loop

const byte Number[10] = {
	// numbers 0-9 LCD format, a,b,c,d,e,f,g,dp
	0b11111100,	// 0
	0b01100000,	// 1
	0b11011010,	// 2
	0b11110010,	// 3
	0b01100110,	// 4
	0b10110110,	// 5
	0b10111110,	// 6
	0b11100000,	// 7
	0b11111110,	// 8
	0b11110110	// 9
	
};

const byte decimalPoint = 0b00000001;

const byte Letter[30] = {
	0b00000000,	// 0 <space> 
	0b11101110,	// 1 A 
	0b00111110,	// 2 b 
	0b10011100,	// 3 C 
	0b01111010,	// 4 d 
	0b10011110,	// 5 E 
	0b10001110,	// 6 F 
	0b10111100,	// 7 G 
	0b01101110,	// 8 H 
	0b00001100,	// 9 I 
	0b01110000,	// 10 J 
	0b00000000,	// 11* k? 
	0b00011100,	// 12L 
	0b1110110,	// 13* m? 
	0b00101010,	// 14 n 
	0b00111010,	// 15 o 
	0b11001110,	// 16 P 
	0b11100110,	// 17 q 
	0b00001010,	// 18 r 
	0b10110110,	// 19 S 
	0b00011110,	// 20 t 
	0b00111000,	// 21 u 
	0b01010100,	// 22* v? 
	0b01111100,	// 23* w? 
	0b00101000,	// 24* x? 
	0b01110110,	// 25 y 
	0b10010000,	// 26* z?
	0b11000110,	// 27 degree symbol
	0b00000010,	// 28 "-"
	0b00010010	// 29 "="
};

OneWire oneWire(ONE_WIRE_BUS);// Setup a oneWire instance

DallasTemperature sensors(&oneWire);
unsigned long lastSensorCheck = 0;
//const int sensorCheckDelay = 750; // not needed when using dallas library method to ask if the temperature conversion is complete instead of timing between reads
float senseTemp = 30;
float setTemp = 18.5;

byte minSetTemp = 10;
byte maxSetTemp = 30;
float hysteresis = 0.3; // this value is the +- from setTemp at which to switch
byte zone = 1;

unsigned long lastTransmit = 0;
const int transmitDelay = 1000;

#define ENC_1 D6
#define ENC_2 D5
#define CLICKS_PER_STEP   4
ESPRotary encoder;

#define ENCODER_BUTTON D4
Button button1(ENCODER_BUTTON);
unsigned long lastInputMillis;

unsigned long saveStatusTimer;
const int saveStatusDelay = 10000;

unsigned long storeDialogLastChange;
bool storeDialogFlag = true;
const int storeDialogFlashDelay = 2000;

//#define BOILER_LED D4


byte modeState = 1;	
byte prevModeState;

// Structure to send data
// Must match the receiver structure
typedef struct temps_floats_message {
	float senseTemp;
	float setTemp;
	byte zone;
	bool heatDemand;
};

typedef struct boiler_status_message {
	byte boilerIsOn;
	byte overrideIsOn;
	byte hour;
	byte minute;
	//byte crcNum;
};

byte boilerIsOn;
byte overrideIsOn;
byte hour;
byte minute;

// Create struct instances
temps_floats_message zoneData;
boiler_status_message boilerStatus;
boiler_status_message boilerStatusIncoming;

// struct for a function that converts temps from "##.#" to "#, #, #"
typedef struct temperatureBytes {
	byte tens;
	byte units;
	byte tenths;
};

// function to take temp as float andreturn a struct with three bytes
temperatureBytes convertFloatToBytes(float tempAsFloat) {
	// ds18b20 accurate to 0.5degC, so not worried about tenths changing at 5 hundredths
	tempAsFloat *= 10;	// bring tenths up to units
	int tempAsInt = int(tempAsFloat); // cast to int to discard remaining decimal (hundredths)
	
	temperatureBytes tempInBytes;	// initialize struct
	tempInBytes.tenths = tempAsInt % 10;
	tempAsInt /= 10;
	tempInBytes.units = tempAsInt % 10;
	tempInBytes.tens = tempAsInt / 10;
	
	return tempInBytes;
}

// Smoothing global vars
bool firstRun = true;
byte avgCount = 0;
const byte numAvgCounts = 25;
float avgArr[numAvgCounts];
float avgTVal;


// Callback when data is sent (registered in setup)
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
  Serial.print("Last Packet Send Status: ");
  if (sendStatus == 0){
    Serial.println("Delivery success");
  }
  else{
    Serial.println("Delivery fail");
  }
}

// Callback when data is received
void OnDataRecv(uint8_t * mac, uint8_t *incomingData, uint8_t len) {
	Serial.print("Bytes received: ");
	Serial.println(len);
	
	memcpy(&boilerStatus, incomingData, sizeof(boilerStatus));
	
	boilerIsOn = boilerStatus.boilerIsOn;
	overrideIsOn = boilerStatus.overrideIsOn;
	hour = boilerStatus.hour;
	minute = boilerStatus.minute;
	
}

void bootEEPROMCheck() {
	// look for existing set temp in EEPROM at location 0, if between bounds store it in setTemp, else save current setTemp in EEPROM and commit change
	float setTempCheck;
	EEPROM.get(0, setTempCheck);
	if (setTempCheck < maxSetTemp && setTempCheck > minSetTemp) {
		setTemp = setTempCheck;
		Serial.println();
		Serial.print("Existing Set Temp found in EEPROM: ");
		Serial.println(setTempCheck);
	} else {
		EEPROM.put(0, setTemp);
		if (EEPROM.commit()) {
			Serial.println();
			Serial.println("EEPROM successfully committed");
		} else {
		Serial.println();
		Serial.println("ERROR! EEPROM commit failed");
		}
	}
}

// callback func to reset the timer since last input
void encoderChangedCallbackFunction(ESPRotary& encoder) {
	lastInputMillis = millis();
}

void setup() {
	// Init Serial Monitor
	Serial.begin(115200);
	sensors.begin();// Start up the dallas sensors library
	sensors.setWaitForConversion(false); //*** for non-blocking
	sensors.requestTemperatures(); //*** request first temp now to give time to convert before asking what it is in the main sketch

	EEPROM.begin(256);	// required on ESP8266 to allow EEPROM function, with size in bytes
	
	delay(1000); //give Serial a little time
	
	Serial.print("MAC:  ");
	Serial.println(WiFi.macAddress());
	
	bootEEPROMCheck();
	
	// setup the encoder, ESPRotary library format
	encoder.begin(ENC_1, ENC_2, CLICKS_PER_STEP);
	encoder.setChangedHandler(encoderChangedCallbackFunction);
	//encoder.setLeftRotationHandler(showDirection);
	//encoder.setRightRotationHandler(showDirection);
	
	digitalWrite(SEG_9_PIN, HIGH);
	pinMode(SEG_9_PIN, OUTPUT);

	// Set device as a Wi-Fi Station
	WiFi.mode(WIFI_STA);
	WiFi.disconnect();

	// Init ESP-NOW
	if (esp_now_init() != 0) {
		Serial.println("Error initializing ESP-NOW");
		return;
	}

	// set role as COMBO to allow send and receive
	esp_now_set_self_role(ESP_NOW_ROLE_COMBO);
	// register callback func for when data is sent
	esp_now_register_send_cb(OnDataSent);
	// Register a callback function that will be called when data is received
	esp_now_register_recv_cb(OnDataRecv); 
	// Register peers
	esp_now_add_peer(kitchenEspMacAddress, ESP_NOW_ROLE_COMBO, 1, NULL, 0);
	
	// set 595 pins
	pinMode(LATCH_PIN, OUTPUT);
	pinMode(CLOCK_PIN, OUTPUT);
	pinMode(DATA_PIN, OUTPUT);
	
}
 
void loop() {
	
	// Serial.print("boil: "); Serial.print(boilerStatus.boilerIsOn);
	// Serial.print("   override "); Serial.println(boilerStatus.overrideIsOn);
	Serial.println(hour);
	
	encoder.loop(); // reads the encoder, ESPRotary library format
		
	if(millis() - lastInputMillis > screenSleepDelay) modeState = 0; // sleep the screen
	
	displayUpdate();
	
	senseTemp = avgTemp( checkTemp() );	// set the senseTemp to the averaged array of the instantaneous reads
		
	transmitZonedata();
	
	// depending on which modeState, run a function to diplay that mode
	// mode 0 sleep,  
	// mode 1 diplay sense temp 			-> "XX.X*C off" "XX.X*C  on"
	// mode 3 display save dialog 			-> "Pr.= StorE"
	
	// under modes, use:
	/*
	if(modeState != prevModeState) {
		encoder.setIncrement(int);
		encoder.setUpperBound(int);
		encoder.setLowerBound(int);
		encoder.resetPosition(int);
		prevModeState = modeState;
	}
	varToChange = encoder.getPosition();
	*/
	
	
	switch (modeState) {
		case 0: {
			// display sleep
			sleepMenu();
			break;
		}
		
		case 1: {
			// sense temp & output
			senseTempMenu();
			break;
		}
		
		case 2: {
			// set temp screen
			setTempMenu();
			break;
		}
			
		case 20: {
			// save to EEPROM dialog
			setTempSaveScreen();
			break;
		}
		
		case 21: {
			// eeprom confirmation
			saveStatusScreen();
			break;
		}
		
		case 3: {
			//clock
			clockScreen();
			break;
		}
		
		default: {
			modeState = 1;
			break;
		}
	}

/*
// button check needs moved into mode functions
	switch(button1.check()) {

		case 1:
			// single press
			// change screen mode
			Serial.println("sing");
			break;
			
		case 2:
			// double press
			Serial.println("doub");
			
			break;
			
		case 3:
			// hold
			
			break;
			
		case 4:
			// long hold
			
			break;
			
		default:
			break;
	}
*/
}

// modestate 0
void sleepMenu() {
	if(modeState != prevModeState) {
		prevModeState = modeState;
		encoder.setIncrement(1);
		encoder.setUpperBound(1);
		encoder.setLowerBound(-1);
		encoder.resetPosition(0);
		// fill Segment array with " ", i.e. blank
		for(byte seg = 0; seg < NUM_DISPLAY_SEGMENTS; seg++) {
			Segment[seg] = Letter[0];
		}
	}
	// if button pressed or encoder moved
	if(button1.check() != 0 || encoder.getPosition() != 0 ) {
		modeState = 1;
		lastInputMillis = millis();
	}
}
// modestate 1
void senseTempMenu() {
	
	if(modeState != prevModeState) {
		prevModeState = modeState;
		
	}
	
	// struct containing setTemp converted to three bytes
	temperatureBytes senseTempBytes = convertFloatToBytes(senseTemp);
	
	// "XX.X*C off" "XX.X*C  on"
	Segment[0] = Number[senseTempBytes.tens];					// #
	Segment[1] = Number[senseTempBytes.units] | decimalPoint;	// #.
	Segment[2] = Number[senseTempBytes.tenths];					// #
	Segment[3] = Letter[27];									// degree symbol
	Segment[4] = Letter[3] | decimalPoint;						// C
	Segment[5] = Letter[0];										// " "
	
	if(boilerIsOn == 1) { // boiler is on
		Segment[6] = Letter[0]; 	// " "
		Segment[7] = Letter[15];  	// o
		Segment[8] = Letter[14];  	// n
	} else {	// boiler is off
		Segment[6] = Letter[15];	// o
		Segment[7] = Letter[6];		// F
		Segment[8] = Letter[6];		// F
	}
	
	if(button1.check() == 1) {
		lastInputMillis = millis();
		modeState = 2; // single press goes to set temp screen
	}
}
/*
void updateBoilerStatusOnScreen() {
	if(boilerStatus.boilerIsOn == 1) { // boiler is on
		Segment[6] = Letter[0]; 	// " "
		Segment[7] = Letter[15];  	// o
		Segment[8] = Letter[14];  	// n
	} else {	// boiler is off
		Segment[6] = Letter[15];	// o
		Segment[7] = Letter[6];		// F
		Segment[8] = Letter[6];		// F
	}
}
*/

// modestate 2
void setTempMenu() {
	
	// struct containing setTemp converted to three bytes
	temperatureBytes setTempBytes = convertFloatToBytes(setTemp);
	
	if(modeState != prevModeState) {
		prevModeState = modeState;
		// if just changed to this screen set encoder parameters
		encoder.setIncrement(1);
		encoder.setUpperBound(280);
		encoder.setLowerBound(120);
		int t = int(setTemp * 10);
		encoder.resetPosition(t);
	}
	
	// load the right characters into the display array
	// "SEt. XX.X*C" 
	Segment[0] = Letter[19];								// S
	Segment[1] = Letter[5];									// E
	Segment[2] = Letter[20] | decimalPoint;					// t.
	Segment[3] = Letter[0];									// " "
	Segment[4] = Number[setTempBytes.tens];					// #
	Segment[5] = Number[setTempBytes.units] | decimalPoint;	// #.
	Segment[6] = Number[setTempBytes.tenths];				// #
	Segment[7] = Letter[27];								// degree symbol
	Segment[8] = Letter[3];									// C
	
	setTemp = float(encoder.getPosition()) / 10;	// takes the encoder position, makes it a float, divides by ten, to give temp in degrees to 1 decimal place
		
	switch (button1.check()) {
		case 1:
			lastInputMillis = millis();
			modeState = 3; // single press goes to clock
			break;
		
		case 3:
			lastInputMillis = millis();
			modeState = 20; // press & hold to go to setTemp save screeen
			break;
		
		default:
			break;
	}
}
// modestate 20
void setTempSaveScreen() {
	if(modeState != prevModeState) {
		prevModeState = modeState;
	}
	
	// struct containing setTemp converted to three bytes
	temperatureBytes setTempBytes = convertFloatToBytes(setTemp);
	
	/*
	// "L.Pr.=StorE"
	Segment[0] = Letter[12] | decimalPoint;	// L.
	Segment[1] = Letter[16];				// P
	Segment[2] = Letter[18] | decimalPoint;	// r.
	Segment[3] = Letter[29];				// =
	Segment[4] = Letter[19];				// S
	Segment[5] = Letter[20];				// t
	Segment[6] = Letter[15];				// o
	Segment[7] = Letter[18];				// r
	Segment[8] = Letter[5];					// E
	*/

	// toggle a boolean every period to decide what to display
	if(millis() - storeDialogLastChange > storeDialogFlashDelay) {
		storeDialogFlag = !storeDialogFlag;
		storeDialogLastChange = millis();
	}
	
	// first display...
	if(storeDialogFlag) {
		// "LonG.PrESS"
		Segment[0] = Letter[12];				// L
		Segment[1] = Letter[15];				// o
		Segment[2] = Letter[14]; 				// n
		Segment[3] = Letter[7]| decimalPoint;	// G.
		Segment[4] = Letter[16];				// P
		Segment[5] = Letter[18];				// r
		Segment[6] = Letter[5];					// E
		Segment[7] = Letter[19];				// S
		Segment[8] = Letter[19];				// S
	// ...second display
	} else {
		// "2.StorE.##.#"
		Segment[0] = Number[2] | decimalPoint;					// 2.
		Segment[1] = Letter[19];								// S
		Segment[2] = Letter[20];								// t
		Segment[3] = Letter[15];								// o
		Segment[4] = Letter[18];								// r
		Segment[5] = Letter[5] | decimalPoint;					// E.
		Segment[6] = Number[setTempBytes.tens];					// #
		Segment[7] = Number[setTempBytes.units] | decimalPoint;	// #.
		Segment[8] = Number[setTempBytes.tenths];				// #
	}

	switch (button1.check()) {
		case 1:
			lastInputMillis = millis();
			modeState = 2; // single press goes back to set temp
			break;
		
		case 3:
			// move to saveStatus screen & run EEPROM save function
			lastInputMillis = millis();
			modeState = 21; // "stored"/"failed" screen
			break;
		
		default:
			break;
	}
}
// modestate 21
void saveStatusScreen() {
	if(modeState != prevModeState) {
		prevModeState = modeState;
		saveStatusTimer = millis();
		if(setTempToEEPROM(setTemp)) {
			//"Store.Good"
			Segment[0] = Letter[19];						// S
			Segment[1] = Letter[20];						// t
			Segment[2] = Letter[15];						// o
			Segment[3] = Letter[18];						// r
			Segment[4] = Letter[5] | decimalPoint;			// E.
			Segment[5] = Letter[7];							// G
			Segment[6] = Letter[15];						// o
			Segment[7] = Letter[15];						// o
			Segment[8] = Letter[4];							// d
		} else {
			//"Store.FaIL"
			Segment[0] = Letter[19];						// S
			Segment[1] = Letter[20];						// t
			Segment[2] = Letter[15];						// o
			Segment[3] = Letter[18];						// r
			Segment[4] = Letter[5] | decimalPoint;			// E.
			Segment[5] = Letter[6];							// F
			Segment[6] = Letter[1];							// A
			Segment[7] = Letter[9];							// I
			Segment[8] = Letter[12];						// L
		}
	}
	
	if(millis() - saveStatusTimer > saveStatusDelay) modeState = 1;
	if(button1.check()) modeState = 2;
	
}
// modestate 3
void clockScreen() {
	if(modeState != prevModeState) {
		prevModeState = modeState;
		//"Chron.##.##" but initialized with spaces for the numbers to clear the previous screen's data
		Segment[0] = Letter[3];					// C
		Segment[1] = Letter[8];					// h
		Segment[2] = Letter[18];				// r
		Segment[3] = Letter[15];				// o
		Segment[4] = Letter[14] | decimalPoint;	// n.
	}
	
	byte hTens = hour / 10;
	byte hUnits = hour % 10;
	byte mTens = minute / 10;
	byte mUnits = minute %10;
	Segment[5] = Number[hTens];					// #
	Segment[6] = Number[hUnits] | decimalPoint;	// #.
	Segment[7] = Number[mTens];					// #
	Segment[8] = Number[mUnits];				// #


	if(button1.check()) modeState = 1;
}

/*
void updateClockMinutesOnScreen(byte h, byte m) {
	byte hTens = h / 10;
	byte hUnits = h % 10;
	byte mTens = m / 10;
	byte mUnits = m%10;
	Segment[5] = Number[hTens];					// #
	Segment[6] = Number[hUnits] | decimalPoint;	// #.
	Segment[7] = Number[mTens];					// #
	Segment[8] = Number[mUnits];				// #

}
*/

//*** this is not yet implemented - need another set of variables passed to the kitchen ESP, forwarded over serial to the kitchen arduino, and handled by the arduino to enact the override
/*
// modestate 4
const int overrideScreenSpashDelay = 1500;
unsigned long overrideScreenStarted;

void overrideScreen() {
	if(modeState != prevModeState) {
		prevModeState = modeState;
		// "booSt oFF" | "booSt on ", initially with spaces to clear the previous screen
		// the "oFF" or "on " added by function called by onDataRecv callback
		Segment[0] = Letter[18];				// b
		Segment[1] = Letter[18];				// o
		Segment[2] = Letter[18];				// o
		Segment[3] = Letter[19];				// S
		Segment[4] = Letter[20];				// t
	}

	if(overrideIsOn) {
		Segment[6] = Letter[15];					// o
		Segment[7] = Letter[14];					// n
		Segment[8] = Letter[0];					// " "
	}
	else {
		Segment[6] = Letter[15];				// o
		Segment[7] = Letter[6];					// F
		Segment[8] = Letter[6];					// F
	}
	
	
	if(button1.check()) modeState = 1;
}
*/

void displayUpdate() {
	// code here to refresh the screen, each loop is a different segment in sequence
	// takes the counter value and sends out the byte for the required character ( Segment[segmentCounter] ) and the byte for displaySegmentCount
	
	byte character = Segment[displaySegmentCount];
	
	if (displaySegmentCount < 8) {	// the hardware segment will be selected by 8 bit shift reg
		byte position = ~(0b00000001<<displaySegmentCount);
		shift2Bytes(character, position);
		digitalWrite(SEG_9_PIN, HIGH);	// segment 9 has a direct connection to the microcontroller, not enough pins on the 595 shift register
	} else {
		byte position = ~0b00000000;
		shift2Bytes(character, position);
		digitalWrite(SEG_9_PIN, LOW); // segment 9 has a direct connection to the microcontroller, not enough pins on the 595 shift register
	}
	
	// increment segment counter
	displaySegmentCount++;
	if (displaySegmentCount > NUM_DISPLAY_SEGMENTS) displaySegmentCount = 0;
		
}

void displayClear() {
	shift2Bytes(0b00000000, 0b11111111);
	digitalWrite(SEG_9_PIN, HIGH);
}

void shift2Bytes(byte data1, byte data2) {
	digitalWrite(LATCH_PIN, LOW);
	shiftOut(DATA_PIN, CLOCK_PIN, LSBFIRST, data1);
	shiftOut(DATA_PIN, CLOCK_PIN, LSBFIRST, data2);
	digitalWrite(LATCH_PIN, HIGH);
}


// saves the passed float parameter to the EEPROM, if different to the current value
bool setTempToEEPROM(float tPassedIn) {
	float setTempInEEPROM;
	bool saveSuccess;
	EEPROM.get(0, setTempInEEPROM);
	if (setTempInEEPROM != tPassedIn) {
		EEPROM.put(0, tPassedIn);
		if (EEPROM.commit()) {
			saveSuccess = true;
			//Serial.println();
			//Serial.println("EEPROM successfully committed");
		} else {
			saveSuccess = false;
			//Serial.println();
			//Serial.println("ERROR! EEPROM commit failed");
		}
	} else saveSuccess = true;
	return saveSuccess;
}

float checkTemp() {
	//if (millis() - lastSensorCheck > sensorCheckDelay) {
	if (sensors.isConversionComplete()) {	// this is much neater than a timed delay
		/*
		if(parasite_power) {
			pinMode(PARASITE_POWER_PIN, INPUT); //*** PARASITE this tri-states the pin before asking the sensor for data
		}
		*/
		displayClear();	// clear it before the getTempByIndex call to reduce LED display flicker
		float t = sensors.getTempCByIndex(0);
		displayUpdate();
		sensors.requestTemperatures(); // re-request to give time to convert before next request
		
		//Serial.println(t);
		/*
		if(parasite_power) {
			pinMode(PARASITE_POWER_PIN, OUTPUT); //*** PARASITE		this sets the pin as output...
			digitalWrite(PARASITE_POWER_PIN, HIGH); //*** PARASITE	... and sets HIGH to power the DS18b20 during conversion
		}
		*/
		lastSensorCheck = millis();
		//Serial.print("Non-smoothed T\tn"); Serial.println(t);
		return t;
	}
	return senseTemp; // if delay not met, return the existing value
}

void checkHeatDemand() {
	if (zoneData.senseTemp + hysteresis <= zoneData.setTemp) zoneData.heatDemand = true;
	else if (zoneData.senseTemp - hysteresis >= zoneData.setTemp) zoneData.heatDemand = false;
}

float avgTemp(float tReading) {
		
	if (firstRun) { // if it's the first run through, array needs filled with the current read
		for (int arrayIndex = 0; arrayIndex < numAvgCounts; arrayIndex++) {
			avgArr[arrayIndex] = tReading;
		}
		firstRun = false;
	}
	else {
		avgArr[avgCount] = tReading;		// if not first run, set array value at the current count location to the current value
	}
	
	// increment counter, if at end loop back to zero & set max/min to the avg
	avgCount++;
	if (avgCount > numAvgCounts) avgCount = 0;
	
	// take average
	float tSum = 0; // this will become the sum total of all values in the array
	for (int arrayIndex = 0; arrayIndex < numAvgCounts; arrayIndex++) {
		tSum += avgArr[arrayIndex];
	}
	avgTVal = tSum / numAvgCounts;
	return avgTVal;
}

void transmitZonedata() {
	if (millis() - lastTransmit > transmitDelay) {
		
		// Set zoneData values to send
		zoneData.zone = zone;
		zoneData.senseTemp = senseTemp;
		zoneData.setTemp = setTemp;
		checkHeatDemand(); // sets zoneData.heatDemand
		
		// sends the saved stuct_message zoneData
		esp_now_send(kitchenEspMacAddress, (uint8_t *) &zoneData, sizeof(zoneData));
		
		lastTransmit = millis();
	}
}

My suspicion is that this might have something to do with the fact that an interrupt - from the incoming ESP-NOW data - is corrupting the variables, and so I tried making the struct volatile. The compiler didn't like this as the memcpy() I use to put the data into the struct is trying to cast void to volatile void. It wasn't happy, I didn't understand, and I don't know if further effort here would bear any fruit. (it's a shame a Raspberry Pi pun doesn't work here...)

I tried making the struct members volatile, this didn't help the problem.

Maybe I'm just using memcpy wrong? Though what I've done seems to fit the examples I'd read.

Is it possible that the wire library or the DallasTemperature library could be interfering in some way?

A quirk of the Wemos D1 board's D1 pin (that the DS18b20/onewire bus is on)?

If anyone can shed any light on how a struct can be (potentially) modified by a sensor that doesn't relate to it, (or more likely how I'm using memcpy() completely wrong...) I'm all ears!

For now, I'll proceed with my hacky fix, as I've been banging my head for too long...

Is the reported "Bytes received:" the same as sizeof boilerStatus? If not, the sender and receiver aren't agreeing on the data layout.

Yes, 2 bytes showing as received.

The data comes in good, and if used only at the time OnDataRecv() is run, all is fine.

However the location that the memcpy() transfers that data to doesn't hold onto the correct values for long, just a handful of loops

--CORRECTION--
apologies, 4 bytes. I had just added the extra two byte variables at the time I posted. Sending struct and receiving struct are the same, each containing 4 bytes

Ok, I figured it out!

My structs are defined using typedef.

Removing 'typedef' solves the problem.

I don't know why, it's all a bit over my head still, the below linked stackoverflow page is talking about aliases and namespaces, and I'm just not following...

-- EDIT --

No, you can ignore the above, it has not fixed it at all.

But, I have discovered that if in OnDataRecv(), I copy the incoming data to individual global vars, the contents of the struct are stable.
If I don't copy them, the struct members have the strange values.

void OnDataRecv(uint8_t * mac, uint8_t *incomingData, uint8_t len) {
	Serial.print("Bytes received: ");
	Serial.println(len);
	
	memcpy(&boilerStatus, incomingData, sizeof(boilerStatus));
	
// with the below four lines, the global struct boilerStatus has its member variables stable with the correct values. without these lines, the struct members have incorrect and rendomly changing values
	boilerIsOn = boilerStatus.boilerIsOn;
	overrideIsOn = boilerStatus.overrideIsOn;
	hour = boilerStatus.hour;
	minute = boilerStatus.minute;
	
}

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