# Christian Fiot 2020
import gc
import uos
import utime
from machine import SPI
from machine import Pin
from machine import disable_irq
from machine import enable_irq
import _thread  # 1.06   LED animate


# 9b colorStates = { "on": [255,255,255], "off": [0,0,0], "warning": [255,160,0], "error": [255,0,0], "info": [0,0,255], "success": [0,255,0], "secondary": [255,0,255], "primary": [0,184,255] }
colorStates = { "on": [255,255,255], "off": [0,0,0], "warning": [255,160,0], "error": [255,0,0], "info": [0,0,255], "success": [0,255,0], "secondary": [255,0,255], "primary": [255,255,0] } # 9b
# on= whyte, warning = orange, error = red, info = blue, success = Green, secondary = violet, primary = yellow



"""
Driver for WS2812 WS2813 RGB LEDs.
May be used for controlling single LED or chain of LEDs.

usage:
    from neopixel import WS2812
    rgbStrip = WS2812(ledNumber=4, dataPin='P2', traceDebug=True) #  P2,P5
    rgbStrip = WS2812(ledNumber=60, dataPin='P2')

    # rgbStrip.np[0] = (255, 0, 0) # red
    # rgbStrip.np[1] = (0, 255, 0) # green
    # rgbStrip.np[2] = (0, 0, 255) # blue
    # rgbStrip.np[3] = (85, 85, 85) # white
    # rgbStrip.show()
    # rrgbStrip.buf  # display tx buffer

    rgbStrip.setColor(16,16,16)


Credit   https://github.com/aureleq/wipy-lopy-WS2812/blob/master/ws2812.py
Credit   https://raw.githubusercontent.com/Gadgetoid/wipy-WS2812/master/ws2812alt.py
Credit   stephen @ core electronics
"""


class WS2812:

    def __init__(self, ledNumber=1, dataPin='P2', traceDebug=False):
        """
        Params:
        * ledNumber = count of LEDs
        * dataPin = pin to connect data channel
        * p_ExRGB = Pin('P2', mode=Pin.OUT) # Expansion board
        """
        self.trace = traceDebug
        self.ledNumber = ledNumber
        self.dataPin = dataPin   # V1.1
        self.threaded = False  # 1.06
        self.effect = 0  # 1.06
        self.color = (0,0,0)  # 1.06
        self.delay = 0  # 1.06


        # Prepare SPI tx data buffer
        self.buf_length = self.ledNumber * 24  # 8 bytes for each color  3 * 8
        self.buf = bytearray(self.buf_length)
        self.np = [(0,0,0)] * self.ledNumber  # Clear all  Individual NeoPixel (R,G,B)
        if self.trace :
            print("Create object WS2812")


        # SPI init
        # Bus 0, 8Mhz => 125ns per ck , 8 clock cycle per bitpixel +2 clock? cycle between each bit transfert
        # = 1.25Usec per bitpixel
        # Tramsmit 8 bits, firstbit=b7,  Pins(CLK,MOSI,MISO)   only use MOSI (Master Out Slave In)
        # self.spi = SPI(0, SPI.MASTER, baudrate=8000000, polarity=0, phase=1, bits=8, firstbit=SPI.MSB, pins=(None, dataPin, None))
        self.spi = SPI(0, SPI.MASTER, baudrate=8000000, polarity=0, phase=1, pins=(None, dataPin, None))

        self.show()  # Turn LEDs off


    def show(self): # , data):
        """
        Show RGB data on LEDs. Expected data = [(R, G, B), ...] where R, G and B
        are intensities of colors in range from 0 to 255. One RGB tuple for each
        LED. Count of tuples may be less than count of connected LEDs.
        """
        self.create_buf() # data)
        self.send_buf()


    def send_buf(self): # Send buffer over SPI.
        if self.trace :
            print("send_buf") # for debug only

        # 1.07B disable_irq() #  1.06
        # V1.1
        # There is only 'one' SPI bus available, it is hard.
        # There is 'zero' SPI soft available
        # Sending SPI data to a different pin requires re-init of the bus
        self.spi.init(SPI.MASTER, baudrate=8000000, polarity=0, phase=1, bits=8, firstbit=SPI.MSB, pins=(None, self.dataPin, None))

        # 1.06  disable_irq()
        self.spi.write(self.buf)

        # 1.07B  Copy Strip1 Send to all other LED strips
        if self.threaded == True:
            self.spi.init(SPI.MASTER, baudrate=8000000, polarity=0, phase=1, bits=8, firstbit=SPI.MSB, pins=(None, 'P3', None)) # rgbStrip2
            self.spi.write(self.buf)
            self.spi.init(SPI.MASTER, baudrate=8000000, polarity=0, phase=1, bits=8, firstbit=SPI.MSB, pins=(None, 'P9', None)) # rgbStrip3
            self.spi.write(self.buf)
            self.spi.init(SPI.MASTER, baudrate=8000000, polarity=0, phase=1, bits=8, firstbit=SPI.MSB, pins=(None, 'P10', None)) # rgbStrip4
            self.spi.write(self.buf)

        # 1.07B enable_irq()
        # 1.03  gc.collect()


    # Values to put inside SPi register for each color's bit
    # see "WS2812B LED Datasheet.pdf"
    # representation of 1 bits 0, 1
    # 1 bytes per bit code
    #    code 0 = 11 10 00 00      1/3 high  2/3 low
    #    code 1 = 11 11 11 00      2/3 high  1/3 low
    CodeColBit = (0xE0, 0xFC)


    def create_buf(self):  # data, start=0):
        """
        Fill tx buffer with RGB data.
        Order of colors in buffer is GRB because WS2812 LED
        Each color is represented by 8 bytes in buffer, one byte per bitcolor
        """
        buf = self.buf
        CodeColBit = self.CodeColBit

        index = 0
        for red, green, blue in self.np:  # data:
            if self.trace :
                print("update_buf - Idx:" + str(index) + " Color r:" + str(red) + " g:" + str(green) + " b:" + str(blue) )
            buf[index]   = CodeColBit[(green & 0x080) >> 7]
            buf[index+1] = CodeColBit[(green & 0x040) >> 6]
            buf[index+2] = CodeColBit[(green & 0x020) >> 5]
            buf[index+3] = CodeColBit[(green & 0x010) >> 4]
            buf[index+4] = CodeColBit[(green & 0x008) >> 3]
            buf[index+5] = CodeColBit[(green & 0x004) >> 2]
            buf[index+6] = CodeColBit[(green & 0x002) >> 1]
            buf[index+7] = CodeColBit[(green & 0x001)     ]
            index += 8

            buf[index]   = CodeColBit[(red & 0x080) >> 7]
            buf[index+1] = CodeColBit[(red & 0x040) >> 6]
            buf[index+2] = CodeColBit[(red & 0x020) >> 5]
            buf[index+3] = CodeColBit[(red & 0x010) >> 4]
            buf[index+4] = CodeColBit[(red & 0x008) >> 3]
            buf[index+5] = CodeColBit[(red & 0x004) >> 2]
            buf[index+6] = CodeColBit[(red & 0x002) >> 1]
            buf[index+7] = CodeColBit[(red & 0x001)     ]
            index += 8

            buf[index]   = CodeColBit[(blue & 0x080) >> 7]
            buf[index+1] = CodeColBit[(blue & 0x040) >> 6]
            buf[index+2] = CodeColBit[(blue & 0x020) >> 5]
            buf[index+3] = CodeColBit[(blue & 0x010) >> 4]
            buf[index+4] = CodeColBit[(blue & 0x008) >> 3]
            buf[index+5] = CodeColBit[(blue & 0x004) >> 2]
            buf[index+6] = CodeColBit[(blue & 0x002) >> 1]
            buf[index+7] = CodeColBit[(blue & 0x001)     ]
            index += 8

    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # RGB Animation functions

    # One color selection for the strip
    def setColor(self,r, g, b):
        """
        Set all pixels to the same color
        """
        self.effect = 0  # 1.06
        self.color = (r, g, b)  # 1.06

        # 1.07B  if self.threaded == True: return   # 1.06
        self.threaded = False  # 1.07B

        for i in range(self.ledNumber):
            self.np[i] = (r, g, b)
        self.show()

    def setState(self, state):
        global colorStates
        self.setColor(colorStates[state][0],colorStates[state][1],colorStates[state][2])

    def blinkState(self, state, count, interval):
        global colorStates
        for i in range(count):
            self.setColor(colorStates[state][0],colorStates[state][1],colorStates[state][2])
            utime.sleep_ms(int(interval))
            self.setColor(0,0,0)
            utime.sleep_ms(int(interval)) # 9b

    def seedColor(self,r,g,b):  # 1.06
        for i in range(self.ledNumber):
            self.np[i] = (r, g, b)
        self.show()


    # Input a value 0 to 255 to get a color value.
    # The colours are a transition r - g - b - back to r.
    # Do not change this!
    def ColorWheel(self,WheelPos):
        WheelPos = 255 - WheelPos
        if WheelPos < 85:
            return (255 - WheelPos * 3, 0, WheelPos * 3)
        if WheelPos < 170:
            WheelPos -= 85
            return (0, WheelPos * 3, 255 - WheelPos * 3)
        WheelPos -= 170
        return (WheelPos * 3, 255 - WheelPos * 3, 0)

    # 1) rainbow equally distributed throughout
    def rainbowCycle(self,wait):
        self.effect = 1  # 1.06
        for j in range (0,256,1):
            for i in range (0,self.ledNumber,1):
                self.np[i] = self.ColorWheel(int((i * 256 / self.ledNumber) + j) & 255)
            self.show()
            utime.sleep_ms(wait)
            if self.effect != 1:  return   # 1.06


    # 2) Cycles all the lights through rainbow colors
    def rainbow(self,wait):
        self.effect = 2  # 1.06
        for j in range (0,256,1):
            for i in range (0,self.ledNumber,1):
                self.np[i] = self.ColorWheel((i+j & 255))
            self.show()
            utime.sleep_ms(wait)
            if self.effect != 2:  return   # 1.06


    # 3) Theatre-style crawling lights.
    def theaterChase(self,c, wait):
        self.effect = 3  # 1.06
        for j in range(0, 10, 1):  # do 10 cycles of chasing
            for q in range(0, 3, 1):
                for i in range(0, self.ledNumber, 3):
                    try:
                        self.np[i+q] = c # turn every third pixel on
                    except: # if i+q is out of the list then ignore
                        pass
                    if self.effect != 3:  return  # 1.07
                self.show( )
                utime.sleep_ms(wait)
                # 1.07 if self.effect != 3:  return  # 1.06

                for i in range(0, self.ledNumber, 3):
                    try:
                        self.np[i+q] = (0,0,0)  # turn every third pixel off
                    except: # if i+q is out of the list then ignore
                        pass
                    if self.effect != 3:  return  # 1.07
                # 1.07 if self.effect != 3:  return  #  1.06

    # 4)
    def theaterChaseRainbow(self,wait):
        self.effect = 4  # 1.06
        for j in range(0, 256, 1):     # cycle all 256 colors in the wheel
            for q in range(0, 3, 1):
                for i in range(0, self.ledNumber, 3):
                    try:
                        self.np[i+q] = self.ColorWheel((i + j) % 255) #Wheel( int((i+j)) % 255)) # turn every third pixel on
                    except: # if i+q is out of the list then ignore
                        pass
                    if self.effect != 4:  return  # 1.07
                self.show()
                utime.sleep_ms(wait)
                # 1.07 if self.effect != 4:  return    # 1.06

                for i in range(0, self.ledNumber, 3):
                    try:
                        self.np[i+q] = (0,0,0)  # turn every third pixel off
                    except: # if i+q is out of the list then ignore
                        pass
                    if self.effect != 4:  return # 1.07
                # 1.07 if self.effect != 4:  return    # 1.06


    # 5) Fill the dots one after the other with a color
    def scrollWipe(self,wait):
        self.effect = 5  # 1.06
        for j in range(0, 256, 16): # Transition through all colors of the wheel skip every 16 so the change is visible
            for i in range(0, self.ledNumber, 1):
                self.np[i] = self.ColorWheel((j) & 255)
                self.show()
                utime.sleep_ms(wait)
                if self.effect != 5:  return   # 1.06


    # 6)  sparkle the LEDs to the set color
    def sparkle(self,c, wait):
        self.effect = 6  # 1.06
        pixel = int.from_bytes(uos.urandom(1), "big") % self.ledNumber
        pixel2 = int.from_bytes(uos.urandom(1), "big") % self.ledNumber
        self.np[pixel] = c
        self.np[pixel2] = c
        self.show( )
        utime.sleep_ms(wait)
        self.np[pixel] = (0,0,0)
        self.np[pixel2] = (0,0,0)
        # 1.07  if self.effect != 6:  return   # 1.06


    # 7)  Fill the dots one after the other with a color
    def colorWipe(self,c, wait):
        self.effect = 7  # 1.06
        for i in range(0, self.ledNumber, 1):
            self.np[i] = c
            self.show( )
            utime.sleep_ms(wait)
            if self.effect != 7:  return  # 1.06
        self.seedColor(0,0,0)



    # 8) Bounce effect
    # The bounce() function creates a bounce effect and accepts the r, g and b parameters to set the color,
    # and the waiting time. The waiting time determines how fast the bouncing effect is.
    def bounce(self, c, wait):  # 1.06
        self.effect = 8
        n = self.ledNumber
        for i in range(2 * n):
            for j in range(n):
                self.np[j] = c
            if (i // n) % 2 == 0:
                self.np[i % n] = (0, 0, 0)
            else:
                self.np[ - 1 - (i % n)] = (0, 0, 0)
            self.show( )
            utime.sleep_ms(wait)
            if self.effect != 8:  return  # 1.06

    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    """ 1.07
    # To prevent cratch, never exit the task
    def threadFunc(self, effect, color, delay):  # 1.06
        self.threaded = True
        if effect != 0: self.seedColor(0,0,0)  # start animation with blank strip
        while True:  # 1.06   (self.effect != 0):
            effect = self.effect  # change effect by changing the external variable
            color = self.color
            delay = self.delay
            try:
                if effect == 0:   # executed once then loop 100ms
                    r,g,b = self.color
                    self.seedColor(r,g,b)
                    effect = -1

                elif effect == 1:
                    self.rainbowCycle(delay)  # rainbow equally distributed throughout
                elif effect == 2:
                        self.rainbow(delay)  # Cycles all the lights through rainbow colors
                elif effect == 3:
                    self.theaterChase(color, delay)  # Theatre-style crawling lights.
                elif effect == 4:
                    self.theaterChaseRainbow(delay)
                elif effect == 5:
                    self.scrollWipe(delay)  # Fill the dots one after the other with a color
                elif effect == 6:
                    self.sparkle(color, delay)  # sparkle the LEDs to the set color
                elif effect == 7:
                    self.colorWipe(color, delay) # Fill the dots one after the other with a color
                elif effect == 8:
                    self.bounce(color, delay)  # Bounce effect

                else:
                    utime.sleep_ms(100)

                # if effect != self.effect: self.seedColor(0,0,0)

            except:
                pass
                self.effect = 0
                print("np thread exception")

        r,g,b = self.color
        self.seedColor(r,g,b)
        self.threaded = False
        print("np thread %r completed" % ( _thread.get_ident() ) )
        _thread.exit()  # 1.07   unable to close the task without crashing


    # entry point
    def animate(self, effect=0, r=7 , g=7 , b=7, delay = 50):   # 1.06
        self.color = (r, g, b)
        if effect > 8 or effect < 0: effect = 0
        self.effect = effect
        if delay < 1: delay = 1
        self.delay = delay
        args = (self.effect, self.color, self.delay, )

        if self.threaded == False:
            _thread.start_new_thread(self.threadFunc, args)
            # _thread.start_new_thread(rgbStrip3.threadFunc, (1,(7,7,7), 0) )
    """

    # 1.07B not used
    # 1.07A  no thread version, SolarTask() call nothreadFunc() at regular interval
    def animate(self, effect=0, r=7 , g=7 , b=7, delay = 50, threaded = True):
        self.color = (r, g, b)
        if effect > 8 or effect < 0: effect = 0
        self.effect = effect
        if delay < 50: delay = 50
        self.delay = delay
        self.threaded = threaded


    # 1.07A  no thread version, SolarTask() call nothreadFunc() at regular interval
    def nothreadFunc(self):
        if self.threaded == True:
            effect = self.effect  # change effect by changing the external variable
            color = self.color
            delay = self.delay
            if effect == 0:
                r,g,b = self.color
                self.seedColor(r,g,b)
                self.threaded = False

            elif effect == 1:
                self.rainbowCycle(delay)  # rainbow equally distributed throughout
            elif effect == 2:
                    self.rainbow(delay)  # Cycles all the lights through rainbow colors
            elif effect == 3:
                self.theaterChase(color, delay)  # Theatre-style crawling lights.
            elif effect == 4:
                self.theaterChaseRainbow(delay)
            elif effect == 5:
                self.scrollWipe(delay)  # Fill the dots one after the other with a color
            elif effect == 6:
                self.sparkle(color, delay)  # sparkle the LEDs to the set color
            elif effect == 7:
                self.colorWipe(color, delay) # Fill the dots one after the other with a color
            elif effect == 8:
                self.bounce(color, delay)  # Bounce effect



# rgbStrip3 = WS2812(ledNumber=60, dataPin='P9')  # 1.06   for testing
# rgbStrip4 = WS2812(ledNumber=60, dataPin='P10')


# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# RGB Animation functions
    """
    # Cycle effect
    # The cycle effect works similarly to the bounce effect.
    # There is a pixel on that runs through all the strip positions while the other pixels are off.
    def cycle(r, g, b, wait):
        for i in range(n):
            for j in range(n):
                np[j] = (0, 0, 0)
            np[i % n] = (r, g, b)
            np.write()
            time.sleep_ms(wait)
    """


    """
    def demo(self):
        self.rainbowCycle(0) # cycles through the rainbow --rainbowCycle(wait)
        self.rainbow(0) # cycles all lights through the rainbow --rainbow(wait)
        self.theaterChaseRainbow(50) # theater chase and rainbow at the same time --theaterChaseRainbow(wait)
        # chain.set_brightness(100) # change brighness (0-100), stays set --chain.set_brightness(0-100)
        self.colorWipe((255, 0, 0), 50) # color wipes red -- colorWipe(color, wait)
        self.colorWipe((0, 255, 0), 50) # color wipes Green -- colorWipe(color, wait)
        self.colorWipe((0, 0, 255), 50) # color wipes Blue -- colorWipe(color, wait)
        # chain.set_brightness(10) # change brightness (0-100), stays set --chain.set_brightness(0-100)
        self.theaterChase((127, 127, 127), 50) # White -- theaterChase(color, wait)
        self.theaterChase((127, 0, 0), 50) # Red -- theaterChase(color, wait)
        self.theaterChase((0, 0, 127), 50) # Blue -- theaterChase(color, wait)
        self.scrollWipe(50) # scrolls through --scrollWipe(wait)
        # self.sparkle((127, 127, 127), 5) # randomly sparkles two LEDs at a time -- sparkle(color, wait)
        #self.fade((255, 0, 0), 50) # fades a color in and out -- fade(color, wait)
        self.setColor(16, 32, 23) # set all LEDs to a color for a set time -- solid(color, wait)
    """
