Object Oriented Aquarium using the Arduino and Raspberry Pi
What is Object-Oriented Programming
Object-Oriented Programming (OOP) is a programming paradigm or methodology that organizes and structures code around the concept of "objects." Objects are instances of classes, which are templates or blueprints for creating objects. OOP is a way of designing and modeling software that emphasizes the following key principles:
- Objects: Objects are the fundamental building blocks of OOP. Each object represents a real-world entity, concept, or thing and encapsulates both data (attributes or properties) and behavior (methods or functions) related to that entity. For example, you could have objects representing cars, employees, bank accounts, or any other concept in your program.
- Classes: A class is a blueprint or template for creating objects of a specific type. It defines the structure and behavior of objects of that class. The class specifies what data an object will have (attributes) and what operations it can perform (methods). Objects are created based on classes.
- Encapsulation: Encapsulation is the practice of bundling data (attributes) and methods (functions) that operate on that data within a single unit, the object. It provides a way to control access to the internal state of an object and ensures that the object's data is accessed and modified through well-defined interfaces (methods).
- Inheritance: Inheritance allows you to create a new class (a subclass or derived class) based on an existing class (a superclass or base class). The subclass inherits the attributes and methods of the superclass and can also add its own attributes and methods or override existing ones. Inheritance promotes code reuse and the creation of hierarchical relationships between classes.
- Polymorphism: Polymorphism means "many forms." It allows objects of different classes to be treated as objects of a common superclass through a shared interface. This enables flexibility and extensibility in code. Polymorphism is often achieved through method overriding and interfaces in languages like Java and C#.
- Abstraction: Abstraction is the process of simplifying complex systems by breaking them down into smaller, more manageable parts. In OOP, classes and objects provide a level of abstraction that hides the internal implementation details while exposing a well-defined interface.
OOP promotes modular and organized code, making it easier to design, understand, maintain, and extend software systems. It is widely used in various programming languages, including Java, C++, Python, C#, and many others. OOP is particularly suitable for modeling real-world systems and relationships between entities, making it a valuable approach for software development in a wide range of domains.
Example of Object-Oriented Programming on the Arduino
Here's a simple example of Object-Oriented Programming (OOP) on an Arduino using LEDs. In this example, we'll create a basic LED class to represent individual LEDs, allowing you to control their state (on/off) and behavior independently.
// Define the LED class
class LED {
private:
int pin; // The Arduino pin connected to the LED
bool isOn; // LED state (on or off)
public:
// Constructor: Initialize the LED with a specific pin
LED(int pin) {
this->pin = pin;
isOn = false; // Default state is off
pinMode(pin, OUTPUT); // Set the pin as an output
}
// Method to turn the LED on
void turnOn() {
digitalWrite(pin, HIGH); // Set the pin voltage to HIGH
isOn = true; // Update the state
}
// Method to turn the LED off
void turnOff() {
digitalWrite(pin, LOW); // Set the pin voltage to LOW
isOn = false; // Update the state
}
// Method to toggle the LED state (on/off)
void toggle() {
if (isOn) {
turnOff();
} else {
turnOn();
}
}
};
// Create instances of the LED class for two LEDs
LED led1(2); // Connect LED 1 to digital pin 2
LED led2(3); // Connect LED 2 to digital pin 3
void setup() {
// No setup required in this example
}
void loop() {
// Toggle the state of LED 1 every second
led1.toggle();
delay(1000);
// Toggle the state of LED 2 every 500 milliseconds
led2.toggle();
delay(500);
}
In this example:
- We define an LED class with private member variables pin and isOn, and public member functions (methods) to control the LED's state (turnOn, turnOff, and toggle).
- The constructor LED(int pin) initializes an LED object with a specific Arduino pin, sets its initial state to off, and configures the pin as an output.
- We create two instances of the LED class: led1 and led2, representing two LEDs connected to different digital pins.
- In the setup() function, we don't need any setup in this example.
- In the loop() function, we toggle the state of led1 every second and the state of led2 every 500 milliseconds, creating a simple alternating LED pattern.
This example demonstrates the principles of OOP by encapsulating the LED behavior within a class. Each LED object maintains its own state, and you can control and manipulate them independently, making it a scalable and organized approach for working with multiple LEDs or other components.
Creating Header, Implementation and Main Sketch Files as Tabs in Arduino
In Arduino, you can create header files (.h files) to organize and encapsulate functions, variables, and declarations that you want to reuse across multiple sketches or tabs. Header files make your code more modular and easier to maintain. Here's how to create and use header files in Arduino:
Step 1: Create a Header File
- Open the Arduino IDE.
- Go to File -> New to create a new sketch.
- In the new sketch, go to Sketch -> New Tab to create a new tab. This tab will be your header file. By convention, Arduino header files have the ".h" extension, so name it accordingly (e.g., "MyLibrary.h").
- In the header file, define your functions, variables, and any other declarations that you want to reuse in your sketches. For example:
// MyLibrary.h
#ifndef MyLibrary_h
#define MyLibrary_h
// Function declarations
void myFunction(int value);
// Variable declarations
extern int myVariable;
#endif
Use #ifndef, #define, and #endif to include guards to prevent multiple inclusions of the same header file. The extern keyword is used for variable declarations in the header file. It tells the compiler that the actual variable is defined elsewhere (typically in one of your sketches).
Step 2: Create a Sketch (Tab)
Go back to your main sketch (the one you created initially). In your main sketch, include the header file you created at the beginning of your code. Use #include followed by the filename in double quotes:
// YourMainSketch.ino
#include "MyLibrary.h"
void setup() {
// Setup code here
}
void loop() {
// Loop code here
}
This step makes the declarations from your header file available for use in your sketch.
Step 3: Implement File
Create a new tab (if needed) for the implementation of your header file. This tab will contain the actual code for the functions and variables declared in the header file. Name the file (in the tab) MyLibrary.cpp In the implementation tab, implement the functions and variables as per your declarations in the header file:
// MyLibrary.cpp
#include "MyLibrary.h"
// Define the function
void myFunction(int value) {
// Function code here
}
// Define the variable
int myVariable = 42;
Ensure that the implementation file (.cpp) includes the header file at the beginning.
Now, you have a modular Arduino library structure where the header file contains declarations, and the implementation file contains the actual code. You can use this library (header and implementation) in multiple sketches by including the header file with #include.
To use functions or access variables defined in the library, simply call them in your main sketch code.
Note: This method is suitable for more complex code organization or for creating custom libraries for Arduino. For simple projects, organizing code across tabs within the Arduino IDE is usually sufficient.
Object Oriented Example from SARCNET
Separate Arduino Library Files
Creating a separate library file in Arduino C++ allows you to encapsulate code and functionality into reusable libraries. Here, I'll provide a step-by-step guide to creating a custom Arduino library for controlling LEDs. We'll create a library called "LEDControl" that includes a simple example of turning an LED on and off.
Step 1: Create the Library Folder or create new Tabs in the Arduino IDE
Navigate to your Arduino Sketchbook directory. This is typically located in your Documents folder. Inside the Sketchbook directory, create a new folder called "libraries" if it doesn't already exist.
Step 2: Create the Library
Inside the "libraries" folder, create a new folder named "LEDControl." This folder will contain your custom library.
Step 3: Create the Library Files
Inside the "LEDControl" folder, create the following files:
LEDControl.h: This is the header file for your library, where you declare your class and its methods. LEDControl.cpp: This is the implementation file, where you define the actual functionality of your class.
Step 4: Write the Library Code
Here's an example of what your LEDControl.h and LEDControl.cpp files might look like:
LEDControl.h:
#ifndef LEDControl_h
#define LEDControl_h
#include <Arduino.h>
class LEDControl {
private:
int pin;
bool isOn;
public:
LEDControl(int pin);
void turnOn();
void turnOff();
void toggle();
};
#endif
LEDControl.cpp:
#include "LEDControl.h"
LEDControl::LEDControl(int pin) {
this->pin = pin;
isOn = false;
pinMode(pin, OUTPUT);
}
void LEDControl::turnOn() {
digitalWrite(pin, HIGH);
isOn = true;
}
void LEDControl::turnOff() {
digitalWrite(pin, LOW);
isOn = false;
}
void LEDControl::toggle() {
if (isOn) {
turnOff();
} else {
turnOn();
}
}
Step 5: Use the Library in Your Arduino Sketch
Now that you've created the library, you can use it in your Arduino sketch. Here's a simple example of how to use the "LEDControl" library to control an LED:
#include <LEDControl.h>
// Create an LEDControl object for an LED connected to digital pin 13
LEDControl led(13);
void setup() {
// No setup needed in this example
}
void loop() {
// Toggle the LED state every second
led.toggle();
delay(1000);
}
Step 6: Upload and Run Your Sketch
Upload your sketch to your Arduino board as you normally would. The "LEDControl" library will handle the LED control, making your code more modular and organized.
This example demonstrates how to create a custom Arduino library for LED control, but you can extend this approach to create libraries for other components and functionality, making your Arduino projects more maintainable and reusable.
this->pin = pin;
In the code snippet this->pin = pin;, this refers to the current instance of the class, and it's used to differentiate between a class member variable and a function parameter or a local variable that share the same name. This is often necessary when there is a naming conflict.
Here's a breakdown of the code:
- this is a pointer to the current instance of the class. It allows you to access the class's member variables and methods within class methods.
- pin on the left side of the assignment (this->pin) is a member variable of the class. It represents an attribute of the class that can hold data.
- pin on the right side of the assignment (= pin) is a function parameter. It represents the value that's passed to the constructor when creating an instance of the class.
So, in the context of the code you provided:
LEDControl::LEDControl(int pin) {
this->pin = pin;
// ...
}
- this->pin assigns the value of the pin parameter passed to the constructor to the pin member variable of the LEDControl class. This is done to initialize the class's pin with the value provided when creating an instance of the class.
- By using this->pin, you're specifying that you want to assign the value to the class member variable pin rather than just assigning it to the local variable pin. This disambiguates which variable you're referring to, especially in cases where the parameter name and member variable name have the same name, as in this example.
this->pin versus _pin
Using this->pin = pin; and _pin to reference class member variables in Arduino OOP code is functionally equivalent. Both approaches serve the same purpose, which is to distinguish class member variables from local variables or function parameters. However, they differ in terms of coding style and personal preference. Here's a comparison of the two:
this->pin = pin;: This approach uses the this pointer, which is a reference to the current instance of the class. It explicitly specifies that pin is a member variable of the current object (instance). It is more verbose and can be seen as explicit, especially if there is a risk of variable shadowing or ambiguity.
class MySensor {
private:
int pin; // Class member variable without underscore prefix
public:
MySensor(int pin) {
this->pin = pin; // Assign the passed value to the class member variable
}
void readSensorValue() {
int sensorValue = analogRead(this->pin); // Access the class member variable using this->pin
// ...
}
};
_pin: This approach uses an underscore prefix directly in the variable name to indicate that it's a class member. It is more concise and commonly used in OOP code. It relies on naming conventions and may not explicitly indicate that it's referring to the current instance.
class MySensor {
private:
int _pin; // Class member variable with an underscore prefix
public:
MySensor(int pin) {
_pin = pin; // Assign the passed value to the class member variable
}
void readSensorValue() {
int sensorValue = analogRead(_pin); // Access the class member variable using _pin
// ...
}
};
Both approaches are valid, and which one to use often depends on personal coding style preferences and the coding standards followed by a team or project. The key is to maintain consistency within your codebase to make it more readable and understandable.
Object-Oriented Programming code for the Arduino reading a Potentiometer
Reading Raw Values from a Potentiometer
int pHValue;
class AnalogInput{
public:
AnalogInput(int pin);
int readParameter();
private:
int _pin;
};
AnalogInput::AnalogInput(int pin) {
_pin = pin;
}
int AnalogInput::readParameter() {
return analogRead(_pin);
}
AnalogInput pH(A1);
void setup() {
Serial.begin(9600);
}
void loop() {
pHValue = pH.readParameter();
Serial.println(pHValue);
delay(1000);
}
Adjusting Potentiometer Raw Values using Map Function
Below is an example of an object-oriented Arduino sketch that reads the value of a potentiometer using an analog pin and converts it to a value between 4.0 and 9.0. In this example, we'll create a class called PotentiometerReader to encapsulate this functionality:
// Define the PotentiometerReader class
class PotentiometerReader {
private:
int analogPin;
float minValue;
float maxValue;
public:
// Constructor: Initialize the PotentiometerReader with an analog pin,
// minimum value, and maximum value
PotentiometerReader(int pin, float minVal, float maxVal) {
analogPin = pin;
minValue = minVal;
maxValue = maxVal;
pinMode(analogPin, INPUT);
}
// Read the potentiometer value and convert it to a range between 4.0 and 9.0
float readValue() {
int rawValue = analogRead(analogPin); // Read analog value (0-1023)
float scaledValue = map(rawValue, 0, 1023, minValue * 100, maxValue * 100) / 100.0; // Map to range
return scaledValue;
}
};
// Create an instance of the PotentiometerReader class
PotentiometerReader pH(A1, 5.0, 9.0); // Connect potentiometer to analog pin A1
void setup() {
Serial.begin(9600); // Initialize serial communication
}
void loop() {
// Read and print the potentiometer value
float pHValue = pH.readValue();
Serial.println("Potentiometer Value: " + String(pHValue, 2)); // Print with 2 decimal places
delay(500); // Delay for stability
}
In this code:
- We define a PotentiometerReader class with private member variables analogPin (the analog pin connected to the potentiometer), minValue (the minimum value in the desired range), and maxValue (the maximum value in the desired range).
- The constructor PotentiometerReader(int pin, float minVal, float maxVal) initializes the class with the specified analog pin and value range, and it configures the analog pin as an input.
- The readValue() method reads the analog value from the potentiometer using analogRead(), maps it from the full range of 0-1023 to the desired range between minValue and maxValue, and returns the scaled value.
- In the setup() function, we initialize serial communication to display the potentiometer value on the serial monitor.
- In the loop() function, we continuously read and print the potentiometer value, delaying for stability.
Make sure to connect a potentiometer to analog pin A0 or adjust the analogPin parameter accordingly. This code will read the potentiometer value, map it to the specified range, and display it on the serial monitor.
Adding all Water Quality Measurement Parameters
// Define the PotentiometerReader class
class PotentiometerReader {
private:
int analogPin;
float minValue;
float maxValue;
public:
// Constructor: Initialize the PotentiometerReader with an analog pin,
// minimum value, and maximum value
PotentiometerReader(int pin, float minVal, float maxVal) {
analogPin = pin;
minValue = minVal;
maxValue = maxVal;
pinMode(analogPin, INPUT);
}
// Read the potentiometer value and convert it to a range between 4.0 and 9.0
float readValue() {
int rawValue = analogRead(analogPin); // Read analog value (0-1023)
float scaledValue = map(rawValue, 0, 1023, minValue * 100, maxValue * 100) / 100.0; // Map to range
return scaledValue;
}
};
/* Look up chart
Salinity 200-350ppm - A2
pH 6.0 - 8.0 - A1
Phosphate 0.0-3.0 - A0
Ammonium 0.0-1.0 - A5
Nitrite 0.0-1.0 - A4
Nitrate 0.0-2.5 - A3
*/
// Create an instance of the PotentiometerReader class
PotentiometerReader phosphate(A0, 0.0, 3.0);
PotentiometerReader pH(A1, 5.0, 9.0); // Connect potentiometer to analog pin A1
PotentiometerReader salinity(A2, 100, 450);
PotentiometerReader ammonium(A5, 0.0, 1.3);
PotentiometerReader nitrite(A4, 0.0, 1.3);
PotentiometerReader nitrate(A3, 0.0, 35);
void setup() {
Serial.begin(9600); // Initialize serial communication
}
void loop() {
// Read and print the potentiometer value
float salinityValue = salinity.readValue();
Serial.print("Salinity: " + String(salinityValue, 0));
float pHValue = pH.readValue();
Serial.print(" - pH: " + String(pHValue, 2)); // Print with 2 decimal places
float phosphateValue = phosphate.readValue();
Serial.print(" - Phosphate: " + String(phosphateValue, 2)); // Print with 2 decimal places
float ammoniumValue = ammonium.readValue();
Serial.print(" - Ammonium: " + String(ammoniumValue, 2)); // Print with 2 decimal places
float nitriteValue = nitrite.readValue();
Serial.print(" - Nitrite: " + String(nitriteValue, 2)); // Print with 2 decimal places
float nitrateValue = nitrate.readValue();
Serial.println(" - Nitrate: " + String(nitrateValue, 1)); // Print with 1 decimal places
delay(500); // Delay for stability
}
All Water Quality Measurement Parameters and Temperature
- See the following link to include a One Wire Temperature Sensor.
- One Wire Temperature Sensor
- Waterproof DS18B20 Digital temperature sensor
#include <OneWire.h>
#include <DallasTemperature.h>
// Data wire is conntec to the Arduino digital pin 4
#define ONE_WIRE_BUS 4
// Define the PotentiometerReader class
class PotentiometerReader {
private:
int analogPin;
float minValue;
float maxValue;
public:
// Constructor: Initialize the PotentiometerReader with an analog pin,
// minimum value, and maximum value
PotentiometerReader(int pin, float minVal, float maxVal) {
analogPin = pin;
minValue = minVal;
maxValue = maxVal;
pinMode(analogPin, INPUT);
}
// Read the potentiometer value and convert it to a range between 4.0 and 9.0
float readValue() {
int rawValue = analogRead(analogPin); // Read analog value (0-1023)
float scaledValue = map(rawValue, 0, 1023, minValue * 100, maxValue * 100) / 100.0; // Map to range
return scaledValue;
}
};
/* Look up chart
Salinity 200-350ppm - A2
pH 6.0 - 8.0 - A1
Phosphate 0.0-3.0 - A0
Ammonium 0.0-1.0 - A5
Nitrite 0.0-1.0 - A4
Nitrate 0.0-2.5 - A3
*/
// Create an instance of the PotentiometerReader class
PotentiometerReader phosphate(A0, 0.0, 3.0);
PotentiometerReader pH(A1, 5.0, 9.0); // Connect potentiometer to analog pin A1
PotentiometerReader salinity(A2, 100, 450);
PotentiometerReader ammonium(A5, 0.0, 1.3);
PotentiometerReader nitrite(A4, 0.0, 1.3);
PotentiometerReader nitrate(A3, 0.0, 35);
// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);
// Pass our oneWire reference to Dallas Temperature sensor
DallasTemperature tempSensor(&oneWire);
void setup() {
Serial.begin(9600); // Initialize serial communication
}
void loop() {
// Read and print the potentiometer value
float salinityValue = salinity.readValue();
Serial.print("Salinity: " + String(salinityValue, 0));
float pHValue = pH.readValue();
Serial.print(" - pH: " + String(pHValue, 2)); // Print with 2 decimal places
float phosphateValue = phosphate.readValue();
Serial.print(" - Phosphate: " + String(phosphateValue, 2)); // Print with 2 decimal places
float ammoniumValue = ammonium.readValue();
Serial.print(" - Ammonium: " + String(ammoniumValue, 2)); // Print with 2 decimal places
float nitriteValue = nitrite.readValue();
Serial.print(" - Nitrite: " + String(nitriteValue, 2)); // Print with 2 decimal places
float nitrateValue = nitrate.readValue();
Serial.print(" - Nitrate: " + String(nitrateValue, 1)); // Print with 1 decimal places
// Call sensors.requestTemperatures() to issue a global temperature and Requests to all devices on the bus
tempSensor.requestTemperatures();
float temperatureValue = tempSensor.getTempCByIndex(0);
Serial.println(" - Temperature: " + String(temperatureValue, 1)); // Print with 1 decimal places
delay(500); // Delay for stability
}
All Water Quality Measurement Parameters and Temperature - 10 Nov 2023
#include <OneWire.h>
#include <DallasTemperature.h>
// Data wire is conntec to the Arduino digital pin 4
#define ONE_WIRE_BUS 4
// Define the UltrasonicSensor class
class UltrasonicSensor {
private:
int _triggerPin;
int _echoPin;
public:
UltrasonicSensor(int trigPin, int echPin) {
_triggerPin = trigPin;
_echoPin = echPin;
pinMode(_triggerPin, OUTPUT);
pinMode(_echoPin, INPUT);
}
float getDistance() {
digitalWrite(_triggerPin, LOW);
delayMicroseconds(2);
digitalWrite(_triggerPin, HIGH);
delayMicroseconds(10);
digitalWrite(_triggerPin, LOW);
long duration = pulseIn(_echoPin, HIGH);
// Speed of sound at sea level at 20°C is approximately 343 m/s
// Divide by 2 for round trip, and convert to centimeters
float distance = (duration * 0.0343) / 2;
if (distance >= 100 || distance <= 2) {
distance = 0;
}
return distance;
}
};
// Define the PotentiometerReader class
class PotentiometerReader {
private:
int analogPin;
float minValue;
float maxValue;
public:
// Constructor: Initialize the PotentiometerReader with an analog pin,
// minimum value, and maximum value
PotentiometerReader(int pin, float minVal, float maxVal) {
analogPin = pin;
minValue = minVal;
maxValue = maxVal;
pinMode(analogPin, INPUT);
}
// Read the potentiometer value and convert it to a range between 4.0 and 9.0
float readValue() {
int rawValue = analogRead(analogPin); // Read analog value (0-1023)
float scaledValue = map(rawValue, 0, 1023, minValue * 100, maxValue * 100) / 100.0; // Map to range
return scaledValue;
}
};
/* Look up chart
Salinity 200-350ppm - A2
pH 6.0 - 8.0 - A1
Phosphate 0.0-3.0 - A0
Ammonium 0.0-1.0 - A5
Nitrite 0.0-1.0 - A4
Nitrate 0.0-2.5 - A3
*/
// Create an instance of the PotentiometerReader class
PotentiometerReader phosphate(A0, 0.0, 3.0);
PotentiometerReader pH(A1, 5.0, 9.0); // Connect potentiometer to analog pin A1
PotentiometerReader salinity(A2, 100, 450);
PotentiometerReader ammonium(A5, 0.0, 1.3);
PotentiometerReader nitrite(A4, 0.0, 1.3);
PotentiometerReader nitrate(A3, 0.0, 35);
// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);
// Pass our oneWire reference to Dallas Temperature sensor
DallasTemperature tempSensor(&oneWire);
// Define the digital pins for the ultrasonic sensor
const int triggerPin = 7; // Connect to the trigger pin
const int echoPin = 8; // Connect to the echo pin
// Create an instance of the UltrasonicSensor class
UltrasonicSensor distanceSensor(triggerPin, echoPin);
void setup() {
Serial.begin(9600); // Initialize serial communication
}
void loop() {
// Read and print the potentiometer value
float salinityValue = salinity.readValue();
//Serial.print("Salinity: " + String(salinityValue, 0));
float pHValue = pH.readValue();
//Serial.print(" - pH: " + String(pHValue, 2)); // Print with 2 decimal places
float phosphateValue = phosphate.readValue();
//Serial.print(" - Phosphate: " + String(phosphateValue, 2)); // Print with 2 decimal places
float ammoniumValue = ammonium.readValue();
//Serial.print(" - Ammonium: " + String(ammoniumValue, 2)); // Print with 2 decimal places
float nitriteValue = nitrite.readValue();
//Serial.print(" - Nitrite: " + String(nitriteValue, 2)); // Print with 2 decimal places
float nitrateValue = nitrate.readValue();
//Serial.print(" - Nitrate: " + String(nitrateValue, 1)); // Print with 1 decimal places
// Call sensors.requestTemperatures() to issue a global temperature and Requests to all devices on the bus
tempSensor.requestTemperatures();
float temperatureValue = tempSensor.getTempCByIndex(0);
//Serial.print(" - Temperature: " + String(temperatureValue, 1)); // Print with 1 decimal places
// Water level - read distance from ultrasonic sensor
float distanceValue = distanceSensor.getDistance();
//Serial.println(" - Distance: " + String(distanceValue, 2)); // Print with 2 decimal places
Serial.println(String(salinityValue, 0) + "," + String(pHValue, 2) + "," + String(phosphateValue, 2) + "," + \
String(ammoniumValue, 2) + "," + String(nitriteValue, 2) + "," + String(nitrateValue, 1) + "," + \
String(temperatureValue, 1) + "," + String(distanceValue, 2));
delay(3000); // Delay for stability
}
Using Separate Headers and CPP files in Tabs
Object-Oriented Programming code for the Arduino reading a Potentiometer with separate Header and CPP files
PotentiometerSensor.h (Header File)
#ifndef PotentiometerReader_h
#define PotentiometerReader_h
#include <Arduino.h>
class PotentiometerReader {
private:
int analogPin;
float minValue;
float maxValue;
public:
PotentiometerReader(int pin, float minVal, float maxVal);
float readValue();
};
#endif
PotentiometerSensor.cpp (Implementation File)
#include "PotentiometerReader.h"
PotentiometerReader::PotentiometerReader(int pin, float minVal, float maxVal) {
analogPin = pin;
minValue = minVal;
maxValue = maxVal;
pinMode(analogPin, INPUT);
}
float PotentiometerReader::readValue() {
int rawValue = analogRead(analogPin);
float scaledValue = map(rawValue, 0, 1023, minValue * 100, maxValue * 100) / 100.0;
return scaledValue;
}
YourMainSketch.ino (Main Sketch)
#include <Arduino.h>
#include "PotentiometerReader.h"
PotentiometerReader potReader(A0, 4.0, 9.0);
void setup() {
Serial.begin(9600);
}
void loop() {
float potValue = potReader.readValue();
Serial.println("Potentiometer Value: " + String(potValue, 2));
delay(500);
}
pH measurement using Potentiometer and OOP
- This code uses Object-Oriented Programming (OOP)
- An Instance of the PotentiometerReader class is created named phReader
- The instance is initilised with pin A1, minValue 4.0 and maxValue 9.0
- Other instances of the PotiometerReader class can be created for other important water quality measurements such as:
- nitrate
- nitrite
- ammonia
- phosphate
// Define the PotentiometerReader class
class PotentiometerReader {
private:
int analogPin;
float minValue;
float maxValue;
public:
// Constructor: Initialize the PotentiometerReader with an analog pin,
// minimum value, and maximum value
PotentiometerReader(int pin, float minVal, float maxVal) {
analogPin = pin;
minValue = minVal;
maxValue = maxVal;
pinMode(analogPin, INPUT);
}
// Read the potentiometer value and convert it to a range between 4.0 and 9.0
float readValue() {
int rawValue = analogRead(analogPin); // Read analog value (0-1023)
float scaledValue = map(rawValue, 0, 1023, minValue * 100, maxValue * 100) / 100.0; // Map to range
return scaledValue;
}
};
// Create an instance of the PotentiometerReader class
//PotentiometerReader potReader(A0, 4.0, 9.0); // Connect potentiometer to analog pin A0
PotentiometerReader phReader(A1, 4.0, 9.0); // Connect potentiometer to analog pin A0
void setup() {
Serial.begin(9600); // Initialize serial communication
}
void loop() {
// Read and print the potentiometer value
//float potValue = potReader.readValue();
//Serial.println("Potentiometer Value: " + String(potValue, 2)); // Print with 2 decimal places
float pH = phReader.readValue();
Serial.println("pH Value: " + String(pH, 2)); // Print with 2 decimal places
delay(1000); // Delay for stability
}
Object-Oriented Programming code for DS18B20 One-wire Temperature sensor
The DS18B20 One-wire temperature sensor will measure the water temperature of the aquarium.
The sensor was purchased from Core Electronics - Temperature Sensor - Waterproof (DS18B20) Sparkfun
To create object-oriented Arduino C++ code for the DS18B20 temperature sensor, you can define a class that encapsulates the functionality of the sensor. Here's an example of object-oriented code for interfacing with a DS18B20 temperature sensor:
#include <OneWire.h>
#include <DallasTemperature.h>
// Define the DS18B20TemperatureSensor class
class DS18B20TemperatureSensor {
private:
OneWire oneWire;
DallasTemperature sensors;
DeviceAddress sensorAddress; // Replace with your sensor's address
public:
DS18B20TemperatureSensor(int pin) : oneWire(pin), sensors(&oneWire) {
// Optionally, set the sensor resolution (e.g., SENSOR_9_BIT, SENSOR_10_BIT, SENSOR_11_BIT, SENSOR_12_BIT)
// sensors.setResolution(sensorAddress, SENSOR_12_BIT);
sensors.begin();
sensors.getAddress(sensorAddress, 0); // Replace with the sensor's address if known
}
float readTemperature() {
sensors.requestTemperaturesByAddress(sensorAddress);
float temperature = sensors.getTempC(sensorAddress);
if (temperature != DEVICE_DISCONNECTED_C) {
return temperature;
} else {
// Error reading temperature data
return -999.99; // Use a sentinel value to indicate an error
}
}
};
// Define the digital pin where the DS18B20 sensor is connected
const int ds18b20Pin = 4; // Replace with your pin number
// Create an instance of the DS18B20TemperatureSensor class
DS18B20TemperatureSensor tempSensor(ds18b20Pin);
void setup() {
Serial.begin(9600); // Initialize serial communication
}
void loop() {
// Read temperature from the DS18B20 sensor
float temperature = tempSensor.readTemperature();
if (temperature != -999.99) {
// Print the temperature reading
Serial.print("Temperature (Celsius): ");
Serial.println(temperature);
} else {
// Error reading temperature data
Serial.println("Error reading temperature data.");
}
delay(1000); // Delay for stability
}
In this code:
- We define the DS18B20TemperatureSensor class to represent the DS18B20 temperature sensor. The class includes a constructor that initializes the OneWire and DallasTemperature libraries for communication with the sensor. You can optionally set the sensor's resolution using the setResolution method.
- The readTemperature method requests the temperature reading from the sensor and returns the temperature in degrees Celsius. If there is an error reading the data, it returns a sentinel value (-999.99) to indicate an error.
- In the setup function, we initialize serial communication for displaying temperature readings on the serial monitor.
- In the loop function, we continuously read the temperature from the DS18B20 sensor using the readTemperature method and print it to the serial monitor. If there is an error reading the data, a message is printed to indicate the error.
Make sure to replace ds18b20Pin with the digital pin where your DS18B20 sensor is connected. Additionally, you may need to replace sensorAddress with the actual address of your sensor if you have multiple sensors connected to the same pin. Adjust the code according to your specific sensor's setup and requirements.
Object-Oriented Programming code for the Arduino reading DS18B20 One-wire Temperature Sensor with separate Header and CPP files
DS18B20TemperatureSensor.h (Header File)
#ifndef DS18B20TemperatureSensor_h
#define DS18B20TemperatureSensor_h
#include <OneWire.h>
#include <DallasTemperature.h>
class DS18B20TemperatureSensor {
private:
OneWire oneWire;
DallasTemperature sensors;
DeviceAddress sensorAddress;
public:
DS18B20TemperatureSensor(int pin);
float readTemperature();
};
#endif
DS18B20TemperatureSensor.cpp (Implementation File)
#include "PotentiometerReader.h"
#include "DS18B20TemperatureSensor.h"
DS18B20TemperatureSensor::DS18B20TemperatureSensor(int pin) : oneWire(pin), sensors(&oneWire) {
sensors.begin();
sensors.getAddress(sensorAddress, 0);
}
float DS18B20TemperatureSensor::readTemperature() {
sensors.requestTemperaturesByAddress(sensorAddress);
float temperature = sensors.getTempC(sensorAddress);
if (temperature != DEVICE_DISCONNECTED_C) {
return temperature;
} else {
return -999.99;
}
}
YourMainSketch.ino (Main Sketch)
#include <OneWire.h>
#include <DallasTemperature.h>
#include "DS18B20TemperatureSensor.h" // Include the header file
const int ds18b20Pin = 4;
DS18B20TemperatureSensor tempSensor(ds18b20Pin);
void setup() {
Serial.begin(9600);
}
void loop() {
float temperature = tempSensor.readTemperature();
if (temperature != -999.99) {
Serial.print("Temperature (Celsius): ");
Serial.println(temperature);
} else {
Serial.println("Error reading temperature data.");
}
delay(1000);
}
Object-Oriented Programming code for Ultrasonic Distance 4-pin Sensor
An ultrasonic distance sensor measures the water level in the aquarium. The water level becomes lower as water evaporates from the aquarium.
Teh ultrasonic distance sensor was purchased from Core Electronics HC-SR04 Ultrasonic Module Distance Measuring Sensor
To create object-oriented Arduino C++ code for an ultrasonic distance sensor with 4 pins (VCC, GND, Trigger, Echo), you can define a class that encapsulates the functionality of the sensor. Here's an example of object-oriented code for interfacing with a 4-pin ultrasonic distance sensor:
// Define the UltrasonicSensor class
class UltrasonicSensor {
private:
int _triggerPin;
int _echoPin;
public:
UltrasonicSensor(int trigPin, int echPin) {
_triggerPin = trigPin;
_echoPin = echPin;
pinMode(_triggerPin, OUTPUT);
pinMode(_echoPin, INPUT);
}
float getDistance() {
digitalWrite(_triggerPin, LOW);
delayMicroseconds(2);
digitalWrite(_triggerPin, HIGH);
delayMicroseconds(10);
digitalWrite(_triggerPin, LOW);
long duration = pulseIn(echoPin, HIGH);
// Speed of sound at sea level at 20°C is approximately 343 m/s
// Divide by 2 for round trip, and convert to centimeters
float distance = (duration * 0.0343) / 2;
return distance;
}
};
// Define the digital pins for the ultrasonic sensor
const int triggerPin = 7; // Connect to the trigger pin
const int echoPin = 8; // Connect to the echo pin
// Create an instance of the UltrasonicSensor class
UltrasonicSensor distanceSensor(triggerPin, echoPin);
void setup() {
Serial.begin(9600); // Initialize serial communication
}
void loop() {
// Read distance from the ultrasonic sensor
float distance = distanceSensor.getDistance();
// Print the distance reading
Serial.print("Distance (cm): ");
Serial.println(distance);
delay(1000); // Delay for stability
}
In this code:
- We define the UltrasonicSensor class to represent the 4-pin ultrasonic distance sensor. The class includes a constructor that initializes the trigger and echo pins as specified. The getDistance method sends a trigger pulse, measures the time it takes for the echo pulse to return, and calculates the distance in centimeters based on the speed of sound.
- In the setup function, we initialize serial communication for displaying distance readings on the serial monitor.
- In the loop function, we continuously read the distance from the ultrasonic sensor using the getDistance method and print it to the serial monitor.
Make sure to connect the VCC and GND pins of the ultrasonic sensor to the appropriate power and ground sources. Also, adjust the triggerPin and echoPin values according to the digital pins you have connected the sensor to.
All Aquarium Sensors - water quality, temperature, water level
#include <OneWire.h>
#include <DallasTemperature.h>
// Data wire is conntec to the Arduino digital pin 4
#define ONE_WIRE_BUS 4
// Define the UltrasonicSensor class
class UltrasonicSensor {
private:
int _triggerPin;
int _echoPin;
public:
UltrasonicSensor(int trigPin, int echPin) {
_triggerPin = trigPin;
_echoPin = echPin;
pinMode(_triggerPin, OUTPUT);
pinMode(_echoPin, INPUT);
}
float getDistance() {
digitalWrite(_triggerPin, LOW);
delayMicroseconds(2);
digitalWrite(_triggerPin, HIGH);
delayMicroseconds(10);
digitalWrite(_triggerPin, LOW);
long duration = pulseIn(_echoPin, HIGH);
// Speed of sound at sea level at 20°C is approximately 343 m/s
// Divide by 2 for round trip, and convert to centimeters
float distance = (duration * 0.0343) / 2;
if (distance >= 100 || distance <= 2) {
distance = 0;
}
return distance;
}
};
// Define the PotentiometerReader class
class PotentiometerReader {
private:
int analogPin;
float minValue;
float maxValue;
public:
// Constructor: Initialize the PotentiometerReader with an analog pin,
// minimum value, and maximum value
PotentiometerReader(int pin, float minVal, float maxVal) {
analogPin = pin;
minValue = minVal;
maxValue = maxVal;
pinMode(analogPin, INPUT);
}
// Read the potentiometer value and convert it to a range between 4.0 and 9.0
float readValue() {
int rawValue = analogRead(analogPin); // Read analog value (0-1023)
float scaledValue = map(rawValue, 0, 1023, minValue * 100, maxValue * 100) / 100.0; // Map to range
return scaledValue;
}
};
/* Look up chart
Salinity 200-350ppm - A2
pH 6.0 - 8.0 - A1
Phosphate 0.0-3.0 - A0
Ammonium 0.0-1.0 - A5
Nitrite 0.0-1.0 - A4
Nitrate 0.0-2.5 - A3
*/
// Create an instance of the PotentiometerReader class
PotentiometerReader phosphate(A0, 0.0, 3.0);
PotentiometerReader pH(A1, 5.0, 9.0); // Connect potentiometer to analog pin A1
PotentiometerReader salinity(A2, 100, 450);
PotentiometerReader ammonium(A5, 0.0, 1.3);
PotentiometerReader nitrite(A4, 0.0, 1.3);
PotentiometerReader nitrate(A3, 0.0, 35);
// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);
// Pass our oneWire reference to Dallas Temperature sensor
DallasTemperature tempSensor(&oneWire);
// Define the digital pins for the ultrasonic sensor
const int triggerPin = 7; // Connect to the trigger pin
const int echoPin = 8; // Connect to the echo pin
// Create an instance of the UltrasonicSensor class
UltrasonicSensor distanceSensor(triggerPin, echoPin);
void setup() {
Serial.begin(9600); // Initialize serial communication
}
void loop() {
// Read and print the potentiometer value
float salinityValue = salinity.readValue();
Serial.print("Salinity: " + String(salinityValue, 0));
float pHValue = pH.readValue();
Serial.print(" - pH: " + String(pHValue, 2)); // Print with 2 decimal places
float phosphateValue = phosphate.readValue();
Serial.print(" - Phosphate: " + String(phosphateValue, 2)); // Print with 2 decimal places
float ammoniumValue = ammonium.readValue();
Serial.print(" - Ammonium: " + String(ammoniumValue, 2)); // Print with 2 decimal places
float nitriteValue = nitrite.readValue();
Serial.print(" - Nitrite: " + String(nitriteValue, 2)); // Print with 2 decimal places
float nitrateValue = nitrate.readValue();
Serial.print(" - Nitrate: " + String(nitrateValue, 1)); // Print with 1 decimal places
// Call sensors.requestTemperatures() to issue a global temperature and Requests to all devices on the bus
tempSensor.requestTemperatures();
float temperatureValue = tempSensor.getTempCByIndex(0);
Serial.print(" - Temperature: " + String(temperatureValue, 1)); // Print with 1 decimal places
// Water level - read distance from ultrasonic sensor
float distanceValue = distanceSensor.getDistance();
Serial.println(" - Distance: " + String(distanceValue, 2)); // Print with 2 decimal places
delay(500); // Delay for stability
}
Positioning of Classes within Arduino Code
In Arduino code, it's a common practice to define classes at the beginning of the code body. Placing class definitions near the top of your Arduino sketch before the setup and loop functions is a good practice for several reasons:
- Readability: Placing class definitions at the top of the code makes it clear what classes are available and how they are structured, which improves the code's readability and organization.
- Scope: Classes and their methods should be defined before you use them in the setup and loop functions. If you define classes at the end of your code, you might encounter issues with function prototypes and order of definition.
- Easy Reference: Having class definitions at the beginning of your code allows you to easily reference and understand the structure of your program, even before you reach the setup and loop functions.
Here's a typical structure for an Arduino sketch:
// Include any necessary libraries
// Define global constants and variables
// Define classes (if used)
void setup() {
// Initialization code
}
void loop() {
// Main program logic
}
Sending Arduino Data to Raspberry Pi
#include <OneWire.h>
#include <DallasTemperature.h>
// Data wire is conntec to the Arduino digital pin 4
#define ONE_WIRE_BUS 4
// Define the UltrasonicSensor class
class UltrasonicSensor {
private:
int _triggerPin;
int _echoPin;
public:
UltrasonicSensor(int trigPin, int echPin) {
_triggerPin = trigPin;
_echoPin = echPin;
pinMode(_triggerPin, OUTPUT);
pinMode(_echoPin, INPUT);
}
float getDistance() {
digitalWrite(_triggerPin, LOW);
delayMicroseconds(2);
digitalWrite(_triggerPin, HIGH);
delayMicroseconds(10);
digitalWrite(_triggerPin, LOW);
long duration = pulseIn(_echoPin, HIGH);
// Speed of sound at sea level at 20°C is approximately 343 m/s
// Divide by 2 for round trip, and convert to centimeters
float distance = (duration * 0.0343) / 2;
if (distance >= 100 || distance <= 2) {
distance = 0;
}
return distance;
}
};
// Define the PotentiometerReader class
class PotentiometerReader {
private:
int analogPin;
float minValue;
float maxValue;
public:
// Constructor: Initialize the PotentiometerReader with an analog pin,
// minimum value, and maximum value
PotentiometerReader(int pin, float minVal, float maxVal) {
analogPin = pin;
minValue = minVal;
maxValue = maxVal;
pinMode(analogPin, INPUT);
}
// Read the potentiometer value and convert it to a range between 4.0 and 9.0
float readValue() {
int rawValue = analogRead(analogPin); // Read analog value (0-1023)
float scaledValue = map(rawValue, 0, 1023, minValue * 100, maxValue * 100) / 100.0; // Map to range
return scaledValue;
}
};
/* Look up chart
Salinity 200-350ppm - A2
pH 6.0 - 8.0 - A1
Phosphate 0.0-3.0 - A0
Ammonium 0.0-1.0 - A5
Nitrite 0.0-1.0 - A4
Nitrate 0.0-2.5 - A3
*/
// Create an instance of the PotentiometerReader class
PotentiometerReader phosphate(A0, 0.0, 3.0);
PotentiometerReader pH(A1, 5.0, 9.0); // Connect potentiometer to analog pin A1
PotentiometerReader salinity(A2, 100, 450);
PotentiometerReader ammonium(A5, 0.0, 1.3);
PotentiometerReader nitrite(A4, 0.0, 1.3);
PotentiometerReader nitrate(A3, 0.0, 35);
// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);
// Pass our oneWire reference to Dallas Temperature sensor
DallasTemperature tempSensor(&oneWire);
// Define the digital pins for the ultrasonic sensor
const int triggerPin = 7; // Connect to the trigger pin
const int echoPin = 8; // Connect to the echo pin
// Create an instance of the UltrasonicSensor class
UltrasonicSensor distanceSensor(triggerPin, echoPin);
void setup() {
Serial.begin(9600); // Initialize serial communication
}
void loop() {
// Read and print the potentiometer value
float salinityValue = salinity.readValue();
//Serial.print("Salinity: " + String(salinityValue, 0));
float pHValue = pH.readValue();
//Serial.print(" - pH: " + String(pHValue, 2)); // Print with 2 decimal places
float phosphateValue = phosphate.readValue();
//Serial.print(" - Phosphate: " + String(phosphateValue, 2)); // Print with 2 decimal places
float ammoniumValue = ammonium.readValue();
//Serial.print(" - Ammonium: " + String(ammoniumValue, 2)); // Print with 2 decimal places
float nitriteValue = nitrite.readValue();
//Serial.print(" - Nitrite: " + String(nitriteValue, 2)); // Print with 2 decimal places
float nitrateValue = nitrate.readValue();
//Serial.print(" - Nitrate: " + String(nitrateValue, 1)); // Print with 1 decimal places
// Call sensors.requestTemperatures() to issue a global temperature and Requests to all devices on the bus
tempSensor.requestTemperatures();
float temperatureValue = tempSensor.getTempCByIndex(0);
//Serial.print(" - Temperature: " + String(temperatureValue, 1)); // Print with 1 decimal places
// Water level - read distance from ultrasonic sensor
float distanceValue = distanceSensor.getDistance();
//Serial.println(" - Distance: " + String(distanceValue, 2)); // Print with 2 decimal places
Serial.println(String(salinityValue, 0) + "," + String(pHValue, 2) + "," + String(phosphateValue, 2) + "," + \
String(ammoniumValue, 2) + "," + String(nitriteValue, 2) + "," + String(nitrateValue, 1) + "," + \
String(temperatureValue, 1) + "," + String(distanceValue, 2));
delay(1000); // Delay for stability
}
Object-Oriented Programming code for the Arduino reading Ultrasonic Distance 4-pin Sensor with separate Header and CPP files
UltrasonicSensor.h (Header File)
#ifndef UltrasonicSensor_h
#define UltrasonicSensor_h
#include <Arduino.h>
class UltrasonicSensor {
private:
int triggerPin;
int echoPin;
public:
UltrasonicSensor(int trigPin, int echPin);
float getDistance();
};
#endif
UltrasonicSensor.cpp (Implementation File)
#include "UltrasonicSensor.h"
UltrasonicSensor::UltrasonicSensor(int trigPin, int echPin) {
triggerPin = trigPin;
echoPin = echPin;
pinMode(triggerPin, OUTPUT);
pinMode(echoPin, INPUT);
}
float UltrasonicSensor::getDistance() {
digitalWrite(triggerPin, LOW);
delayMicroseconds(2);
digitalWrite(triggerPin, HIGH);
delayMicroseconds(10);
digitalWrite(triggerPin, LOW);
long duration = pulseIn(echoPin, HIGH);
// Speed of sound at sea level at 20°C is approximately 343 m/s
// Divide by 2 for round trip, and convert to centimeters
float distance = (duration * 0.0343) / 2;
return distance;
}
UltrasonicSensorDemo.ino (Main Sketch File)
#include "UltrasonicSensor.h"
// Define the digital pins for the ultrasonic sensor
const int triggerPin = 7; // Connect to the trigger pin
const int echoPin = 8; // Connect to the echo pin
// Create an instance of the UltrasonicSensor class
UltrasonicSensor distanceSensor(triggerPin, echoPin);
void setup() {
Serial.begin(9600); // Initialize serial communication
}
void loop() {
// Read distance from the ultrasonic sensor
float distance = distanceSensor.getDistance();
// Print the distance reading
Serial.print("Distance (cm): ");
Serial.println(distance);
delay(1000); // Delay for stability
}