Introduction: Control Cybot From Web Browser

About: Retired due to health. Oldish. My background is in Structural Engineering. Also smith of many trades. The majority of my project will be what can be made sat in a chair within arm's reach, on a plotter, 3D pri…

Control Cybot from any device that has Wi-Fi and a Web Browser.

I wanted to give Cybot Sight, that is, add a camera to Cybot.

To do this, I decided to use an ESP32-CAM module.

This modification has been done on a Cybot that has had the Power Upgrade.

The skill set required (Some assumptions)

  • Cybot: you have some knowledge of Cybot. (Back in 2001 Eaglemoss started the release of a fortnightly magazine called Ultimate Real Robots, that came with parts to build your own robot.)
  • 3D printer: You know how to use it.
  • Circuit Board: You have some basic soldering skills.
  • Arduino: You have some experience of making and uploading Sketches. I have put links to help in the Instruction.

Supplies

As this is an addition/modification to Cybot, you will need Cybot built up to issue 59.

I have made some mounts for the camera board.

  • I have attached all the STL Files needed.
  • Those identified with _R will need a mirrored version also making.

Purchased parts needed are as follows:

Step 1: How It Works

To control Cybot with the ESP32-CAM module requires tapping into the Two-Wire Communication Bus.

The Two-Wire protocol used by Cybot is very similar to the I2C standard, but not quite the same.

So to use a modified I2C protocol to talk to Cybot, I need to use an Arduino NANO to translate the ESP32-CAM I2C protocol commands to the Cybot I2C protocol commands.

This means that all the original hardware on Cybot can be used and controlled from a web browser.

Step 2: Perfboard

To mount all the components for the Camera Board, I used a cheap PY-5CM*7CM perforated board.

  • I have used a mixture of tined wire and insulated wire to make the connections.
  • Best to use the Fitzing and connect as you prefer.
  • Note, all headers are female type except for the power connection.

Step 3: Logic Level Shifter

As the ESP32-CAM is 3.3v and the Arduino NANO and Cybot are 5v.

A logic level shifter is needed.

The Fritzing shows where it is fitted, be sure to have the orientation correct.

  • The LOW level side is towards the ESP32-CAM
  • The HIGH level side is towards the Arduino NANO

Step 4: ESP32-CAM Power

The ESP32-CAM has a 5v input, this goes to a 3.3v voltage regulator, which can handle more than 5v input. This input is connected to the 6v side of the battery supply of Cybot under the new power board.

The 6v side of the power supply is for the motors, but by the time it gets to the motors its about 4v.

So I have connected the ESP32-CAM here before any voltage drop at the battery supply because the camera and Wi-Fi draws quite a bit of power. If the power drops it can create a brownout.

Step 5: Arduino NANO Power

The power for the Arduino NANO is taken from the header that stacks through all the circuit boards.

The pins on the header are:

  1. Ground.
  2. 6 volt supply. (I haven't used this due to voltage drop)
  3. 5 volt supply.
  4. SCL. Two-Wire Clock.
  5. SDA. Two-Wire Data.
  6. Reset for Drive Microcontroller.

Made a cable to go from headers to the Camera Board.

  • Be sure to connect the cable the correct way round, see the Fritzing for details.

Step 6: ESP32-CAM and ESP32-CAM-MB

The main module for this is an ESP32-CAM Module.

You can now get an ESP32-CAM-MB (Mother Board) to easaly program this module.

Take a look at what you are buying when getting Mother Board, one of the above MB has only one switch, or the ESP32-CAM for that matter, there are several manufacturers of these Modules.

Check what camera comes with the module, the cameras orientation cannot be changed with firmware, it is fixed. I have done a blog on how to change the cameras orientation if needed.

About the Motherboards, I have found that I can program and run the Serial Monitor with the ESP32-CAM Modules that came with the Motherboards, the older ESP32-CAM Modules that did not come with the Motherboards, I can only program, I cannot see the Serial from the ESP32-CAM Modules and the program does not run until I remove the ESP32-CAM Module from the Motherboard.

Step 7: FTDI Programmer

Before the release of the Mother Board, you needed an FTDI programmer to upload your code.

Before I continue I want to talk about power to the ESP32-CAM, when the Camera is on it needs a lot of power. Depending on which FTDI Programmer you have to program the ESP32-CAM, you may need to use a separate Power supply for the ESP32-CAM, this ensures no brownouts after programming.

The Programmer I have has a jumper to switch the VCC between 3.3v and 5v, this does not change the voltage on the Data Pins, these remain at 3.3v.

On this Programmer the 3.3v comes from the FTDI Chip and does not have enough current to run the Camera.

So the jumper needs to be on 5v, this comes from the USB so the VCC is connected to the 5v on the ESP32-CAM.

The new Motherboard supplies USB 5v to the 5v Pin..

To program the ESP32-CAM, GPIO Pin IO0 needs to be connected to GND when Reset.

Some Motherboards have a switch for this.

I have done a small Modification to make it easy for those that don't have the switch.

I have soldered a small SMD Switch across the two pins.

I used to just put a jumper on these two pins when using the FTDI Programmer, but that cant be done with the Motherboard with only one switch. Without the modification you have to unplug the USB and plug it back in.

Step 8: Code

I have done code for the ESP32-CAM and Arduino NANO.

They are all in this file: Pub_Cybot_ESP32CAM_Control.zip

NANO

There are three versions of HEX file for the NANO

  • No bootloader - Cybot_NANO_I2C.ino.eightanaloginputs.hex
  • Old bootloader - Cybot_NANO_I2C.ino.with_old_bootloader.eightanaloginputs.hex
  • New bootloader - Cybot_NANO_I2C.ino.with_bootloader.eightanaloginputs.hex

I have added XLoader to install your choice.

ESP32-CAM

This requires the Arduino IDE to install the firmware.

  1. The Arduino Sketch has some options for how you want to Connect with Wi-Fi and which Camera Module you use.
  2. Read the comments inside the code to set the correct choices.
  3. I have put lots of comments in the code so you can see what does what.
/*
	ESP32-CAM R.O.V.
	By: Tim Jacson.1960

	Give credit where it is due.
	Credits:
		2015-2016 Espressif Systems (Shanghai) PTE LTD
		Arduino

	Use Arduino "Board Manager" to install files for the ESP32.
	If Module: ESP32-CAM Cheap China Clone.(no brand) Probably clone of AI Thinker ESP32-CAM.

			----------------------
			-         SD         -
		5v	-                    -	3.3v
		GND	-                    -	GPIO 16
	Motor	GPIO 12	-                    - GPIO 0	Cam clk on AI Thinker
	Motor	GPIO 13	-                    - GND
	Motor	GPIO 1  -         cam        - 3.3/5v
	Motor	GPIO 14	-                    - GPIO 3	Serial
	Servo	GPIO 2	-                    - GPIO 1	Serial
	LED	GPIO 4	-                    -	GND
			-                    -
			-                    -
			-       GPIO 4 - LED -
			-                    -
			----------------------

	Arduino IDE Settings: (May be differen if you have a branded Module)
	Tools	->	Board	->	ESP32 Arduino	->	AI Thinker ESP32-CAM
	Tools	->	Port	->	user choice. (Use device manager to find correct COM-Port)

	Header "Index_Page.h" is the Index_Page.html inserted into a char array as rawliteral.

	I assume you have learned to program the ESP32-CAM for instructions on the internet.

	You will need to set your WiFi's ssid and password in the code if using your LOCAL_NETWORK.
*/
#include <WiFi.h>
#include <Wire.h>
#include "esp_camera.h"
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "fb_gfx.h"
#include "soc/soc.h"             // disable brownout problems
#include "soc/rtc_cntl_reg.h"    // disable brownout problems
#include "esp_http_server.h"
#include "IndexHTML.h"
/*
	Type of connection?
	"Access Point" or "Local Network"

		"Access Point"
		This is not connected to your local network, you connect to the Network of the ESP32-CAM.
		This means you can connect to it with your Moble Phone anywhare you are.
		You change the WiFi your Phone is connected to, to the ESP32-CAM WiFi.
		Then open browser to the IP of the ESP32-CAM control page. (may be 192.168.4.1)
		Have your serial monitor connected when you re-set the module to confirm correct IP Address.

		"Access Point Pasword"
		This is same as above, bvut you need a password to connect to the WiFi. (it currently is: 2468)

		"Local Network"
		This is, both you and the ESP32-CAM is connected to your local network.
		You will need to give the ssid (name of your network) and the password required to gain access to your network.
		You can connect to the ESP32-CAM from any devices borwser that is connected to your network.
		Open a browser to the IP of the ESP32-CAM control page. (may be 192.168.0.46)
		Have your serial monitor connected when you re-set the module to confirm correct IP Address. (Using a local Network, it may be alocated a different IP)

		Un-Comment which one of the three you want to connect.
*/
#define ACCESS_POINT
//#define ACCESS_POINT_PW
//#define LOCAL_NETWORK

/*
	Replace with your network credentials
*/
const char* ssid = "Your network name here";	// You need to change to yours if LOCAL_NETWORK is used.
const char* password = "Your network password";	// You need to change to yours if LOCAL_NETWORK is used.

/*
	The name of network of the ESP32-CAM
	The Password if used
*/
const char* ESP32_CAMssid = "Cybot ESP32-CAM Control";	//	ESP32 Server name if used.
const char* ESP32_CAMpassword = "2468";					//	ESP32 Server password if used.

#define PART_BOUNDARY "123456789000000000000987654321"
/*
	Chose the model of your ESP32-CAM module.
	Un-Comment the one you have. (If you have a branded Module)
	If you have a cheep Cinese Clone, try the CAMERA_MODEL_AI_THINKER
*/
#define CAMERA_MODEL_AI_THINKER
//#define CAMERA_MODEL_M5STACK_PSRAM
//#define CAMERA_MODEL_M5STACK_WITHOUT_PSRAM
//#define CAMERA_MODEL_M5STACK_PSRAM_B
//#define CAMERA_MODEL_WROVER_KIT

#if defined(CAMERA_MODEL_AI_THINKER)
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

#elif defined(CAMERA_MODEL_WROVER_KIT)
#define PWDN_GPIO_NUM    -1
#define RESET_GPIO_NUM   -1
#define XCLK_GPIO_NUM    21
#define SIOD_GPIO_NUM    26
#define SIOC_GPIO_NUM    27
#define Y9_GPIO_NUM      35
#define Y8_GPIO_NUM      34
#define Y7_GPIO_NUM      39
#define Y6_GPIO_NUM      36
#define Y5_GPIO_NUM      19
#define Y4_GPIO_NUM      18
#define Y3_GPIO_NUM       5
#define Y2_GPIO_NUM       4
#define VSYNC_GPIO_NUM   25
#define HREF_GPIO_NUM    23
#define PCLK_GPIO_NUM    22

#elif defined(CAMERA_MODEL_M5STACK_PSRAM)
#define PWDN_GPIO_NUM     -1
#define RESET_GPIO_NUM    15
#define XCLK_GPIO_NUM     27
#define SIOD_GPIO_NUM     25
#define SIOC_GPIO_NUM     23
#define Y9_GPIO_NUM       19
#define Y8_GPIO_NUM       36
#define Y7_GPIO_NUM       18
#define Y6_GPIO_NUM       39
#define Y5_GPIO_NUM        5
#define Y4_GPIO_NUM       34
#define Y3_GPIO_NUM       35
#define Y2_GPIO_NUM       32
#define VSYNC_GPIO_NUM    22
#define HREF_GPIO_NUM     26
#define PCLK_GPIO_NUM     21

#elif defined(CAMERA_MODEL_M5STACK_WITHOUT_PSRAM)
#define PWDN_GPIO_NUM     -1
#define RESET_GPIO_NUM    15
#define XCLK_GPIO_NUM     27
#define SIOD_GPIO_NUM     25
#define SIOC_GPIO_NUM     23
#define Y9_GPIO_NUM       19
#define Y8_GPIO_NUM       36
#define Y7_GPIO_NUM       18
#define Y6_GPIO_NUM       39
#define Y5_GPIO_NUM        5
#define Y4_GPIO_NUM       34
#define Y3_GPIO_NUM       35
#define Y2_GPIO_NUM       17
#define VSYNC_GPIO_NUM    22
#define HREF_GPIO_NUM     26
#define PCLK_GPIO_NUM     21

#elif defined(CAMERA_MODEL_M5STACK_PSRAM_B)
#define PWDN_GPIO_NUM     -1
#define RESET_GPIO_NUM    15
#define XCLK_GPIO_NUM     27
#define SIOD_GPIO_NUM     22
#define SIOC_GPIO_NUM     23
#define Y9_GPIO_NUM       19
#define Y8_GPIO_NUM       36
#define Y7_GPIO_NUM       18
#define Y6_GPIO_NUM       39
#define Y5_GPIO_NUM        5
#define Y4_GPIO_NUM       34
#define Y3_GPIO_NUM       35
#define Y2_GPIO_NUM       32
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     26
#define PCLK_GPIO_NUM     21

#else
#error "Camera model not selected"
#endif

/*
	Module Pins.
	I have used the same Pin Values for the PWM Channels.
	(Doing this will only work for the first 16 pins, there is only 16 PWM channels)

	Pin 0 used by Camera.
	Pins 1 and 3 used for serial.
*/
#define LED_PIN			4
/*
  Wire I2C
  Arduino NANO address on Cybot will be 100 = 0x64
*/
#define SDA_PIN			16
#define SCL_PIN			13
#define WIRE_BUF		32		// What is the longest message Arduino can store?
#define CYBOT_NANNO		0x64	// 100 Arduino NANO address.
#define I2C_FREQ_HZ		10000	// 10000 (low speed mode) 100000 (standard mode) 400000 (fast mode)
String CommandString = "";
char I2C_Buffer[WIRE_BUF];		// i2c message.
unsigned int I2C_Data_Count;	// Size in I2C Buffer.
TwoWire I2C_0 = TwoWire(0);
/*
	Setting for PWM properties
*/
const int pwmFreq = 30000;
const int pwmResolution = 8;
int dutyCycle = 200;

String SliderValue = "0";
String ControlCybot = "OFF";
byte MotorSpeed = 1;
byte LED_Power = 0;
byte ServoAngle = 0;
boolean Toggle = true;

static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";

httpd_handle_t camera_httpd = NULL;
httpd_handle_t stream_httpd = NULL;

static esp_err_t index_handler(httpd_req_t* req) {
	httpd_resp_set_type(req, "text/html");
	httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
	return httpd_resp_send(req, (const char*)Tims_Cybot_ESP32CAM_html, Tims_Cybot_ESP32CAM_html_LEN);
}

static esp_err_t stream_handler(httpd_req_t* req) {
	camera_fb_t* fb = NULL;
	esp_err_t res = ESP_OK;
	size_t _jpg_buf_len = 0;
	uint8_t* _jpg_buf = NULL;
	char* part_buf[64];

	res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
	if (res != ESP_OK) {
		return res;
	}

	while (true) {
		fb = esp_camera_fb_get();
		if (!fb) {
			Serial.println("Camera capture failed");
			res = ESP_FAIL;
		}
		else {
			if (fb->width > 400) {
				if (fb->format != PIXFORMAT_JPEG) {
					bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
					esp_camera_fb_return(fb);
					fb = NULL;
					if (!jpeg_converted) {
						Serial.println("JPEG compression failed");
						res = ESP_FAIL;
					}
				}
				else {
					_jpg_buf_len = fb->len;
					_jpg_buf = fb->buf;
				}
			}
		}
		if (res == ESP_OK) {
			size_t hlen = snprintf((char*)part_buf, 64, _STREAM_PART, _jpg_buf_len);
			res = httpd_resp_send_chunk(req, (const char*)part_buf, hlen);
		}
		if (res == ESP_OK) {
			res = httpd_resp_send_chunk(req, (const char*)_jpg_buf, _jpg_buf_len);
		}
		if (res == ESP_OK) {
			res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
		}
		if (fb) {
			esp_camera_fb_return(fb);
			fb = NULL;
			_jpg_buf = NULL;
		}
		else if (_jpg_buf) {
			free(_jpg_buf);
			_jpg_buf = NULL;
		}
		if (res != ESP_OK) {
			break;
		}
		//Serial.printf("MJPG: %uB\n",(uint32_t)(_jpg_buf_len));
	}
	return res;
}

static esp_err_t cmd_handler(httpd_req_t* req) {
	char* buf;
	size_t buf_len;
	char variable[32] = { 0, };

	buf_len = httpd_req_get_url_query_len(req) + 1;
	if (buf_len > 1) {
		buf = (char*)malloc(buf_len);
		if (!buf) {
			httpd_resp_send_500(req);
			return ESP_FAIL;
		}
		if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
			if (httpd_query_key_value(buf, "drive", variable, sizeof(variable)) == ESP_OK) {
			}
			else if (httpd_query_key_value(buf, "Speed_value", variable, sizeof(variable)) == ESP_OK) {
				SliderValue = variable;
				MotorSpeed = SliderValue.toInt() - 1;
				Send_On_Wire("Speed");
				Serial.print("MotorSpeed: ");
				Serial.println(MotorSpeed);
			}
			else if (httpd_query_key_value(buf, "LED_value", variable, sizeof(variable)) == ESP_OK) {
				SliderValue = variable;
				LED_Power = SliderValue.toInt();
				ledcWrite(LED_PIN, LED_Power);
				Serial.print("LED_Power: ");
				Serial.println(LED_Power);
			}
			else if (httpd_query_key_value(buf, "control", variable, sizeof(variable)) == ESP_OK) {
				ControlCybot = variable;
				Send_On_Wire(ControlCybot);
			}
			else {
				free(buf);
				httpd_resp_send_404(req);
				return ESP_FAIL;
			}
		}
		else {
			free(buf);
			httpd_resp_send_404(req);
			return ESP_FAIL;
		}
		free(buf);
	}
	else {
		httpd_resp_send_404(req);
		return ESP_FAIL;
	}

	sensor_t* s = esp_camera_sensor_get();

	int res = 0;
	/*
		Motor control
	*/
	if (!strcmp(variable, "forward")) {
		Send_On_Wire("Forward");
	}
	else if (!strcmp(variable, "left")) {
		Send_On_Wire("Left");
	}
	else if (!strcmp(variable, "right")) {
		Send_On_Wire("Right");
	}
	else if (!strcmp(variable, "backward")) {
		Send_On_Wire("Backward");
	}
	else if (!strcmp(variable, "stop")) {
		Send_On_Wire("Stop");
	}
	else {
		res = -1;
	}
	if (res) {
		return httpd_resp_send_500(req);
	}

	httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
	return httpd_resp_send(req, NULL, 0);
}

void startCameraServer() {
	httpd_config_t config = HTTPD_DEFAULT_CONFIG();
	config.server_port = 80;

	httpd_uri_t index_uri = {
	  .uri = "/",
	  .method = HTTP_GET,
	  .handler = index_handler,
	  .user_ctx = NULL
	};

	httpd_uri_t cmd_uri = {
	  .uri = "/action",
	  .method = HTTP_GET,
	  .handler = cmd_handler,
	  .user_ctx = NULL
	};
	httpd_uri_t stream_uri = {
	  .uri = "/stream",
	  .method = HTTP_GET,
	  .handler = stream_handler,
	  .user_ctx = NULL
	};
	if (httpd_start(&camera_httpd, &config) == ESP_OK) {
		httpd_register_uri_handler(camera_httpd, &index_uri);
		httpd_register_uri_handler(camera_httpd, &cmd_uri);
	}
	config.server_port += 1;
	config.ctrl_port += 1;
	if (httpd_start(&stream_httpd, &config) == ESP_OK) {
		httpd_register_uri_handler(stream_httpd, &stream_uri);
	}
}

void setup() {
	/*
	  Start Wire I2C
	*/
	//I2C_0.begin(SDA_PIN, SCL_PIN, I2C_FREQ_HZ);
	Wire.begin(SDA_PIN, SCL_PIN, I2C_FREQ_HZ);
	/*
		Set Pin Direction.
	*/
	pinMode(LED_PIN, OUTPUT);
	/*
		Configure LED PWM functionalitites.
		I have used same channel as pin number.
		ledcSetup(Channel, Frequency, Resolution);
	*/
	ledcSetup(LED_PIN, pwmFreq, pwmResolution);
	/*
		Attach the channel to the GPIO to be controlled
		I have used same channel as pin number.
		ledcAttachPin(Pin, pwmChannel);
	*/
	ledcAttachPin(LED_PIN, LED_PIN);
	/*
		LED Off
		To control the LED you alter the dutyclcle of the LED Channel.
		ledcWrite(Channel, dutyCycle);
	*/
	ledcWrite(LED_PIN, 0);
	/*
		Disable brownout detector
	*/
	WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);

	Serial.begin(115200);
	Serial.setDebugOutput(false);

	camera_config_t config;
	config.ledc_channel = LEDC_CHANNEL_0;
	config.ledc_timer = LEDC_TIMER_0;
	config.pin_d0 = Y2_GPIO_NUM;
	config.pin_d1 = Y3_GPIO_NUM;
	config.pin_d2 = Y4_GPIO_NUM;
	config.pin_d3 = Y5_GPIO_NUM;
	config.pin_d4 = Y6_GPIO_NUM;
	config.pin_d5 = Y7_GPIO_NUM;
	config.pin_d6 = Y8_GPIO_NUM;
	config.pin_d7 = Y9_GPIO_NUM;
	config.pin_xclk = XCLK_GPIO_NUM;
	config.pin_pclk = PCLK_GPIO_NUM;
	config.pin_vsync = VSYNC_GPIO_NUM;
	config.pin_href = HREF_GPIO_NUM;
	config.pin_sscb_sda = SIOD_GPIO_NUM;
	config.pin_sscb_scl = SIOC_GPIO_NUM;
	config.pin_pwdn = PWDN_GPIO_NUM;
	config.pin_reset = RESET_GPIO_NUM;
	config.xclk_freq_hz = 20000000;
	config.pixel_format = PIXFORMAT_JPEG;

	/*
		FRAMESIZE_QVGA	(320  x 240)
		FRAMESIZE_CIF	(352  x 288)
		FRAMESIZE_VGA	(640  x 480)
		FRAMESIZE_SVGA	(800  x 600)
		FRAMESIZE_XGA	(1024 x 768)
		FRAMESIZE_SXGA	(1280 x 1024)
		FRAMESIZE_UXGA	(1600 x 1200)
	*/
	if (psramFound()) {
		config.frame_size = FRAMESIZE_VGA;	//	FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA Default = FRAMESIZE_VGA
		config.jpeg_quality = 20;			//	10-63 lower number means higher quality. Default = 10
		config.fb_count = 2;				//	Default = 2
	}
	else {
		config.frame_size = FRAMESIZE_SVGA;	//	FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA default = FRAMESIZE_SVGA
		config.jpeg_quality = 12;			//	Default = 12
		config.fb_count = 1;				//	Default = 1
	}
	/*
		Camera init
	*/
	esp_err_t err = esp_camera_init(&config);
	if (err != ESP_OK) {
		Serial.printf("Camera init failed with error 0x%x", err);
		return;
	}

#ifdef ACCESS_POINT
	/*
		Connect to ESP32-CAM Wi-Fi network with no password
	*/
	Serial.println("Setting AP (Access Point)…");
	WiFi.softAP(ESP32_CAMssid);
	Serial.print("Connect you device to network '");
	Serial.print(ESP32_CAMssid);
	Serial.println("'\r\nThere is no Password.");
	Serial.print("Open browser to ");
	Serial.println(WiFi.softAPIP());

#endif // ACCESS_POINT
#ifdef ACCESS_POINT_PW
	/*
		Connect to ESP32-CAM Wi-Fi network with a password
	*/
	Serial.println("Setting AP (Access Point)…");
	WiFi.softAP(ESP32_CAMssid, ESP32_CAMpassword);
	Serial.print("Connect you device to network '");
	Serial.print(ESP32_CAMssid);
	Serial.println("'");
	Serial.print("Use password '");
	Serial.print(ESP32_CAMpassword);
	Serial.println("'");
	Serial.print("Open browser to ");
	Serial.println(WiFi.softAPIP());

#endif // ACCESS_POINT_PW
#ifdef LOCAL_NETWORK
	/*
		Connect to ESP32-CAM to Local Wi-Fi network
	*/
	WiFi.begin(ssid, password);
	while (WiFi.status() != WL_CONNECTED) {
		delay(500);
		Serial.print(".");
	}
	Serial.println("");
	Serial.println("WiFi connected to Local Network");
	Serial.print("Camera Stream Ready! Go to:  http://");

	Serial.println(WiFi.localIP());

#endif // LOCAL_NETWORK

	/*
		Start server
	*/
	startCameraServer();
}
void loop() {
	delay(1);// Let things happen.
}
/*
	Clears buffer (I2C_Buffer), sets all bytes to zero.
*/
char Clear_I2C_Buffer() {
	for (byte i = 0; i < WIRE_BUF; i++) { I2C_Buffer[i] = 0; }
	I2C_Data_Count = 0;
}
/*
	Going to make the address of my NANO controling cybot = 100.
		First part is a string command (thisCommand), can be any resonable length.
		If a value is need (1 byte, 0-255), this can be added to the end of the string.
		My arduino NANO treats the last byte as a value.
*/
void Send_On_Wire(String thisCommand) {


	Clear_I2C_Buffer();
	Serial.println(thisCommand);
	I2C_Data_Count = thisCommand.length() + 1;
	thisCommand.toCharArray(I2C_Buffer, I2C_Data_Count);
	Wire.beginTransmission(CYBOT_NANNO);
	for (size_t i = 0; i < I2C_Data_Count; i++) {
		Wire.write(I2C_Buffer[i]);
	}
	if (thisCommand == "Speed") { Wire.write(MotorSpeed); }
	Wire.endTransmission();
}


The "IndexHTML.h" file is the Web page that will be shown in the Web Browser.

  • It is compressed using gzip and converted to 8bit to save memory. It needs to be in the same folder as the Sketch.

I assume that if you have an ESP32-CAM you have experimented with it and have been to Arduino.cc site to learn things about it.

If this is your first time using a Device with the Arduino Architecture, then first go here: Arduino IDE 2 Tutorials

  • Here you can download the Arduino IDE and there are tutorials from the very people who created Arduino.
  • The tutorial show how to upload a sketch to a device.
Remote Control Contest

Participated in the
Remote Control Contest