/*
 * This file is part of the MicroPython ESP32 project, https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2021 Sorz 
 * Copyright (c) 2018 LoBo (https://github.com/loboris)
 *
 * 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.
 */

/*
 * Module for the Neopixel/WS2812 RGB LEDs using the RMT peripheral on the ESP32.
 */

#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include "esp_rmt.h"
#include "neopixel.h"

#include "py/nlr.h"
#include "py/runtime.h"
#include "modmachine.h"
#include "mphalport.h"

const mp_obj_type_t neopixel_type;

typedef struct _machine_neopixel_obj_t {
    mp_obj_base_t base;
    rmt_channel_t channel;
    int gpio_num;
    pixel_settings_t px;
} machine_neopixel_obj_t;


//------------------------------------------------
STATIC void np_check(machine_neopixel_obj_t *self)
{
    if (self->px.pixels == NULL) {
		mp_raise_msg_varg(&mp_type_OSError, MP_ERROR_TEXT("Neopixel instance not initialized"));
    }
}

//-----------------------------------------------------------------------------------------------
STATIC void machine_neopixel_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind)
{
    machine_neopixel_obj_t *self = self_in;

    if (self->px.pixels != NULL) {
        char colorder[5];
        sprintf(colorder, self->px.color_order);
        if (self->px.nbits == 24) colorder[3] = '\0';
		mp_printf(print, "Neopixel(Pin=%d, Pixels: %d, bit/pix=%d, RMTChannel=%d, PixBufLen=%u, Color order: '%s'\n",
				self->gpio_num, self->px.pixel_count, self->px.nbits, self->channel, sizeof(uint32_t) * self->px.pixel_count, colorder);
		mp_printf(print, "         Timings (ns): T1H=%d, T1L=%d, T0H=%d, T0L=%d, Treset=%d\n)",
				self->px.timings.t1h, self->px.timings.t1l, self->px.timings.t0h, self->px.timings.t0l, self->px.timings.reset);
    }
    else {
		mp_printf(print, "Neopixel(Not initialized)");
    }
}

//------------------------------------------------------------------------------------------------------------------------
STATIC mp_obj_t machine_neopixel_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args)
{
	enum { ARG_pin, ARG_pixels, ARG_t0h, ARG_t0l, ARG_t1h, ARG_t1l, ARG_reset };
	//-----------------------------------------------------
	const mp_arg_t machine_neopixel_init_allowed_args[] = {
			{ MP_QSTR_pin,     MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
			{ MP_QSTR_pixels,  MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 4} },
			{ MP_QSTR_t0h,                       MP_ARG_INT, {.u_int = 350} },
			{ MP_QSTR_t0l,                       MP_ARG_INT, {.u_int = 800} },
			{ MP_QSTR_t1h,                       MP_ARG_INT, {.u_int = 600} },
			{ MP_QSTR_t1l,                       MP_ARG_INT, {.u_int = 700} },
			{ MP_QSTR_reset,                     MP_ARG_INT, {.u_int = 80000} },
	};

	mp_arg_val_t args[MP_ARRAY_SIZE(machine_neopixel_init_allowed_args)];
	mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(machine_neopixel_init_allowed_args), machine_neopixel_init_allowed_args, args);

    int rmtchan = platform_rmt_allocate(1);
    if (rmtchan < 0) {
    	mp_raise_ValueError(MP_ERROR_TEXT("Cannot acquire RMT channel"));
    }

    int8_t pin = machine_pin_get_id(args[ARG_pin].u_obj);

    int pixels = args[ARG_pixels].u_int;

    // Check pin
    if (pin > 33) {
    	mp_raise_ValueError(MP_ERROR_TEXT("Wrong pin, only pins 0~33 allowed"));
    }
    // Check number of pixels
    if ((pixels < 0) || (pixels > 1024)) {
    	mp_raise_ValueError(MP_ERROR_TEXT("Maximum 1024 pixels can be used"));
    }

    // Setup the neopixels object
    machine_neopixel_obj_t *self = m_new_obj(machine_neopixel_obj_t );

    self->channel = rmtchan;
    self->gpio_num = pin;
    self->px.pixel_count = pixels;

    // Set defaults
    self->px.brightness = 255;
    sprintf(self->px.color_order, "GRBW");

	self->px.nbits = 24;
	self->px.timings.t0h = args[ARG_t0h].u_int;
	self->px.timings.t0l = args[ARG_t0l].u_int;
	self->px.timings.t1h = args[ARG_t1h].u_int;
	self->px.timings.t1l = args[ARG_t1l].u_int;
	self->px.timings.reset = args[ARG_reset].u_int;

    // Allocate buffers
    self->px.pixels = (uint8_t *)malloc((self->px.nbits / 8) * self->px.pixel_count);

    if (self->px.pixels == NULL) goto error_exit;
    if (neopixel_init(pin, rmtchan) != ESP_OK) goto error_exit;

    self->base.type = &neopixel_type;
	np_clear(&self->px);
	np_show(&self->px, self->channel);

    return MP_OBJ_FROM_PTR(self);

error_exit:
	platform_rmt_release(rmtchan);
	if (self->px.pixels) free(self->px.pixels);
	self->px.pixels = NULL;

    mp_raise_msg_varg(&mp_type_OSError, MP_ERROR_TEXT("Error initializing Neopixel interface"));
	return mp_const_none;
}

//------------------------------------------------------
STATIC mp_obj_t machine_neopixel_deinit(mp_obj_t self_in)
{
    machine_neopixel_obj_t *self = self_in;
    np_check(self);

	np_clear(&self->px);
	np_show(&self->px, self->channel);

    neopixel_deinit(self->channel);

    if (self->px.pixels) free(self->px.pixels);
    self->px.pixels = NULL;
    platform_rmt_release(self->channel);

    return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(machine_neopixel_deinit_obj, machine_neopixel_deinit);

//------------------------------------------------------
STATIC mp_obj_t machine_neopixel_clear(mp_obj_t self_in)
{
    machine_neopixel_obj_t *self = self_in;
    np_check(self);

	np_clear(&self->px);
   	MP_THREAD_GIL_EXIT();
	np_show(&self->px, self->channel);
   	MP_THREAD_GIL_ENTER();
    return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(machine_neopixel_clear_obj, machine_neopixel_clear);

//-----------------------------------------------------
STATIC mp_obj_t machine_neopixel_show(mp_obj_t self_in)
{
    machine_neopixel_obj_t *self = self_in;
    np_check(self);

   	MP_THREAD_GIL_EXIT();
	np_show(&self->px, self->channel);
   	MP_THREAD_GIL_ENTER();
    return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(machine_neopixel_show_obj, machine_neopixel_show);

//----------------------------------------------------------------------------------------------
STATIC mp_obj_t machine_neopixel_set(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
{
	const mp_arg_t allowed_args[] = {
	    { MP_QSTR_pos,   MP_ARG_REQUIRED | MP_ARG_INT,  {.u_int = 1} },
	    { MP_QSTR_color, MP_ARG_REQUIRED | MP_ARG_INT,  {.u_int = 0} },
	    { MP_QSTR_white,                   MP_ARG_INT,  {.u_int = 0} },
	    { MP_QSTR_num,                     MP_ARG_INT,  {.u_int = 1} },
	    { MP_QSTR_update,                  MP_ARG_BOOL, {.u_bool = true} },
	};
	machine_neopixel_obj_t *self = pos_args[0];
    np_check(self);
    mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
    mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

    int cnt = args[3].u_int;
    uint32_t color = (uint32_t)args[1].u_int << 8;
    int pos = args[0].u_int;

    if (self->px.nbits == 32) {
    	color |= (args[2].u_int & 0x000000FF);
    }

    if (pos < 1) pos = 1;
	if (pos > self->px.pixel_count) pos = self->px.pixel_count;
	if (cnt < 1) cnt = 1;
	if ((cnt + pos - 1) > self->px.pixel_count) cnt = self->px.pixel_count - pos + 1;

	for (uint16_t i = 0; i < cnt; i++) {
		np_set_pixel_color(&self->px, i + pos - 1, color);
	}

	if (args[4].u_bool) {
	   	MP_THREAD_GIL_EXIT();
		np_show(&self->px, self->channel);
	   	MP_THREAD_GIL_ENTER();
	}

	return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_KW(machine_neopixel_set_obj, 3, machine_neopixel_set);

//----------------------------------------------------------------------------------------------------
STATIC mp_obj_t machine_neopixel_set_white(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
{
	const mp_arg_t allowed_args[] = {
	    { MP_QSTR_pos,   MP_ARG_REQUIRED | MP_ARG_INT,  {.u_int = 1} },
	    { MP_QSTR_white, MP_ARG_REQUIRED | MP_ARG_INT,  {.u_int = 0} },
	    { MP_QSTR_num,                     MP_ARG_INT,  {.u_int = 1} },
	    { MP_QSTR_update,                  MP_ARG_BOOL, {.u_bool = true} },
	};
	machine_neopixel_obj_t *self = pos_args[0];
    np_check(self);
    mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
    mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

    if (self->px.nbits == 32) {
		int pos = args[0].u_int;
		int cnt = args[2].u_int;
		if (pos < 1) pos = 1;
		if (pos > self->px.pixel_count) pos = self->px.pixel_count;
		if (cnt < 1) cnt = 1;
		if ((cnt + pos - 1) > self->px.pixel_count) cnt = self->px.pixel_count - pos + 1;

		uint8_t white;
		uint32_t color = np_get_pixel_color(&self->px, pos-1, &white) << 8;

		color |= (args[1].u_int & 0x000000FF);

		for (uint16_t i = 0; i < cnt; i++) {
			np_set_pixel_color(&self->px, i+pos-1, color);
		}

		if (args[3].u_bool) {
			MP_THREAD_GIL_EXIT();
			np_show(&self->px, self->channel);
			MP_THREAD_GIL_ENTER();
		}
    }

	return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_KW(machine_neopixel_set_white_obj, 3, machine_neopixel_set_white);

//---------------------------------------------------------------------------------------------------
STATIC mp_obj_t machine_neopixel_setHSB(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {

    const mp_arg_t allowed_args[] = {
  	    { MP_QSTR_pos, 		   MP_ARG_REQUIRED | MP_ARG_INT,  { .u_int = 0} },
        { MP_QSTR_hue,         MP_ARG_REQUIRED | MP_ARG_OBJ,  { .u_obj = mp_const_none } },
        { MP_QSTR_saturation,  MP_ARG_REQUIRED | MP_ARG_OBJ,  { .u_obj = mp_const_none } },
        { MP_QSTR_brightness,  MP_ARG_REQUIRED | MP_ARG_OBJ,  { .u_obj = mp_const_none } },
	    { MP_QSTR_num,                           MP_ARG_INT,  { .u_int = 1} },
	    { MP_QSTR_update,                        MP_ARG_BOOL, { .u_bool = true} },
    };
	machine_neopixel_obj_t *self = pos_args[0];
    np_check(self);

    mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
    mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

    mp_float_t hue = mp_obj_get_float(args[1].u_obj);
    mp_float_t sat = mp_obj_get_float(args[2].u_obj);
    mp_float_t bri = mp_obj_get_float(args[3].u_obj);

    uint32_t color = hsb_to_rgb(hue, sat, bri) << 8;

    int pos = args[0].u_int;
    int cnt = args[4].u_int;

	if (pos < 1) pos = 1;
	if (pos > self->px.pixel_count) pos = self->px.pixel_count;
	if (cnt < 1) cnt = 1;
	if ((cnt + pos - 1) > self->px.pixel_count) cnt = self->px.pixel_count - pos + 1;

	for (uint16_t i = 0; i < cnt; i++) {
		np_set_pixel_color(&self->px, i+pos-1, color);
	}

	if (args[5].u_bool) {
	   	MP_THREAD_GIL_EXIT();
		np_show(&self->px, self->channel);
	   	MP_THREAD_GIL_ENTER();
	}

	return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_neopixel_setHSB_obj, 5, machine_neopixel_setHSB);

//-------------------------------------------------------------------------------------------------------
STATIC mp_obj_t machine_neopixel_setHSB_int(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {

    const mp_arg_t allowed_args[] = {
  	    { MP_QSTR_pos, 		   MP_ARG_REQUIRED | MP_ARG_INT,  { .u_int = 0} },
        { MP_QSTR_hue,         MP_ARG_REQUIRED | MP_ARG_OBJ,  { .u_obj = mp_const_none } },
        { MP_QSTR_saturation,  MP_ARG_REQUIRED | MP_ARG_OBJ,  { .u_obj = mp_const_none } },
        { MP_QSTR_brightness,  MP_ARG_REQUIRED | MP_ARG_OBJ,  { .u_obj = mp_const_none } },
	    { MP_QSTR_num,                           MP_ARG_INT,  { .u_int = 1} },
	    { MP_QSTR_update,                        MP_ARG_BOOL, { .u_bool = true} },
    };
	machine_neopixel_obj_t *self = pos_args[0];
    np_check(self);

    mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
    mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

    int hue = mp_obj_get_int(args[1].u_obj) & 0xFF;
    int sat = mp_obj_get_int(args[2].u_obj) & 0xFF;
    int bri = mp_obj_get_int(args[3].u_obj) & 0xFF;

    if (hue < 0) hue = 0;
    if (sat < 0) sat = 0;
    if (bri < 0) bri = 0;
    if (sat > 1000) sat = 1000;
    if (bri > 1000) bri = 1000;

    uint32_t color = hsb_to_rgb_int(hue, sat, bri) << 8;

    int pos = args[0].u_int;
    int cnt = args[4].u_int;

	if (pos < 1) pos = 1;
	if (pos > self->px.pixel_count) pos = self->px.pixel_count;
	if (cnt < 1) cnt = 1;
	if ((cnt + pos - 1) > self->px.pixel_count) cnt = self->px.pixel_count - pos + 1;

	for (uint16_t i = 0; i < cnt; i++) {
		np_set_pixel_color(&self->px, i+pos-1, color);
	}

	if (args[5].u_bool) {
	   	MP_THREAD_GIL_EXIT();
		np_show(&self->px, self->channel);
	   	MP_THREAD_GIL_ENTER();
	}

	return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_neopixel_setHSB_int_obj, 5, machine_neopixel_setHSB_int);

//-----------------------------------------------------------------------
STATIC mp_obj_t machine_neopixel_get(size_t n_args, const mp_obj_t *args)
{
    machine_neopixel_obj_t *self = args[0];
    np_check(self);

	uint8_t white;
	uint32_t icolor;
    int pos = mp_obj_get_int(args[1]);
    if (pos < 1) pos = 1;
    if (pos > self->px.pixel_count) pos = self->px.pixel_count;
    int cnt = 0;
    if (n_args > 2) {
    	cnt  = mp_obj_get_int(args[2]);
    	if (cnt < 1) cnt = 1;
    	if ((cnt + pos - 1) > self->px.pixel_count) cnt = self->px.pixel_count - pos + 1;
    }
    if (cnt < 2) {
		icolor = np_get_pixel_color(&self->px, pos-1, &white);
		if (self->px.nbits == 24) {
			return mp_obj_new_int(icolor);
		}
		else {
			mp_obj_t tuple[2];

			tuple[0] = mp_obj_new_int(icolor);
			tuple[1] = mp_obj_new_int(white);

			return mp_obj_new_tuple(2, tuple);
		}
    }
    else {
		mp_obj_t pix_tuple[cnt];
		mp_obj_t tuple[2];
		for (int i=0; i<cnt; i++) {
			icolor = np_get_pixel_color(&self->px, pos+i-1, &white);
			if (self->px.nbits == 24) {
				pix_tuple[i] = mp_obj_new_int(icolor);
			}
			else {
				tuple[0] = mp_obj_new_int(icolor);
				tuple[1] = mp_obj_new_int(white);

				pix_tuple[i] = mp_obj_new_tuple(2, tuple);
			}
		}
		return mp_obj_new_tuple(cnt, pix_tuple);
    }
}
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_neopixel_get_obj, 2, 3, machine_neopixel_get);

//-----------------------------------------------------------------------------------------------------
STATIC mp_obj_t machine_neopixel_brightness(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
{
	const mp_arg_t allowed_args[] = {
	    { MP_QSTR_brightness,   MP_ARG_INT, {.u_int = -1} },
	    { MP_QSTR_update,       MP_ARG_BOOL, { .u_bool = true} },
	};
	machine_neopixel_obj_t *self = pos_args[0];
    np_check(self);
    mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
    mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

    int bright = args[0].u_int;
    if (bright >= 0) {
    	self->px.brightness = bright & 0xFF;
    	if (args[1].u_bool) {
    	   	MP_THREAD_GIL_EXIT();
    		np_show(&self->px, self->channel);
    	   	MP_THREAD_GIL_ENTER();
    	}
    }
    return mp_obj_new_int(self->px.brightness);
}
MP_DEFINE_CONST_FUN_OBJ_KW(machine_neopixel_brightness_obj, 0, machine_neopixel_brightness);

//-----------------------------------------------------------------------------------------------------
STATIC mp_obj_t machine_neopixel_HSBtoRGB(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {

    const mp_arg_t allowed_args[] = {
        { MP_QSTR_hue,  MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = mp_const_none } },
        { MP_QSTR_saturation,  MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = mp_const_none } },
        { MP_QSTR_brightness,  MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = mp_const_none } },
    };
	machine_neopixel_obj_t *self = pos_args[0];
    np_check(self);

    mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
    mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

    mp_float_t hue = mp_obj_get_float(args[0].u_obj);
    mp_float_t sat = mp_obj_get_float(args[1].u_obj);
    mp_float_t bri = mp_obj_get_float(args[2].u_obj);

    uint32_t color = hsb_to_rgb(hue, sat, bri);

    return mp_obj_new_int(color);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_neopixel_HSBtoRGB_obj, 4, machine_neopixel_HSBtoRGB);

//--------------------------------------------------------------------------------------------------------
STATIC mp_obj_t machine_neopixel_HSBtoRGBint(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {

    const mp_arg_t allowed_args[] = {
        { MP_QSTR_hue,  MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = mp_const_none } },
        { MP_QSTR_saturation,  MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = mp_const_none } },
        { MP_QSTR_brightness,  MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = mp_const_none } },
    };
	machine_neopixel_obj_t *self = pos_args[0];
    np_check(self);

    mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
    mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

    int hue = mp_obj_get_int(args[0].u_obj) & 0xFF;
    int sat = mp_obj_get_int(args[1].u_obj) & 0xFF;
    int bri = mp_obj_get_int(args[2].u_obj) & 0xFF;

    uint32_t color = hsb_to_rgb_int(hue, sat, bri);

    return mp_obj_new_int(color);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_neopixel_HSBtoRGBint_obj, 4, machine_neopixel_HSBtoRGBint);

//------------------------------------------------------------------------------
STATIC mp_obj_t machine_neopixel_RGBtoHSB(mp_obj_t self_in, mp_obj_t color_in) {

    machine_neopixel_obj_t *self = self_in;
    np_check(self);

    uint32_t color = mp_obj_get_int(color_in);

    float hue, sat, bri;

    rgb_to_hsb(color, &hue, &sat, &bri);

   	mp_obj_t tuple[3];

   	tuple[0] = mp_obj_new_float(hue);
   	tuple[1] = mp_obj_new_float(sat);
   	tuple[2] = mp_obj_new_float(bri);

   	return mp_obj_new_tuple(3, tuple);
}
MP_DEFINE_CONST_FUN_OBJ_2(machine_neopixel_RGBtoHSB_obj, machine_neopixel_RGBtoHSB);

//---------------------------------------------------------------------------------------------------
STATIC mp_obj_t machine_neopixel_corder(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {

    const mp_arg_t allowed_args[] = {
        { MP_QSTR_color_order,  MP_ARG_OBJ, { .u_obj = mp_const_none } },
    };
	machine_neopixel_obj_t *self = pos_args[0];
    np_check(self);

    mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
    mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

    char colorder[5];
    const char *corder = NULL;
    if (MP_OBJ_IS_STR(args[0].u_obj)) {
    	corder = mp_obj_str_get_str(args[0].u_obj);
    	// Check color order string
    	uint8_t str_ok = 0;
    	uint8_t nR = 0;
    	uint8_t nG = 0;
    	uint8_t nB = 0;
    	uint8_t nW = 0;
    	int len = strlen(corder);

    	if (((len == 3) && (self->px.nbits == 24)) || ((len == 4) && (self->px.nbits == 32))) {
    		for (int i=0; i < len; i++) {
    			if (corder[i] == 'R') nR++;
    			if (corder[i] == 'G') nG++;
    			if (corder[i] == 'B') nB++;
    			if (corder[i] == 'W') nW++;
    		}
    		if ((nR == 1) && (nG == 1) && (nB == 1)) {
    			str_ok = 1;
    			if ((len == 4) && (nW != 1)) str_ok = 0;
    		}
    	}
    	if (str_ok) {
    		// Set new color order string
   			sprintf(self->px.color_order, "%s", corder);
    	}
    	else {
        	mp_raise_ValueError(MP_ERROR_TEXT("Wrong color order string"));
    	}

    }
    sprintf(colorder, self->px.color_order);
    if (self->px.nbits == 24) colorder[3] = '\0';
    return mp_obj_new_str(colorder, strlen(colorder));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_neopixel_corder_obj, 0, machine_neopixel_corder);

//----------------------------------------------------------------------------------------------------
STATIC mp_obj_t machine_neopixel_timings(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {

    const mp_arg_t allowed_args[] = {
        { MP_QSTR_timings, MP_ARG_OBJ, { .u_obj = mp_const_none } },
    };
	machine_neopixel_obj_t *self = pos_args[0];
    np_check(self);

    mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
    mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

    if (MP_OBJ_IS_TYPE(args[0].u_obj, &mp_type_tuple)) {
        mp_obj_t *t_items;
        mp_obj_t *t1_items;
        mp_obj_t *t0_items;
        uint t_len, t1_len, t0_len;
        int rst;

        mp_obj_tuple_get(args[0].u_obj, &t_len, &t_items);
        if (t_len == 3) {
			mp_obj_tuple_get(t_items[0], &t1_len, &t1_items);
			mp_obj_tuple_get(t_items[1], &t0_len, &t0_items);
			rst = mp_obj_get_int(t_items[2]);
	        if ((t1_len != 2) || (t0_len != 2)) {
	        	mp_raise_ValueError(MP_ERROR_TEXT("Wrong arguments"));
	        }
        }
        else {
        	mp_raise_ValueError(MP_ERROR_TEXT("tuple argument expected"));
        }
        pixel_timing_t tm;
        if (rst > 0) {
			tm.t0h = mp_obj_get_int(t0_items[0]) & 0x7FFF;
			tm.t0l = mp_obj_get_int(t0_items[1]) & 0x7FFF;
			tm.t1h = mp_obj_get_int(t1_items[0]) & 0x7FFF;
			tm.t1l = mp_obj_get_int(t1_items[1]) & 0x7FFF;
			tm.reset = rst;
			
        }
        memcpy(&self->px.timings, &tm, sizeof(pixel_timing_t));
    }

    mp_obj_t t1_tuple[2];
   	mp_obj_t t0_tuple[2];
   	mp_obj_t t_tuple[3];

   	t1_tuple[0] = mp_obj_new_int(self->px.timings.t1h);
   	t1_tuple[1] = mp_obj_new_int(self->px.timings.t1l);

   	t0_tuple[0] = mp_obj_new_int(self->px.timings.t0h);
   	t0_tuple[1] = mp_obj_new_int(self->px.timings.t0l);

   	t_tuple[0] = mp_obj_new_tuple(2, t1_tuple);
   	t_tuple[1] = mp_obj_new_tuple(2, t0_tuple);
   	t_tuple[2] = mp_obj_new_int(self->px.timings.reset);

   	return mp_obj_new_tuple(3, t_tuple);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_neopixel_timings_obj, 0, machine_neopixel_timings);

//-------------------------------------------------------------------------------------------
STATIC mp_obj_t machine_neopixel_rainbow(mp_obj_t self_in, mp_obj_t pos_in, mp_obj_t fact_in)
{
    machine_neopixel_obj_t *self = self_in;
    np_check(self);

    uint32_t color;
    int pos = mp_obj_get_int(pos_in);
    int fact = mp_obj_get_int(fact_in);
    float hue;
    float dHue = 360.0 * fact / (float)self->px.pixel_count;
    float bri = (float)self->px.brightness / 255.0;

    for (int i=0; i<self->px.pixel_count; i++) {
        hue = fmod((dHue * (pos+i)), 360.0);
        color = hsb_to_rgb(hue, 1.0, bri) << 8;
		np_set_pixel_color(&self->px, i, color);
    }
   	MP_THREAD_GIL_EXIT();
	np_show(&self->px, self->channel);
   	MP_THREAD_GIL_ENTER();
    return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_3(machine_neopixel_rainbow_obj, machine_neopixel_rainbow);

//-----------------------------------------------------
STATIC mp_obj_t machine_neopixel_info(mp_obj_t self_in)
{
    machine_neopixel_obj_t *self = self_in;
    np_check(self);

    char colorder[5];
    sprintf(colorder, self->px.color_order);
    if (self->px.nbits == 24) colorder[3] = '\0';

    mp_obj_t tuple[6];

	tuple[0] = mp_obj_new_int(self->gpio_num);
	tuple[1] = mp_obj_new_int(self->px.pixel_count);
	tuple[2] = mp_obj_new_int(self->px.nbits);
	tuple[3] = mp_obj_new_int(self->channel);
	tuple[4] = mp_obj_new_int(sizeof(uint32_t) * self->px.pixel_count);
	tuple[5] = mp_obj_new_str(colorder, strlen(colorder));

	return mp_obj_new_tuple(6, tuple);

}
MP_DEFINE_CONST_FUN_OBJ_1(machine_neopixel_info_obj, machine_neopixel_info);


//=====================================================================
STATIC const mp_rom_map_elem_t machine_neopixel_locals_dict_table[] = {
    { MP_ROM_QSTR(MP_QSTR_clear),      (mp_obj_t)&machine_neopixel_clear_obj },
    { MP_ROM_QSTR(MP_QSTR_set),        (mp_obj_t)&machine_neopixel_set_obj },
    { MP_ROM_QSTR(MP_QSTR_setWhite),   (mp_obj_t)&machine_neopixel_set_white_obj },
    { MP_ROM_QSTR(MP_QSTR_setHSB),     (mp_obj_t)&machine_neopixel_setHSB_obj },
    { MP_ROM_QSTR(MP_QSTR_setHSBint),  (mp_obj_t)&machine_neopixel_setHSB_int_obj },
    { MP_ROM_QSTR(MP_QSTR_get),        (mp_obj_t)&machine_neopixel_get_obj },
    { MP_ROM_QSTR(MP_QSTR_show),       (mp_obj_t)&machine_neopixel_show_obj },
    { MP_ROM_QSTR(MP_QSTR_brightness), (mp_obj_t)&machine_neopixel_brightness_obj },
    { MP_ROM_QSTR(MP_QSTR_HSBtoRGB),   (mp_obj_t)&machine_neopixel_HSBtoRGB_obj },
    { MP_ROM_QSTR(MP_QSTR_HSBtoRGBint),(mp_obj_t)&machine_neopixel_HSBtoRGBint_obj },
    { MP_ROM_QSTR(MP_QSTR_RGBtoHSB),   (mp_obj_t)&machine_neopixel_RGBtoHSB_obj },
    { MP_ROM_QSTR(MP_QSTR_deinit),     (mp_obj_t)&machine_neopixel_deinit_obj },
    { MP_ROM_QSTR(MP_QSTR_timings),    (mp_obj_t)&machine_neopixel_timings_obj },
    { MP_ROM_QSTR(MP_QSTR_color_order),(mp_obj_t)&machine_neopixel_corder_obj },
    { MP_ROM_QSTR(MP_QSTR_rainbow),    (mp_obj_t)&machine_neopixel_rainbow_obj },
    { MP_ROM_QSTR(MP_QSTR_info),	   (mp_obj_t)&machine_neopixel_info_obj },

	{ MP_ROM_QSTR(MP_QSTR_BLACK),		MP_ROM_INT(0x000000) },
	{ MP_ROM_QSTR(MP_QSTR_WHITE),		MP_ROM_INT(0xFFFFFF) },
	{ MP_ROM_QSTR(MP_QSTR_RED),			MP_ROM_INT(0xFF0000) },
	{ MP_ROM_QSTR(MP_QSTR_LIME),		MP_ROM_INT(0x00FF00) },
	{ MP_ROM_QSTR(MP_QSTR_BLUE),		MP_ROM_INT(0x0000FF) },
	{ MP_ROM_QSTR(MP_QSTR_YELLOW),		MP_ROM_INT(0xFFFF00) },
	{ MP_ROM_QSTR(MP_QSTR_CYAN),		MP_ROM_INT(0x00FFFF) },
	{ MP_ROM_QSTR(MP_QSTR_MAGENTA),		MP_ROM_INT(0xFF00FF) },
	{ MP_ROM_QSTR(MP_QSTR_SILVER),		MP_ROM_INT(0xC0C0C0) },
	{ MP_ROM_QSTR(MP_QSTR_GRAY),		MP_ROM_INT(0x808080) },
	{ MP_ROM_QSTR(MP_QSTR_MAROON),		MP_ROM_INT(0x800000) },
	{ MP_ROM_QSTR(MP_QSTR_OLIVE),		MP_ROM_INT(0x808000) },
	{ MP_ROM_QSTR(MP_QSTR_GREEN),		MP_ROM_INT(0x008000) },
	{ MP_ROM_QSTR(MP_QSTR_PURPLE),		MP_ROM_INT(0x800080) },
	{ MP_ROM_QSTR(MP_QSTR_TEAL),		MP_ROM_INT(0x008080) },
	{ MP_ROM_QSTR(MP_QSTR_NAVY),		MP_ROM_INT(0x000080) },

	{ MP_ROM_QSTR(MP_QSTR_TYPE_RGB),	MP_ROM_INT(0) },
    { MP_ROM_QSTR(MP_QSTR_TYPE_RGBW),	MP_ROM_INT(1) },
};

STATIC MP_DEFINE_CONST_DICT(machine_neopixel_locals_dict, machine_neopixel_locals_dict_table);

//===========================================
const mp_obj_type_t neopixel_type = {
    { &mp_type_type },
    .name = MP_QSTR_Neopixel,
    .print = machine_neopixel_print,
    .make_new = machine_neopixel_make_new,
    .locals_dict = (mp_obj_dict_t*)&machine_neopixel_locals_dict,
};
