Sandwich IoT
Published © GPL3+

Prototype a Smart Curtain Robot-Tuya Developer

A smart curtain robot can make your current curtains motorized and smart with easy installation.

BeginnerFull instructions provided8 hours357
Prototype a Smart Curtain Robot-Tuya Developer

Things used in this project

Hardware components

Tuya's Bluetooth LE module
×1
Lithium battery
×1
Battery management chip
×1
LDO regulator
×1
Ambient light sensor
×1
Linear accelerometer
×1

Software apps and online services

Sample code in GitHub
https://github.com/tuya/tuya-iotos-embeded-demo-ble-smart-curtain-robot

Story

Read more

Code

Code snippet #5

Plain text
void curtain_percent_control(unsigned char current_position, unsigned char target_position)
{
	unsigned long total_time;

	if ((current_position == target_position) || \
		(current_position < 0) || (current_position > 100) || \
		(target_position < 0) || (target_position > 100)) {
		TUYA_APP_LOG_ERROR("input error");
		*curtain_robot.dp_percent_state.dp_data = current_position;
		return;
	}

	if (curtain_robot.motor_run_mode == RUN_MODE_IDLE) {
		curtain_robot.motor_run_mode = RUN_MODE_PERCENT_CONTROL;
	} else {
		TUYA_APP_LOG_ERROR("motor_run_mode != RUN_MODE_IDLE");
		*curtain_robot.dp_percent_state.dp_data = current_position;
		return;
	}

	total_time = curtain_robot.dp_time_total.dp_data[0];
	total_time = (total_time << 8) | curtain_robot.dp_time_total.dp_data[1];

	if (total_time == 0) {
		*curtain_robot.dp_percent_state.dp_data = current_position;
		return;
	}

	if (current_position < target_position) {
		curtain_robot.run_end_time = ((target_position - current_position) * (total_time * 10)); //*10=*1000/100,ms->us
		curtain_robot.run_start_time = clock_time();
		curtain_close();
	} else {
		curtain_robot.run_end_time = (current_position - target_position) * (total_time * 10);
		curtain_robot.run_start_time = clock_time();
		curtain_open();
	}

	/* percent control state target position */
	percent_target_value = target_position;

	*curtain_robot.dp_percent_control.dp_data = target_position;
	/* percent control */
	dp_update(curtain_robot.dp_percent_control.dp_id,	\
			curtain_robot.dp_percent_control.dp_type,	\
			curtain_robot.dp_percent_control.dp_data,	\
			curtain_robot.dp_percent_control.dp_data_len);
}

void curtain_percent_control_stop_task(void)
{
	if (((curtain_robot.motor_run_mode == RUN_MODE_PERCENT_CONTROL)) && \
		(clock_time_exceed(curtain_robot.run_start_time, curtain_robot.run_end_time))) {
		curtain_pause();
		curtain_robot.motor_run_mode = RUN_MODE_IDLE;

		save_device_data();

		/* percent state */
		*curtain_robot.dp_percent_state.dp_data = percent_target_value;
		dp_update(curtain_robot.dp_percent_state.dp_id,	\
				curtain_robot.dp_percent_state.dp_type,	\
				curtain_robot.dp_percent_state.dp_data,	\
				curtain_robot.dp_percent_state.dp_data_len);

	}
}

Code snippet #6

Plain text
void curtain_percent_control(unsigned char current_position, unsigned char target_position)
{
	unsigned long total_time;

	if ((current_position == target_position) || \
		(current_position < 0) || (current_position > 100) || \
		(target_position < 0) || (target_position > 100)) {
		TUYA_APP_LOG_ERROR("input error");
		*curtain_robot.dp_percent_state.dp_data = current_position;
		return;
	}

	if (curtain_robot.motor_run_mode == RUN_MODE_IDLE) {
		curtain_robot.motor_run_mode = RUN_MODE_PERCENT_CONTROL;
	} else {
		TUYA_APP_LOG_ERROR("motor_run_mode != RUN_MODE_IDLE");
		*curtain_robot.dp_percent_state.dp_data = current_position;
		return;
	}

	total_time = curtain_robot.dp_time_total.dp_data[0];
	total_time = (total_time << 8) | curtain_robot.dp_time_total.dp_data[1];

	if (total_time == 0) {
		*curtain_robot.dp_percent_state.dp_data = current_position;
		return;
	}

	if (current_position < target_position) {
		curtain_robot.run_end_time = ((target_position - current_position) * (total_time * 10)); //*10=*1000/100,ms->us
		curtain_robot.run_start_time = clock_time();
		curtain_close();
	} else {
		curtain_robot.run_end_time = (current_position - target_position) * (total_time * 10);
		curtain_robot.run_start_time = clock_time();
		curtain_open();
	}

	/* percent control state target position */
	percent_target_value = target_position;

	*curtain_robot.dp_percent_control.dp_data = target_position;
	/* percent control */
	dp_update(curtain_robot.dp_percent_control.dp_id,	\
			curtain_robot.dp_percent_control.dp_type,	\
			curtain_robot.dp_percent_control.dp_data,	\
			curtain_robot.dp_percent_control.dp_data_len);
}

void curtain_percent_control_stop_task(void)
{
	if (((curtain_robot.motor_run_mode == RUN_MODE_PERCENT_CONTROL)) && \
		(clock_time_exceed(curtain_robot.run_start_time, curtain_robot.run_end_time))) {
		curtain_pause();
		curtain_robot.motor_run_mode = RUN_MODE_IDLE;

		save_device_data();

		/* percent state */
		*curtain_robot.dp_percent_state.dp_data = percent_target_value;
		dp_update(curtain_robot.dp_percent_state.dp_id,	\
				curtain_robot.dp_percent_state.dp_type,	\
				curtain_robot.dp_percent_state.dp_data,	\
				curtain_robot.dp_percent_state.dp_data_len);

	}
}

Code snippet #7

Plain text
short x_data_buf[100] = {0};
unsigned int clean_x_buf_count = 0;
unsigned char x_data_index = 0;

void auto_power_task(void)
{
	short x_axis_data = 0;
	unsigned char i = 0;
	unsigned char open_count = 0, close_count = 0;

	if (*(curtain_robot.dp_auto_power.dp_data) == TRUE) {
		if (clock_time_exceed(curtain_robot.get_lis2dw12_data_time, 10 * 1000)) {
			curtain_robot.get_lis2dw12_data_time = clock_time();

			if (curtain_robot.motor_run_mode != RUN_MODE_IDLE) {
				return;
			}

			x_axis_data = get_lis2dw12_x_value();

			if (x_axis_data > 100 || x_axis_data < -100) {
				x_data_buf[x_data_index] = x_axis_data;
				x_data_index++;

				clean_x_buf_count = 0;
			} else {
				clean_x_buf_count++;
			}

			if ((clean_x_buf_count > 20) && (x_data_index >= 20)) { //At this point it has levelled off
				for (i=0; i < 7; i++) {
					if (x_data_buf[i] >= 0) {
						open_count++;
					} else {
						close_count++;
					}
				}

				if (*curtain_robot.dp_percent_state.dp_data == 0) {
					curtain_robot.auto_power_state = AUTO_POWER_CLOSE;
				} else if (*curtain_robot.dp_percent_state.dp_data == 100) {
					curtain_robot.auto_power_state = AUTO_POWER_OPEN;
				} else if (open_count < close_count) {
					curtain_robot.auto_power_state = AUTO_POWER_CLOSE;
				} else {
					curtain_robot.auto_power_state = AUTO_POWER_OPEN;
				}

				//clean flag
				clean_x_buf_count= 0;
				x_data_index=0;
			}

			if (curtain_robot.auto_power_state != AUTO_POWER_IDLE && clean_x_buf_count>= 100) {
				if (curtain_robot.auto_power_state == AUTO_POWER_OPEN) {
					curtain_percent_control(*curtain_robot.dp_percent_state.dp_data, 0);
				} else if (curtain_robot.auto_power_state == AUTO_POWER_CLOSE) {
					curtain_percent_control(*curtain_robot.dp_percent_state.dp_data, 100);
				}
				curtain_robot.auto_power_state=AUTO_POWER_IDLE;
				clean_x_buf_count = 0;
			}

			if (clean_x_buf_count > 500) {
				clean_x_buf_count = 0;
				x_data_index=0;
			}
		}
	}
}

Code snippet #8

Plain text
short x_data_buf[100] = {0};
unsigned int clean_x_buf_count = 0;
unsigned char x_data_index = 0;

void auto_power_task(void)
{
	short x_axis_data = 0;
	unsigned char i = 0;
	unsigned char open_count = 0, close_count = 0;

	if (*(curtain_robot.dp_auto_power.dp_data) == TRUE) {
		if (clock_time_exceed(curtain_robot.get_lis2dw12_data_time, 10 * 1000)) {
			curtain_robot.get_lis2dw12_data_time = clock_time();

			if (curtain_robot.motor_run_mode != RUN_MODE_IDLE) {
				return;
			}

			x_axis_data = get_lis2dw12_x_value();

			if (x_axis_data > 100 || x_axis_data < -100) {
				x_data_buf[x_data_index] = x_axis_data;
				x_data_index++;

				clean_x_buf_count = 0;
			} else {
				clean_x_buf_count++;
			}

			if ((clean_x_buf_count > 20) && (x_data_index >= 20)) { //At this point it has levelled off
				for (i=0; i < 7; i++) {
					if (x_data_buf[i] >= 0) {
						open_count++;
					} else {
						close_count++;
					}
				}

				if (*curtain_robot.dp_percent_state.dp_data == 0) {
					curtain_robot.auto_power_state = AUTO_POWER_CLOSE;
				} else if (*curtain_robot.dp_percent_state.dp_data == 100) {
					curtain_robot.auto_power_state = AUTO_POWER_OPEN;
				} else if (open_count < close_count) {
					curtain_robot.auto_power_state = AUTO_POWER_CLOSE;
				} else {
					curtain_robot.auto_power_state = AUTO_POWER_OPEN;
				}

				//clean flag
				clean_x_buf_count= 0;
				x_data_index=0;
			}

			if (curtain_robot.auto_power_state != AUTO_POWER_IDLE && clean_x_buf_count>= 100) {
				if (curtain_robot.auto_power_state == AUTO_POWER_OPEN) {
					curtain_percent_control(*curtain_robot.dp_percent_state.dp_data, 0);
				} else if (curtain_robot.auto_power_state == AUTO_POWER_CLOSE) {
					curtain_percent_control(*curtain_robot.dp_percent_state.dp_data, 100);
				}
				curtain_robot.auto_power_state=AUTO_POWER_IDLE;
				clean_x_buf_count = 0;
			}

			if (clean_x_buf_count > 500) {
				clean_x_buf_count = 0;
				x_data_index=0;
			}
		}
	}
}

Code snippet #9

Plain text
#define I2C_CLK_SPEED 200000

short lsb_size_tab[] = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048};

unsigned char opt3004_init(void)
{
    unsigned char device_id_data_buf[2] = {0};
    unsigned char manufacturer_id_data_buf[2] = {0};

    unsigned char write_data[2] = {0xcc, 0x10};

    unsigned long delay_time = 0;

    i2c_gpio_set(I2C_GPIO_GROUP_C0C1);

    i2c_master_init(OPT3004_I2C_ADDR_W, (unsigned char)(CLOCK_SYS_CLOCK_HZ / (4 * I2C_CLK_SPEED)));
    i2c_write_series(OPT3004_CONFIG_REGISTER_ADDR, 1, write_data, 2);

    i2c_master_init(OPT3004_I2C_ADDR_R, (unsigned char)(CLOCK_SYS_CLOCK_HZ / (4 * I2C_CLK_SPEED)));

    i2c_read_series(OPT3004_DEVICE_ID_REGISTER_ADDR, 1, device_id_data_buf, 2);
    delay_time = clock_time();
    while (!clock_time_exceed(delay_time, 100)); //100us delay
    i2c_read_series(OPT3004_MANUFACTURER_ID_REGISTER_ADDR, 1, manufacturer_id_data_buf, 2);
    if ((((device_id_data_buf[0]<<8) + device_id_data_buf[1]) != DEVICE_ID) || \
		(((manufacturer_id_data_buf[0]<<8) + manufacturer_id_data_buf[1]) != MANUFACTURER_ID)) {
        return 0;
    }

    return 1;
}

short get_opt3004_value(void)
{
	short ret_value = -1;
	int result_value = 0;
	short result_data_e = 0, result_data_r = 0;
	unsigned char opt3004_cfg_data[2] = {0};
	unsigned char opt3004_result_data[2] = {0};

	i2c_master_init(OPT3004_I2C_ADDR_R, (unsigned char)(CLOCK_SYS_CLOCK_HZ / (4 * I2C_CLK_SPEED)));

	i2c_read_series(OPT3004_CONFIG_REGISTER_ADDR, 1, opt3004_cfg_data, 2);
	if (opt3004_cfg_data[1]&0x80) {
		i2c_read_series(OPT3004_RESULT_REGISTER_ADDR, 1, opt3004_result_data, 2);
		result_value = (opt3004_result_data[0]<<8) + opt3004_result_data[1];

		result_data_e = ((result_value & 0xF000) >> 12);
		result_data_r = (result_value & 0x0FFF);
		ret_value = lsb_size_tab[result_data_e] * result_data_r / 100;
		TUYA_APP_LOG_DEBUG("ret_value:%d", ret_value);
	}
	return ret_value;
}

Code snippet #10

Plain text
#define I2C_CLK_SPEED 200000

short lsb_size_tab[] = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048};

unsigned char opt3004_init(void)
{
    unsigned char device_id_data_buf[2] = {0};
    unsigned char manufacturer_id_data_buf[2] = {0};

    unsigned char write_data[2] = {0xcc, 0x10};

    unsigned long delay_time = 0;

    i2c_gpio_set(I2C_GPIO_GROUP_C0C1);

    i2c_master_init(OPT3004_I2C_ADDR_W, (unsigned char)(CLOCK_SYS_CLOCK_HZ / (4 * I2C_CLK_SPEED)));
    i2c_write_series(OPT3004_CONFIG_REGISTER_ADDR, 1, write_data, 2);

    i2c_master_init(OPT3004_I2C_ADDR_R, (unsigned char)(CLOCK_SYS_CLOCK_HZ / (4 * I2C_CLK_SPEED)));

    i2c_read_series(OPT3004_DEVICE_ID_REGISTER_ADDR, 1, device_id_data_buf, 2);
    delay_time = clock_time();
    while (!clock_time_exceed(delay_time, 100)); //100us delay
    i2c_read_series(OPT3004_MANUFACTURER_ID_REGISTER_ADDR, 1, manufacturer_id_data_buf, 2);
    if ((((device_id_data_buf[0]<<8) + device_id_data_buf[1]) != DEVICE_ID) || \
		(((manufacturer_id_data_buf[0]<<8) + manufacturer_id_data_buf[1]) != MANUFACTURER_ID)) {
        return 0;
    }

    return 1;
}

short get_opt3004_value(void)
{
	short ret_value = -1;
	int result_value = 0;
	short result_data_e = 0, result_data_r = 0;
	unsigned char opt3004_cfg_data[2] = {0};
	unsigned char opt3004_result_data[2] = {0};

	i2c_master_init(OPT3004_I2C_ADDR_R, (unsigned char)(CLOCK_SYS_CLOCK_HZ / (4 * I2C_CLK_SPEED)));

	i2c_read_series(OPT3004_CONFIG_REGISTER_ADDR, 1, opt3004_cfg_data, 2);
	if (opt3004_cfg_data[1]&0x80) {
		i2c_read_series(OPT3004_RESULT_REGISTER_ADDR, 1, opt3004_result_data, 2);
		result_value = (opt3004_result_data[0]<<8) + opt3004_result_data[1];

		result_data_e = ((result_value & 0xF000) >> 12);
		result_data_r = (result_value & 0x0FFF);
		ret_value = lsb_size_tab[result_data_e] * result_data_r / 100;
		TUYA_APP_LOG_DEBUG("ret_value:%d", ret_value);
	}
	return ret_value;
}

Github

https://github.com/TuyaInc/tuya_ble_sdk_Demo_Project_tlsr8253

Github

https://github.com/tuya/tuya-iotos-embeded-demo-ble-samrt-curtain-robot

Credits

Sandwich IoT

Sandwich IoT

40 projects • 4 followers

Comments