Nghia Tran
Published

Salt Water Tracker (SWT)

Salt seawater is penetrating severely into inland rice paddies in Vietnam. SWT monitors and helps regulating salt level in water.

AdvancedFull instructions providedOver 6 days741

Things used in this project

Hardware components

Continuous Servo Motor - FS90R
×1
Adafruit BME688 - Temperature, Humidity, Pressure and Gas Sensor
×1
Adafruit RGB Color Sensor with IR filter and White LED - TCS34725
×1
Thermistor Stainless Steel 3950 NTC Temp Sensor Probe
×1
Seeed XIAO BLE nRF52840 Sense
Seeed Studio Seeed XIAO BLE nRF52840 Sense
×2
Rechargeable Battery, 3.7 V
Rechargeable Battery, 3.7 V
×1
Texas Instruments IC OPAMP GP 1 CIRCUIT SC70-6 - LMV341MG
×4
Large Capacity Aluminum Bobbins 25mm x 10.6mm
×1
Nylon String
×1

Software apps and online services

Arduino IDE
Arduino IDE
NXP MCUXpresso Integrated Development Environment (IDE)
Circuit Maker
CircuitMaker by Altium Circuit Maker
Snappy Ubuntu Core
Snappy Ubuntu Core
Windows 10
Microsoft Windows 10

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
PCB fabrication
Wire Stripper & Cutter, 30-20 AWG / 0.25-0.81mm Capacity Wires
Wire Stripper & Cutter, 30-20 AWG / 0.25-0.81mm Capacity Wires

Story

Read more

Schematics

Sensor Module Schematic

Altium Schematic

Code

water_sensor

C/C++
This code runs on FMUK66-E
/****************************************************************************
 *
 *   Copyright (c) 2019 PX4 Development Team. All rights reserved.
 *
 *   This work based on the original work of Cliff Brake
 *   cbrake@bec-systems.com released under the MIT license below.
 *   See https://github.com/cbrake/linux-serial-test
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name PX4 nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

// SPDX-License-Identifier: MIT
/*
 * 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.
 *
 */

/*
 * To use this test
 * 1) Build the https://github.com/cbrake/linux-serial-test on
 *    a linux box.
 *
 * 2) Add serial_test to the DUT board's default.cmake
 * 3) Connect an FTDI cable from the Linux box to the DUT's Telem port.
 *
 * 4) run ./linux-serial-test -e -b 921600 -p /dev/serial/by-id/usb-FTDI_... -c
 * on the linux box (Use control C to exit)
 *
 * 5) Run  serial_test -e -b 921600 -p /dev/ttyS2 -c on DUT
 *    Use ESC or control C to exit'
 *
 * Modified: Nghia Tran
 * Date: 3/25/2023
 * File Name: water_sensor.c
 *
 */

#if defined(__PX4_NUTTX)
#include <px4_platform_common/px4_config.h>
#include <px4_platform_common/getopt.h>
#endif

#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <poll.h>
#include <getopt.h>
#include <time.h>
#if defined(__PX4_NUTTX)
#include <nuttx/serial/serial.h>
#else
#include <linux/serial.h>
#endif
#include <errno.h>

/*
 * glibc for MIPS has its own bits/termios.h which does not define
 * CMSPAR, so we vampirise the value from the generic bits/termios.h
 */
#ifndef CMSPAR
#define CMSPAR 010000000000
#endif

// command line args
struct cli_args_t {
	int          _baud;
	char        *_port;
	int          _divisor;
	int          _rx_dump;
	int          _rx_dump_ascii;
	int          _tx_detailed;
	int          _stats;
	int          _stop_on_error;
	int          _single_byte;
	int          _another_byte;
	int          _rts_cts;
	int          _2_stop_bit;
	int          _parity;
	int          _odd_parity;
	int          _stick_parity;
	int          _dump_err;
	int          _no_rx;
	int          _no_tx;
	int          _rx_delay;
	int          _tx_delay;
	int          _tx_bytes;
	int          _rs485_delay;
	unsigned int _tx_time;
	unsigned int _rx_time;
	int          _ascii_range;
	int			 _command_drop;		// command to drop down the water sensor
	int			 _command_pull;		// command to pull up the water sensor
	int			 _command_read;		// command to read water sensor data
	int			 _command_image;	// command NAVQ image processing
	int			 _command_video;	// command NAVQ video stream
	int			 _command_stop;		// commnad stop all
};

struct g_mod_t {

	unsigned char  _write_count_value;
	unsigned char  _read_count_value;
	int            _fd;
	unsigned char *_write_data;
	ssize_t        _write_size;

	// keep our own counts for cases where the driver stats don't work
	long long int _write_count;
	long long int _read_count;
	long long int _error_count;
};


static void display_help(void)
{
#if defined(__PX4_NUTTX)
	printf("Usage: water_sensor [OPTION]\n"
#else
	printf("Usage: linux-water-sensor [OPTION]\n"
#endif
	       "\n"
	       "  -h, --help\n"
	       "  -d, --command-drop    command to drop down the water sensor\n"
		   "  -p, --command-pull	command to pull up the water sensor\n"
		   "  -r, --command-read	command to read water sensor data\n"
		   "  -i, --command-image	command NAVQ plus to perform image processing\n"
		   "  -v, --command-image	command NAVQ plus to perform image processing\n"
		   "  -s, --command-stop	commnad stop all\n"
		   "example: water_sensor -d\n"
	       "\n"
	      );
}


static void process_options(int argc, char *argv[],  struct cli_args_t *g_cl)
{
#if defined(__PX4_NUTTX)
	int myoptind = 1;
	const char *myoptarg = NULL;
	int ch;

	while ((ch = px4_getopt(argc, argv, "hdpivs", &myoptind, &myoptarg)) != EOF) {
		switch (ch) {
#else
#define myoptarg optarg

	for (;;) {
		int option_index = 0;
		static const char *short_options = "hdpivs";
		static const struct option long_options[] = {
			{"help", no_argument, 0, 0},
			{"d", no_argument, 0, 'd'},
			{"p", no_argument, 0, 'p'},
			{"r", no_argument, 0, 'r'},
			{"i", no_argument, 0, 'i'},
			{"v", no_argument, 0, 'v'},
			{"s", no_argument, 0, 's'},
			{0, 0, 0, 0},
		};

		int c = getopt_long(argc, argv, short_options,
				    long_options, &option_index);

		if (c == EOF) {
			break;
		}

		switch (c) {
#endif

			case 0:
			case 'h':
				display_help();
				exit(0);
				break;

			case 'd':
				g_cl->_command_drop = 1;
				g_cl->_command_pull = 0;
				break;

			case 'p':
				g_cl->_command_drop = 0;
				g_cl->_command_pull = 1;
				break;

			case 'r':
				g_cl->_command_read = 1;
				break;

			case 'i':
				g_cl->_command_image = 1;
				break;

			case 'v':
				g_cl->_command_video = 1;
				break;

			case 's':
				g_cl->_command_stop = 1;
				break;


		}
	}

}


static void setup_serial_port(int baud, struct g_mod_t *g_mod, struct cli_args_t *g_cl)
{
	struct termios newtio;
	struct serial_rs485 rs485;

	if (g_mod->_fd == -1) {
//		g_mod->_fd = open(g_cl->_port, O_RDWR | O_NONBLOCK);
		g_mod->_fd = open("/dev/ttyS1", O_RDWR | O_NONBLOCK);

		if (g_mod->_fd < 0) {
			perror("Error opening serial port");
			free(g_cl->_port);
			exit(1);
		}
	}

	bzero(&newtio, sizeof(newtio)); /* clear struct for new port settings */

#if defined(__PX4_NUTTX)
	tcgetattr(g_mod->_fd, &newtio);
	cfsetspeed(&newtio, baud);
	newtio.c_cflag = CS8 | CLOCAL | CREAD;
#else
	/* man termios get more info on below settings */
	newtio.c_cflag = baud | CS8 | CLOCAL | CREAD;

#endif

	if (g_cl->_rts_cts) {
		newtio.c_cflag |= CRTSCTS;
	}

	if (g_cl->_2_stop_bit) {
		newtio.c_cflag |= CSTOPB;
	}

	if (g_cl->_parity) {
		newtio.c_cflag |= PARENB;

		if (g_cl->_odd_parity) {
			newtio.c_cflag |= PARODD;
		}

		if (g_cl->_stick_parity) {
			newtio.c_cflag |= CMSPAR;
		}
	}

	newtio.c_iflag = 0;
	newtio.c_oflag = 0;
	newtio.c_lflag = 0;

	// block for up till 128 characters
	newtio.c_cc[VMIN] = 128;

	// 0.5 seconds read timeout
	newtio.c_cc[VTIME] = 5;

	/* now clean the modem line and activate the settings for the port */
	tcflush(g_mod->_fd, TCIOFLUSH);
	tcsetattr(g_mod->_fd, TCSANOW, &newtio);

	/* enable/disable rs485 direction control */
	if (ioctl(g_mod->_fd, TIOCGRS485, (int) &rs485) < 0) {
		if (g_cl->_rs485_delay >= 0) {
			/* error could be because hardware is missing rs485 support so only print when actually trying to activate it */
			perror("Error getting RS-485 mode");
		}

	} else if (g_cl->_rs485_delay >= 0) {
		rs485.flags |= SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | SER_RS485_RTS_AFTER_SEND;
		rs485.delay_rts_after_send = g_cl->_rs485_delay;
		rs485.delay_rts_before_send = 0;

		if (ioctl(g_mod->_fd, TIOCSRS485, (int) &rs485) < 0) {
			perror("Error setting RS-485 mode");
		}

	} else {
		rs485.flags &= ~(SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | SER_RS485_RTS_AFTER_SEND);
		rs485.delay_rts_after_send = 0;
		rs485.delay_rts_before_send = 0;

		if (ioctl(g_mod->_fd, TIOCSRS485, (int) &rs485) < 0) {
			perror("Error setting RS-232 mode");
		}
	}
}


//****************************************************************
// Main program
//****************************************************************
#if defined(__PX4_NUTTX)
int water_sensor_main(int argc, char *argv[])
{
	printf("water sensor app\n");
#else
int main(int argc, char *argv[])
{
	printf("Linux serial test app\n");
#endif

char *cmd;
unsigned char rb[1024];

	struct cli_args_t g_cl = {
		._single_byte  = -1,
		._another_byte = -1,
		._rs485_delay  = -1,
		._command_drop = -1,
		._command_pull = -1,
		._command_read = -1,
		._command_image = -1,
		._command_video = -1,
		._command_stop = -1,
	};

	struct g_mod_t g_mod = {
		._fd           = -1,
	};

#if defined(__PX4_NUTTX)
	memset(&g_cl, 0, sizeof(g_cl));
	g_cl._single_byte = -1;
	g_cl._another_byte = -1;
	g_cl._rs485_delay = -1;
	g_cl._command_drop = -1;
	g_cl._command_pull = -1;
	g_cl._command_read = -1;
	g_cl._command_image = -1;
	g_cl._command_video = -1;
	g_cl._command_stop = -1;
	memset(&g_mod, 0, sizeof(g_mod));
	g_mod._fd = -1;
#endif

	process_options(argc, argv, &g_cl);

	if(g_cl._command_drop == 1 && g_cl._command_pull == 0)
		cmd = "$d\n";
	else if(g_cl._command_drop == 0 && g_cl._command_pull == 1)
		cmd = "$p\n";
	else if(g_cl._command_image == 1)
		cmd = "$i\n";
	else if(g_cl._command_video == 1)
		cmd = "$v\n";
	else if(g_cl._command_stop == 1)
		cmd = "$s\n";
	else
		cmd = "$h\n";

	// Set up serial port
	int baud = B921600;
	setup_serial_port(baud, &g_mod, &g_cl);

	// Write to serial port, sending command to sensor module and NAVQ+
	write(g_mod._fd, cmd, strlen(cmd));

	if(g_cl._command_read == 1){

		// Read sensor data from serial port
		int count = read(g_mod._fd, &rb, sizeof(rb));

		// Send sensor data to terminal
		int i;
		for (i = 0; i < count; i++) {
			printf("%c", rb[i]);
		}
	}
	return 0;
}
water_sensor.c
Displaying water_sensor.c.

Credits

Nghia Tran

Nghia Tran

6 projects • 3 followers
Thanks to Hoa Phan.

Comments