Object Oriented Aquarium using the Arduino and Raspberry Pi

From Sensors in Schools
Jump to navigation Jump to search

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.

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.

Object-Oriented Programming code for the Arduino reading a Potentiometer

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 potReader(A0, 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
  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.


Object-Oriented Programming code for the Arduino reading a Potentiometer with separate Header and CPP files

PotentiometerSensor.h (Header File)

#ifndef PotentiometerSensor_h
#define PotentiometerSensor_h

#include <Arduino.h>

class PotentiometerSensor {
public:
  PotentiometerSensor(int pin);
  void setup();
  float readValue();
  float mapToRange(float value, float fromLow, float fromHigh, float toLow, float toHigh);

private:
  int analogPin;
};

#endif

PotentiometerSensor.cpp (Implementation File)

#include "PotentiometerSensor.h"

PotentiometerSensor::PotentiometerSensor(int pin) {
  analogPin = pin;
}

void PotentiometerSensor::setup() {
  // No setup needed for analogRead()
}

float PotentiometerSensor::readValue() {
  int sensorValue = analogRead(analogPin);
  float mappedValue = mapToRange(sensorValue, 0, 1023, 4.0, 9.0);
  return mappedValue;
}

float PotentiometerSensor::mapToRange(float value, float fromLow, float fromHigh, float toLow, float toHigh) {
  return (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow;
}

YourMainSketch.ino (Main Sketch)

#include <Arduino.h>
#include "PotentiometerSensor.h"

PotentiometerSensor potSensor(A0); // Use analog pin A0 for the potentiometer

void setup() {
  Serial.begin(9600);
  potSensor.setup();
}

void loop() {
  float sensorValue = potSensor.readValue();
  Serial.print("Potentiometer Value: ");
  Serial.println(sensorValue, 2); // Display with 2 decimal places

  // Your main sketch code here
}

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 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.


Object-Oriented Programming code for Atlas Scientific Conductivity Sensor