Currently, marine habitats are under threat of pollution that impacts on many human activities and human life. The increasing concern about pollution levels in the oceans and coastal areas has led to various approaches to measuring and reducing marine pollution, in order to achieve sustainable seawater quality. There are several methods for monitoring ocean conditions such as remote sensing by satellite, ocean buoy monitoring. But I want to add a new method using portable vehicles to get data on marine pollution conditions.
Solutionsome of the existing solution methods are static installations such as ocean bouy installations, so that the conditions for other areas cannot be tracked. So with a portable vehicle makes it easy to take measurements in accordance with the direction given to the vehicle. so it can easily track the condition of marine pollution.
PreparationFor portable vehicles, I made a mini hull boat equipped with a waypoint for automatic features like the Autonomous Surface Vehicle (ASV) or manual with remote control to direct the desired location.
I built mini hull boat from 3d Print with specification
- Pixhawk Controller
- ESC
- Brushless Motor
- GPS
- Receiver Controller
- Remote Controller
- Telemetry
For System monitoring, I built 2 devices
Device in Mini Hull
- Turbidity Sensor
- PH Meter and Temperature Sensor
- Seed XIAO RP240
- Lora
Device in Ground Station
- WIO Terminal
- Lora
1.Turbidity Sensor Test
to read turbidity sensor use Analog Pin (A0) in seeed xiao RP2040
int sensorPin = A0;
float volt;
void setup()
{
Serial.begin(9600);
}
void loop()
{
volt = 0;
for(int i=0; i<800; i++)
{
volt += ((float)analogRead(sensorPin)/1023)*5;
}
volt = volt/800;
volt = round_to_dp(volt,2);
Serial.print(volt);
delay(10);
}
float round_to_dp( float in_value, int decimal_place )
{
float multiplier = powf( 10.0f, decimal_place );
in_value = roundf( in_value * multiplier ) / multiplier;
return in_value;
}
Result Turbidity Test
2.PHSensor Test
to read turbidity sensor use Analog Pin (A1) in seeed xiao RP2040
const int analogInPin = A1;
int sensorValue = 0;
unsigned long int avgValue;
float b;
int buf[10],temp;
void setup() {
Serial.begin(9600);
}
void loop() {
for(int i=0;i<10;i++)
{
buf[i]=analogRead(analogInPin);
delay(10);
}
for(int i=0;i<9;i++)
{
for(int j=i+1;j<10;j++)
{
if(buf[i]>buf[j])
{
temp=buf[i];
buf[i]=buf[j];
buf[j]=temp;
}
}
}
avgValue=0;
for(int i=2;i<8;i++)
avgValue+=buf[i];
float pHVol=(float)avgValue*3.3/1024/6;
float phValue = -5.70 * pHVol + 21.34;
Serial.print("PH = ");
Serial.println(phValue);
delay(20);
}
For test PH, I use 3 sample and PH universal indicator paper
Result PH Test
3.WaterproofTemperatureSensor Test
to read temperature sensor use digital Pin (2) in seeed xiao RP2040, and use resistor 4, 7K (data with vcc)
#include <OneWire.h>
#include <DallasTemperature.h>
// Data wire is conntec to the Arduino digital pin 4
#define ONE_WIRE_BUS 2
// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);
// Pass our oneWire reference to Dallas Temperature sensor
DallasTemperature sensors(&oneWire);
void setup(void)
{
// Start serial communication for debugging purposes
Serial.begin(9600);
// Start up the library
sensors.begin();
}
void loop(void){
// Call sensors.requestTemperatures() to issue a global temperature and Requests to all devices on the bus
sensors.requestTemperatures();
Serial.print("Celsius temperature: ");
// Why "byIndex"? You can have more than one IC on the same bus. 0 refers to the first IC on the wire
Serial.print(sensors.getTempCByIndex(0));
Serial.print(" - Fahrenheit temperature: ");
Serial.println(sensors.getTempFByIndex(0));
delay(1000);
}
Result temperature test
4. WIO Terminal Test
Wio Terminal is amazing device at the moment, I interest to use wio terminal in many project, because is easy to make everything.
5. Lora Test P2P
for lora test P2P, I use 2 lora to connect in wio terminal and seeed xiao, every 2 second send 4 data
Test Lora Transmitter
#include <Arduino.h>
#include <SoftwareSerial.h>
#include <Wire.h>
SoftwareSerial e5(9, 10);
int data1=0;
int data2=0;
int data3=0;
int data4=0;
static char recv_buf[512];
static bool is_exist = false;
static int at_send_check_response(char *p_ack, int timeout_ms, char *p_cmd, ...)
{
int ch = 0;
int index = 0;
int startMillis = 0;
va_list args;
memset(recv_buf, 0, sizeof(recv_buf));
va_start(args, p_cmd);
e5.printf(p_cmd, args);
Serial.printf(p_cmd, args);
va_end(args);
delay(200);
startMillis = millis();
if (p_ack == NULL)
{
return 0;
}
do
{
while (e5.available() > 0)
{
ch = e5.read();
recv_buf[index++] = ch;
Serial.print((char)ch);
delay(2);
}
if (strstr(recv_buf, p_ack) != NULL)
{
return 1;
}
} while (millis() - startMillis < timeout_ms);
return 0;
}
static int node_send(uint32_t timeout)
{
static uint16_t count = 0;
int ret = 0;
char data[32];
char cmd[128];
memset(data, 0, sizeof(data));
sprintf(data, "%04X,%04X,%04X,%04X", data1, data2, data3,data4);
sprintf(cmd, "AT+TEST=TXLRPKT,\"5345454544%s\"\r\n", data);
ret = at_send_check_response("TX DONE", 2000, cmd);
if (ret == 1)
{
Serial.print("Sent successfully!\r\n");
}
else
{
Serial.print("Send failed!\r\n");
}
data1++;
data2=data2+5;
data3=data3+10;
data4=data4+20;
return ret;
}
void setup(void)
{
Serial.begin(115200);
// while (!Serial);
e5.begin(9600);
uint16_t error;
char errorMessage[256];
Serial.print("ping pong communication!\r\n");
if (at_send_check_response("+AT: OK", 100, "AT\r\n"))
{
is_exist = true;
at_send_check_response("+MODE: TEST", 1000, "AT+MODE=TEST\r\n");
at_send_check_response("+TEST: RFCFG", 1000, "AT+TEST=RFCFG,866,SF12,125,12,15,14,ON,OFF,OFF\r\n");
delay(200);
}
else
{
is_exist = false;
Serial.print("No E5 module found.\r\n");
}
}
void loop(void)
{
if (is_exist)
{
node_send(2000);
delay(3000);
}
}
Test Lora Receiver
#include <Arduino.h>
#include <TFT_eSPI.h>
#include <Wire.h>
#include <SoftwareSerial.h>
SoftwareSerial e5(0, 1);
static char recv_buf[512];
static bool is_exist = false;
TFT_eSPI tft;
TFT_eSprite spr = TFT_eSprite(&tft); //sprite
static int at_send_check_response(char *p_ack, int timeout_ms, char *p_cmd, ...)
{
int ch = 0;
int index = 0;
int startMillis = 0;
va_list args;
memset(recv_buf, 0, sizeof(recv_buf));
va_start(args, p_cmd);
e5.printf(p_cmd, args);
Serial.printf(p_cmd, args);
va_end(args);
delay(200);
startMillis = millis();
if (p_ack == NULL)
{
return 0;
}
do
{
while (e5.available() > 0)
{
ch = e5.read();
recv_buf[index++] = ch;
Serial.print((char)ch);
delay(2);
}
if (strstr(recv_buf, p_ack) != NULL)
{
return 1;
}
} while (millis() - startMillis < timeout_ms);
return 0;
}
static int recv_prase(void)
{
char ch;
int index = 0;
memset(recv_buf, 0, sizeof(recv_buf));
while (e5.available() > 0)
{
ch = e5.read();
recv_buf[index++] = ch;
Serial.print((char)ch);
delay(2);
}
if (index)
{
char *p_start = NULL;
char data[32] = {
0,
};
int rssi = 0;
int snr = 0;
p_start = strstr(recv_buf, "+TEST: RX \"5345454544");
if (p_start)
{
spr.fillSprite(TFT_BLACK);
p_start = strstr(recv_buf, "5345454544");
if (p_start && (1 == sscanf(p_start, "5345454544%s,", data)))
{
data[16] = 0;
int data1;
int data2;
int data3;
int data4;
char *endptr;
char *endptr1;
char *endptr2;
char *endptr3;
char dataarray1[5] = {data[0], data[1],data[2], data[3]};
char dataarray2[5] = {data[4], data[5], data[6], data[7]};
char dataarray3[5] = {data[8], data[9], data[10], data[11]};
char dataarray4[5] = {data[12], data[13],data[14], data[15]};
data1 = strtol(dataarray1, &endptr, 16);
data2 = strtol(dataarray2, &endptr1, 16);
data3 = strtol(dataarray3, &endptr, 16);
data4 = strtol(dataarray4, &endptr1, 16);
spr.createSprite(55, 30);
spr.setFreeFont(&FreeSansBoldOblique12pt7b);
spr.setTextColor(TFT_WHITE);
spr.drawNumber(data1, 0, 0, 1);
spr.setTextColor(TFT_GREEN);
spr.pushSprite((tft.width() / 2) - 1, 100);
spr.deleteSprite();
spr.createSprite(55, 30);
spr.setFreeFont(&FreeSansBoldOblique12pt7b);
spr.setTextColor(TFT_WHITE);
spr.drawNumber(data2, 0, 0, 1);
spr.pushSprite(((tft.width() / 2) + (tft.width() / 2) / 2), 97);
spr.deleteSprite();
spr.createSprite(55, 30);
spr.setFreeFont(&FreeSansBoldOblique12pt7b);
spr.setTextColor(TFT_WHITE);
spr.drawNumber(data3, 0, 0, 1);
spr.pushSprite((tft.width() / 2) - 1, (tft.height() / 2) + 67);
spr.deleteSprite();
spr.createSprite(55, 30);
spr.setFreeFont(&FreeSansBoldOblique12pt7b);
spr.setTextColor(TFT_WHITE);
spr.drawNumber(data4, 0 , 0, 1);
spr.pushSprite(((tft.width() / 2) + (tft.width() / 2) / 2), (tft.height() / 2) + 67);
spr.deleteSprite();
Serial.print("data received displaying on the wio terminal");
Serial.print("\r\n");
}
p_start = strstr(recv_buf, "RSSI:");
if (p_start && (1 == sscanf(p_start, "RSSI:%d,", &rssi)))
{
String newrssi = String(rssi);
Serial.print(rssi);
Serial.print("\r\n");
}
p_start = strstr(recv_buf, "SNR:");
if (p_start && (1 == sscanf(p_start, "SNR:%d", &snr)))
{
Serial.print(snr);
Serial.print("\r\n");
}
return 1;
}
}
return 0;
}
static int node_recv(uint32_t timeout_ms)
{
at_send_check_response("+TEST: RXLRPKT", 1000, "AT+TEST=RXLRPKT\r\n");
int startMillis = millis();
do
{
if (recv_prase())
{
return 1;
}
} while (millis() - startMillis < timeout_ms);
return 0;
}
void setup() {
Serial.begin(115200);
tft.begin();
tft.setRotation(3);
e5.begin(9600);
Serial.print("Receiver\r\n");
if (at_send_check_response("+AT: OK", 100, "AT\r\n"))
{
is_exist = true;
at_send_check_response("+MODE: TEST", 1000, "AT+MODE=TEST\r\n");
at_send_check_response("+TEST: RFCFG", 1000, "AT+TEST=RFCFG,866,SF12,125,12,15,14,ON,OFF,OFF\r\n");
delay(200);
}
else
{
is_exist = false;
Serial.print("No E5 module found.\r\n");
}
//Head
tft.fillScreen(TFT_BLACK);
tft.setFreeFont(&FreeSansBoldOblique18pt7b);
tft.setTextColor(TFT_WHITE);
tft.drawString("Test Receiver", 40, 10 , 1);
//Line
for (int8_t line_index = 0; line_index < 5 ; line_index++)
{
tft.drawLine(0, 50 + line_index, tft.width(), 50 + line_index, TFT_WHITE);
}
//Additional
tft.drawRoundRect(5, 60, (tft.width() / 2) - 20 , tft.height() - 65 , 10, TFT_WHITE); // L1
tft.setFreeFont(&FreeSansBoldOblique12pt7b);
tft.setTextColor(TFT_RED);
tft.drawString("Condition", 12 , 65 , 1);
tft.setTextColor(TFT_GREEN);
tft.drawString("Normal", 35, 128, 1);
//data1
tft.drawRoundRect((tft.width() / 2) - 10 , 60, (tft.width() / 2) / 2 , (tft.height() - 65) / 2 , 10, TFT_WHITE); // s1
tft.setFreeFont(&FreeSansBoldOblique9pt7b);
tft.setTextColor(TFT_RED) ;
tft.drawString("Data1", (tft.width() / 2) - 1 , 70 , 1); // Print the test text in the custom font
//data2
tft.drawRoundRect(((tft.width() / 2) + (tft.width() / 2) / 2) - 5 , 60, (tft.width() / 2) / 2 , (tft.height() - 65) / 2 , 10, TFT_WHITE); // s2
tft.setFreeFont(&FreeSansBoldOblique9pt7b);
tft.setTextColor(TFT_RED);
tft.drawString("Data2", ((tft.width() / 2) + (tft.width() / 2) / 2) , 70 , 1); // Print the test text in the custom font
//data3
tft.drawRoundRect((tft.width() / 2) - 10 , (tft.height() / 2) + 30, (tft.width() / 2) / 2 , (tft.height() - 65) / 2 , 10, TFT_WHITE); // s3
tft.setFreeFont(&FreeSansBoldOblique9pt7b);
tft.setTextColor(TFT_RED) ;
tft.drawString("Data3", (tft.width() / 2) - 1 , (tft.height() / 2) + 40 , 1); // Print the test text in the custom font
//data4
tft.drawRoundRect(((tft.width() / 2) + (tft.width() / 2) / 2) - 5 , (tft.height() / 2) + 30, (tft.width() / 2) / 2 , (tft.height() - 65) / 2 , 10, TFT_WHITE); // s4
tft.setFreeFont(&FreeSansBoldOblique9pt7b);
tft.setTextColor(TFT_RED) ;
tft.drawString("Data4", ((tft.width() / 2) + (tft.width() / 2) / 2) , (tft.height() / 2) + 40 , 1); // Print the test text in the custom font
}
void loop() {
if (is_exist)
{
node_recv(2000);
}
}
Result p2p
After all sensor and communication P2P working well, next step collecting data sensor. In this project, data collecting is carried out using a data forwarder to edge impulse.
Create Edge Impulse Account
install the following software:
- Node.js v12 or higher.
- Arduino CLI
- The Edge Impulse CLI and a serial monitor. (Install by opening node.js command prompt)
npm install -g edge-impulse-cli
- Install data forwarder (The data forwarder is used to easily relay data from any device to Edge Impulse over serial. Devices write sensor values over a serial connection, and the data forwarder collects the data, signs the data and sends the data to the ingestion service)
edge-impulse-data-forwarder
For collecting data with forwarder data, this link is very helpfully
go to menu Deployment to get library arduino
Open Arduino IDE than import library was build by edge impulse, open example and edit in raw_feature_get_data
Test Edge Impulse with real data water/* Edge Impulse Arduino examples
* Copyright (c) 2021 EdgeImpulse Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/* Includes ---------------------------------------------------------------- */
#include <sekolahrobot-project-1_inferencing.h>
#include <Arduino.h>
#include <SoftwareSerial.h>
#include <Wire.h>
#include <OneWire.h>
#include <DallasTemperature.h>
SoftwareSerial e5(9, 10);
const int analogInPin = A1;
int sensorPin = A0;
float volt;
int sensorValue = 0;
unsigned long int avgValue;
float pHVol;
float phValue;
float b;
int buf[10],temp;
#define ONE_WIRE_BUS 2
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
float tempC;
static const float features[114] = {};
int raw_feature_get_data(size_t offset, size_t length, float *out_ptr) {
float features[114];
readPH();
readTurbidity();
readSuhu();
for (byte i = 0; i < 114; i = i + 3)
{
features[i]=volt;
features[i+1]=phValue;
features[i+2]=tempC;
delay(200);
}
memcpy(out_ptr, features + offset, length * sizeof(float));
return 0;
}
void setup()
{
// put your setup code here, to run once:
Serial.begin(115200);
// comment out the below line to cancel the wait for USB connection (needed for native USB)
while (!Serial);
Serial.println("Edge Impulse Inferencing Demo");
}
/**
* @brief Arduino main function
*/
void loop()
{
ei_printf("Edge Impulse standalone inferencing (Arduino)\n");
if (sizeof(features) / sizeof(float) != EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE) {
ei_printf("The size of your 'features' array is not correct. Expected %lu items, but had %lu\n",
EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, sizeof(features) / sizeof(float));
delay(1000);
return;
}
ei_impulse_result_t result = { 0 };
// the features are stored into flash, and we don't want to load everything into RAM
signal_t features_signal;
features_signal.total_length = sizeof(features) / sizeof(features[0]);
features_signal.get_data = &raw_feature_get_data;
// invoke the impulse
EI_IMPULSE_ERROR res = run_classifier(&features_signal, &result, false /* debug */);
ei_printf("run_classifier returned: %d\n", res);
if (res != 0) return;
// print the predictions
ei_printf("Predictions ");
ei_printf("(DSP: %d ms., Classification: %d ms., Anomaly: %d ms.)",
result.timing.dsp, result.timing.classification, result.timing.anomaly);
ei_printf(": \n");
ei_printf("[");
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
ei_printf("%.5f", result.classification[ix].value);
#if EI_CLASSIFIER_HAS_ANOMALY == 1
ei_printf(", ");
#else
if (ix != EI_CLASSIFIER_LABEL_COUNT - 1) {
ei_printf(", ");
}
#endif
}
#if EI_CLASSIFIER_HAS_ANOMALY == 1
ei_printf("%.3f", result.anomaly);
#endif
ei_printf("]\n");
// human-readable predictions
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
ei_printf(" %s: %.5f\n", result.classification[ix].label, result.classification[ix].value);
}
#if EI_CLASSIFIER_HAS_ANOMALY == 1
ei_printf(" anomaly score: %.3f\n", result.anomaly);
#endif
delay(1000);
}
void readPH()
{
for(int i=0;i<10;i++)
{
buf[i]=analogRead(analogInPin);
delay(10);
}
for(int i=0;i<9;i++)
{
for(int j=i+1;j<10;j++)
{
if(buf[i]>buf[j])
{
temp=buf[i];
buf[i]=buf[j];
buf[j]=temp;
}
}
}
avgValue=0;
for(int i=2;i<8;i++)
avgValue+=buf[i];
pHVol=(float)avgValue*3.3/1024/6;
phValue = -5.70 * pHVol + 21.34;
//Serial.print("PH = ");
//Serial.println(phValue);
}
void readTurbidity()
{
volt = 0;
for(int i=0; i<800; i++)
{
volt += ((float)analogRead(sensorPin)/1023)*5;
}
volt = volt/800;
volt = round_to_dp(volt,2);
//Serial.print("Turbidity : ");
//Serial.println(volt);
}
void readSuhu()
{
sensors.requestTemperatures();
tempC = sensors.getTempCByIndex(0);
//Serial.print("Celsius temperature: ");
//Serial.println(tempC);
}
float round_to_dp( float in_value, int decimal_place )
{
float multiplier = powf( 10.0f, decimal_place );
in_value = roundf( in_value * multiplier ) / multiplier;
return in_value;
}
Test edge impulse data
next step is implementation in my mini Hull Boat, I attach in the bottom of the ship
For the sea actually I have not tested my project, I just simulated with clean water or dirty water. Next if I have the chance I will go overboard to test my project.
Comments