Bastiaan Slee
Published © GPL3+

Energy Planner

Solar panels produce when there is sun, and during daytime I can earn money with my generated energy. When should I use the washing machine?

AdvancedFull instructions provided20 hours1,635

Things used in this project

Hardware components

Adafruit NeoPixel Stick - 8x RGB LED
×24
Adafruit Monochrome 1.3" 128x64 OLED graphic display
×1
Adafruit Analog 2-axis Joystick with Select Button
×1
Resistor 10k ohm
Resistor 10k ohm
For the Joystick wires
×1
Buzzer
Buzzer
×1
Capacitor 1000 µF
Capacitor 1000 µF
×1
Resistor 475 ohm
Resistor 475 ohm
×2

Software apps and online services

Maker service
IFTTT Maker service
Arduino IDE
Arduino IDE
SQLite
YR.no weather forecast
PyCharm
Firmata / FirmataPlus / PyMata
Qurrent energy prices
FastLED

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

Energy Planner - all code

All code, images, stylesheet, etc - nicely zipped

Schematics

Fritzing diagram

This is how to link all electronic components together.

Code

EnergyPlanner.py

Python
Main Python script that runs all functions and calls the helper scripts
#!/usr/bin/python
# -*- coding: utf8 -*-

import sqlite3
import qrcode
import time
import os.path
import requests
import thread
import json
import math
import fcntl, socket, struct #needed for getting IP Address and HW Address (=device ID)
from oled.device import sh1106
from oled.render import canvas
from PyMata_bsl.pymata import PyMata
from PIL import ImageFont, Image, ImageDraw

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import ssl

from EnergyPlanner_YRno import *
from EnergyPlanner_Qurrent import *
from EnergyPlanner_Webserver import *

# Values for pymata.fastled messaging
FASTLED_SHOW_NOW = 0
FASTLED_SHOW_LATER = 1


# Analog Input Pin
BUTTON_X = 10
BUTTON_Y = 9
# Digital Input Pin
BUTTON_SEL = 11

# Indices for data passed to callback function
PIN_MODE = 0  # This is the PyMata Pin MODE = ANALOG = 2 and DIGITAL = 0x20:
PIN_NUMBER = 1
DATA_VALUE = 2

GraphType = 'blank'
LED_HourOffset = -1

# IP Address (external facing network port)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
EnergyPlanner_ip_address = str(s.getsockname()[0])

# Hardware Address (MAC address of external facing network port)
ifname = 'apcli0'
info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', ifname[:15]))
EnergyPlanner_hw_address = str(':'.join(['%02x' % ord(char) for char in info[18:24]]).replace(":", ""))



# Data change callback functions
AnalogBase = {'0':0};
AnalogRecent = {'0':0};
AnalogTempVal = {'0':0};
i = 1
while i < 30:
    AnalogBase[str(i)] = 0
    AnalogRecent[str(i)] = 0
    AnalogTempVal[str(i)] = 0
    i += 1

cmd_joystick = 0
def joystick_move(data):
    global cmd_joystick
    if cmd_joystick == 0 and cmd_data == 0 and cmd_led == 0 and cmd_menu == 0:
        cmd_joystick = 1
        AnalogTempVal[str(data[PIN_NUMBER])] = AnalogRecent[str(data[PIN_NUMBER])]
        if (AnalogBase[str(data[PIN_NUMBER])] == 0):
            # Get the base value of the untouched joystick
            AnalogBase[str(data[PIN_NUMBER])] = data[DATA_VALUE]
            print("Analog Data: Pin=" + str(data[PIN_NUMBER]) + " Data Value=0")

        if (AnalogBase[str(data[PIN_NUMBER])] - data[DATA_VALUE] > 50):
            AnalogRecent[str(data[PIN_NUMBER])] = 1
        elif (AnalogBase[str(data[PIN_NUMBER])] - data[DATA_VALUE] <= 50 and AnalogBase[str(data[PIN_NUMBER])] - data[DATA_VALUE] >= -50):
            AnalogRecent[str(data[PIN_NUMBER])] = 0
        elif (AnalogBase[str(data[PIN_NUMBER])] - data[DATA_VALUE] < -50):
            AnalogRecent[str(data[PIN_NUMBER])] = -1

        if AnalogTempVal[str(data[PIN_NUMBER])] <> AnalogRecent[str(data[PIN_NUMBER])] and AnalogRecent[str(data[PIN_NUMBER])] <> 0:
            print("Analog Data: Pin=" + str(data[PIN_NUMBER]) + " Data Value=" + str(AnalogRecent[str(data[PIN_NUMBER])]))

            BTN_State = ''
            if data[PIN_NUMBER] == BUTTON_X and AnalogRecent[str(data[PIN_NUMBER])] == 1: BTN_State = 'Up'
            if data[PIN_NUMBER] == BUTTON_X and AnalogRecent[str(data[PIN_NUMBER])] == -1: BTN_State = 'Down'
            if data[PIN_NUMBER] == BUTTON_Y and AnalogRecent[str(data[PIN_NUMBER])] == 1: BTN_State = 'Right'
            if data[PIN_NUMBER] == BUTTON_Y and AnalogRecent[str(data[PIN_NUMBER])] == -1: BTN_State = 'Left'

            if BTN_State != '':
                runScreen(None, BTN_State)
                time.sleep(0.2)
        cmd_joystick = 0


def joystick_push(data):
    global cmd_joystick
    if cmd_joystick == 0 and cmd_data == 0 and cmd_led == 0 and cmd_menu == 0:
        cmd_joystick = 1
        if data[DATA_VALUE] == 0:
            print("Digital Data: Pin=" + str(data[PIN_NUMBER]) + " Data Value=" + str(data[DATA_VALUE]))
            BTN_State = 'Press'
            runScreen(None, BTN_State)
            time.sleep(0.2)
        cmd_joystick = 0


MenuLine = 0
menu_screen = 0
menu_next = 0

DeviceScheduleLine = 0
DeviceScheduleFromLine = 0
DeviceScheduleTotalLine = 0
DeviceScheduleSelected = ''

menu_time = time.localtime(time.mktime(time.localtime()) - 100)
EnergySelectedDatetime = time.localtime()


# Function for menu and others
def runScreen(GotoScreen=None, ButtonDirection=None):
    global cmd_menu, LED_HourOffset
    cmd_menu = 1
    global menu_screen, menu_next, MenuLine, EnergySelectedDatetime, menu_time, DeviceScheduleLine, DeviceScheduleFromLine, DeviceScheduleTotalLine, DeviceScheduleSelected
    connRunScreen = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')

    # Check if Joystick is used
    if GotoScreen != None:
        BTN_State = 'Neutral'
        menu_screen = GotoScreen
        menu_next = GotoScreen

        if GotoScreen == 0:  # Homescreen
            MenuLine = 0

        if GotoScreen == 10:  # Energy Time
            ForecastLine = 1
            ForecastFromLine = 1

    else:
        BTN_State = ButtonDirection


    if menu_screen == 0:  # Clock page
        if BTN_State == 'Up' or BTN_State == 'Down':
            LED_HourOffset = -1
            if GraphType == 'solar':
                ShowPriceData('Fast', 'all')
            elif GraphType == 'price':
                ShowSolarData('Fast', 'all')
            menu_next = 0

        elif BTN_State == 'Right':
            LED_HourOffset = 0
            if GraphType == 'solar':
                ShowSolarData('Fast', 'highlight')
            elif GraphType == 'price':
                ShowPriceData('Fast', 'highlight')
            menu_next = 10

        elif BTN_State == 'Left':
            LED_HourOffset = 23
            if GraphType == 'solar':
                ShowSolarData('Fast', 'highlight')
            elif GraphType == 'price':
                ShowPriceData('Fast', 'highlight')
            menu_next = 10

        elif BTN_State == 'Press':
            menu_next = 2


    elif menu_screen == 1:  # QR setup link
        menu_next = 0



    elif menu_screen == 2:  # Main menu page
        if BTN_State == 'Up':
            if PartyLine > 0: PartyLine -= 1
            menu_next = 2
        elif BTN_State == 'Down':
            PartyLine += 1
            menu_next = 2
        elif BTN_State == 'Left':
            menu_next = 0
        elif BTN_State == 'Right' or BTN_State == 'Press':
            menu_next = 0




    elif menu_screen == 10:  # Detailed graph screen
        if BTN_State == 'Up' or BTN_State == 'Down':
            if GraphType == 'solar':
                ShowPriceData('Fast', 'all')
            elif GraphType == 'price':
                ShowSolarData('Fast', 'all')
            menu_next = 10

        elif BTN_State == 'Right':
            if LED_HourOffset < 23:
                LED_HourOffset = LED_HourOffset + 1
                if GraphType == 'solar':
                    ShowSolarData('Fast', 'highlight')
                elif GraphType == 'price':
                    ShowPriceData('Fast', 'highlight')
            menu_next = 10

        elif BTN_State == 'Left':
            if LED_HourOffset > 0:
                LED_HourOffset = LED_HourOffset - 1
                if GraphType == 'solar':
                    ShowSolarData('Fast', 'highlight')
                elif GraphType == 'price':
                    ShowPriceData('Fast', 'highlight')
            menu_next = 10

        elif BTN_State == 'Press':
            menu_next = 11



    elif menu_screen == 11:  # Device Selection page
        if BTN_State == 'Up':
            if DeviceScheduleLine > 1: DeviceScheduleLine -= 1
            menu_next = 11

        elif BTN_State == 'Down':
            DeviceScheduleLine += 1
            menu_next = 11

        elif BTN_State == 'Right':
            if LED_HourOffset < 23:
                LED_HourOffset = LED_HourOffset + 1
                if GraphType == 'solar':
                    ShowSolarData('Fast', 'highlight')
                elif GraphType == 'price':
                    ShowPriceData('Fast', 'highlight')
            menu_next = 11

        elif BTN_State == 'Left':
            if LED_HourOffset > 0:
                LED_HourOffset = LED_HourOffset - 1
                if GraphType == 'solar':
                    ShowSolarData('Fast', 'highlight')
                elif GraphType == 'price':
                    ShowPriceData('Fast', 'highlight')
            menu_next = 11

        elif BTN_State == 'Press':
            tempRowCount = 0
            cursorRunScreen = connRunScreen.execute("SELECT count(Date) FROM EnergyDeviceSchedule WHERE Date = '" + time.strftime("%Y-%m-%d %H:00",EnergySelectedDatetime) + "' AND IFTTTEventName = '" + DeviceScheduleSelected + "';")
            for row in cursorRunScreen:
                tempRowCount = row[0]

            if tempRowCount == 0:
                connRunScreen.execute("INSERT INTO EnergyDeviceSchedule (Date, IFTTTEventName, Status) SELECT '" + time.strftime("%Y-%m-%d %H:00",EnergySelectedDatetime) + "', '" + DeviceScheduleSelected + "', 1;")
            else:
                connRunScreen.execute("DELETE FROM EnergyDeviceSchedule WHERE Date = '" + time.strftime("%Y-%m-%d %H:00",EnergySelectedDatetime) + "' AND IFTTTEventName = '" + DeviceScheduleSelected + "' AND Status <> 2;")
            connRunScreen.commit()

            DeviceScheduleLine = 1
            menu_next = 11



    # date of the selected bar should be current hour or future
    EnergySelectedDatetime = time.localtime(time.mktime(time.localtime()) + (60 * EnergySettingsUTCoffset) - (time.mktime(time.localtime()) - time.mktime(time.gmtime())) + (LED_HourOffset * 60 * 60))

    print "Menu: " + str(menu_next)

    # Text to be rotated...
    rotate_text = ">"
    img_txt = Image.new('1', font_small.getsize(rotate_text))
    draw_txt = ImageDraw.Draw(img_txt)
    draw_txt.text((0, 0), rotate_text, font=font_small, fill=255)




    if menu_next == 0:  # home screen to show temperatures
        with canvas(device) as draw:
            draw.bitmap((0, 0), logo, fill=1)
            draw.text((69, 17), time.strftime("%H:%M"), font=font_clock, fill=255)

            if GraphType <> 'blank':
                draw.text((65, 52), 'graph: ' + GraphType, font=font_small, fill=255)



    elif menu_next == 1:  # QR code for web-setup
        img = GenerateQR('')
        with canvas(device) as draw:
            draw.rectangle((0, 0, device.width - 1, device.height - 1), outline=0, fill=0)
            draw.bitmap((0, 0), img, fill=1)
            draw.text((60, 0), 'Scan the', font=font_small, fill=255)
            draw.text((60, 12), 'QR code', font=font_small, fill=255)

            IpTextW, IpTextH = font_small.getsize('https://'+EnergyPlanner_ip_address+'/')
            draw.text((device.width - IpTextW, 54), 'https://'+EnergyPlanner_ip_address+'/', font=font_small, fill=255)



    elif menu_next == 10:  # Hourly detail
        cursorRunScreen = connRunScreen.execute("SELECT ifnull(SunHeight,''), ifnull(CloudCover,''), ifnull(CloudEffect,''), ifnull(PriceKwh,''), ifnull(PriceLed0,'?')"
                                        " FROM EnergyForecast WHERE Date >= '" + time.strftime("%Y-%m-%d %H:00",EnergySelectedDatetime) + "' ORDER BY Date ASC LIMIT 1;")

        for row in cursorRunScreen:
            with canvas(device) as draw:
                draw.text((0, -2), "Hour Details", font=font_small, fill=255)
                draw.text((100, -2), time.strftime("%H:%M"), font=font_small, fill=255)
                draw.line((0, 9, device.width - 1, 9), fill=255)

                draw.text((0, 13), time.strftime("%Y-%m-%d  %H:00-%H:59",EnergySelectedDatetime), font=font_small, fill=255)

                draw.text((0, 27), 'Sun:', font=font_small, fill=255)
                draw.text((40, 27), 'Clouds:', font=font_small, fill=255)
                draw.text((90, 27), 'Power:', font=font_small, fill=255)

                draw.text((0, 39), str(round(row[0],1)) +'%', font=font_small, fill=255)
                draw.text((40, 39), str(round(row[1],1)) +'%', font=font_small, fill=255)
                draw.text((90, 39), str(round(row[2],1)) +'%', font=font_small, fill=255)

                draw.text((0, 53), 'Kwh Price:', font=font_small, fill=255)
                draw.text((60, 53), str(row[3]) + ' (' + str(row[4]) + ')', font=font_small, fill=255)



    elif menu_next == 11:  # Schedule detail
        DeviceScheduleTotalLine = 0
        cursorRunScreen = connRunScreen.execute("SELECT count(IFTTTEventName) FROM EnergyDevices")
        for row in cursorRunScreen:
            if (DeviceScheduleLine > row[0]): DeviceScheduleLine = row[0]
            DeviceScheduleTotalLine = row[0]

        if (DeviceScheduleLine == 0): DeviceScheduleLine = 1
        if (DeviceScheduleLine <= 3): DeviceScheduleFromLine = 1
        if (DeviceScheduleLine > 3): DeviceScheduleFromLine = (DeviceScheduleLine - 2)

        with canvas(device) as draw:
            draw.text((0, -2), "Scheduler", font=font_small, fill=255,)
            draw.text((100, -2), time.strftime("%H:%M"), font=font_small, fill=255,)
            draw.line((0, 9, device.width - 1, 9), fill=255)

            draw.text((0, 13), time.strftime("%Y-%m-%d  %H:00-%H:59", EnergySelectedDatetime), font=font_small, fill=255)

            cursorRunScreen = connRunScreen.execute("SELECT ifnull(EnergyDevices.Description,''), ifnull(EnergyDevices.IFTTTEventName,''), ifnull(EnergyDeviceSchedule.Status,99)"
                                            " FROM EnergyDevices LEFT OUTER JOIN EnergyDeviceSchedule on (EnergyDevices.IFTTTEventName = EnergyDeviceSchedule.IFTTTEventName and EnergyDeviceSchedule.Date = '" + time.strftime("%Y-%m-%d %H:00",EnergySelectedDatetime) + "') ORDER BY ifnull(EnergyDeviceSchedule.Status,99) ASC, ifnull(EnergyDevices.Description,'') ASC LIMIT 3 OFFSET " + str(DeviceScheduleFromLine-1) + ";")

            i = DeviceScheduleFromLine
            for row in cursorRunScreen:
                if i == (DeviceScheduleLine):
                    draw.rectangle((0, 24 + ((i - DeviceScheduleFromLine) * 12), device.width - 12, 24 + ((i - DeviceScheduleFromLine) * 12) + 12), outline=255, fill=255)
                    draw.ellipse((1, 25 + ((i - DeviceScheduleFromLine) * 12),1 + 10, 25 + ((i - DeviceScheduleFromLine) * 12) + 10), outline=0, fill=255)
                    if str(row[2]) == '1': draw.ellipse((4, 28 + ((i - DeviceScheduleFromLine) * 12), 4 + 4, 28 + ((i - DeviceScheduleFromLine) * 12) + 4), outline=0, fill=0)
                    if str(row[2]) == '2': draw.ellipse((5, 29 + ((i - DeviceScheduleFromLine) * 12), 5 + 2, 29 + ((i - DeviceScheduleFromLine) * 12) + 2), outline=0, fill=0)
                    draw.text((15, 25 + ((i - DeviceScheduleFromLine) * 12)), row[0], font=font_small, fill=0)
                    DeviceScheduleSelected = row[1]
                else:
                    draw.ellipse((1, 25 + ((i - DeviceScheduleFromLine) * 12), 1 + 10, 25 + ((i - DeviceScheduleFromLine) * 12) + 10), outline=255, fill=0)
                    if str(row[2]) == '1': draw.ellipse((4, 28 + ((i - DeviceScheduleFromLine) * 12), 4 + 4, 28 + ((i - DeviceScheduleFromLine) * 12) + 4), outline=255, fill=255)
                    if str(row[2]) == '2': draw.ellipse((5, 29 + ((i - DeviceScheduleFromLine) * 12), 5 + 2, 29 + ((i - DeviceScheduleFromLine) * 12) + 2), outline=255, fill=255)
                    draw.text((15, 25 + ((i - DeviceScheduleFromLine) * 12)), row[0], font=font_small, fill=255)
                i += 1

            if DeviceScheduleFromLine > 1:
                draw.bitmap((device.width - 11, 21), img_txt.rotate(90, expand=1), fill=1)
            else:
                draw.text((device.width - 6, 18), '-', font=font_small, fill=255)

            if DeviceScheduleTotalLine > i-1:
                draw.bitmap((device.width - 12, device.height - 6), img_txt.rotate(270, expand=1), fill=1)
            else:
                draw.text((device.width - 6, device.height - 8), '-', font=font_small, fill=255)

            draw.line((device.width - 5, 24 , device.width - 5, 60), fill=255)



    menu_screen = menu_next
    connRunScreen.close()
    menu_time = time.localtime()
    time.sleep(0.5)
    cmd_menu = 0







def GenerateQR(url):
    # https://github.com/lincolnloop/python-qrcode
    qr = qrcode.QRCode(
        version=1,
        error_correction=qrcode.constants.ERROR_CORRECT_L,
        box_size=2,
        border=1,
    )
    qr.add_data('https://' + EnergyPlanner_ip_address + '/' + url)
    qr.make(fit=True)

    img = qr.make_image()
    return img



def CheckSetup(threadName, sleepTime):
    SetupComplete = -1
    while cmd_exit == 0 and SetupComplete <> 0:
        SetupComplete = 0
        cursor = conn.execute("SELECT IFTTTMakerKey, YrCity FROM EnergySettings")
        for row in cursor:
            if row[0] == '': SetupComplete += 1
            if row[1] == '': SetupComplete += 1

        if SetupComplete > 0:
            print "setup needed"
            img = GenerateQR('s')

            with canvas(device) as draw:
                draw.rectangle((0, 0, device.width - 1, device.height - 1), outline=0, fill=0)
                draw.bitmap((0, 0), img, fill=1)
                draw.text((60, 0), 'Scan the', font=font_small, fill=255)
                draw.text((60, 12), 'QR code', font=font_small, fill=255)
                draw.text((60, 24), 'to start!', font=font_small, fill=255)

                IpTextW, IpTextH = font_small.getsize('https://'+EnergyPlanner_ip_address+'/')
                draw.text((device.width - IpTextW, 54), 'https://'+EnergyPlanner_ip_address+'/', font=font_small, fill=255)

            i = 0
            time.sleep(5)

        else:
            print "setup complete"
            with canvas(device) as draw:
                draw.bitmap((32, 0), logo, fill=1)
            time.sleep(1)






def UpdateLED(LedStick, LedPos, LedColor, Bright, UpdateLed):

    # Colors for PRICE part:
    # L = green for low price
    if (LedColor == 'L' and Bright == 0):
        firmata.fastled_color(LedStick, LedPos, 0, 20, 0, UpdateLed)
    elif (LedColor == 'L' and Bright == 1):
        firmata.fastled_color(LedStick, LedPos, 0, 200, 0, UpdateLed)

    # M = orange for medium price
    elif (LedColor == 'M' and Bright == 0):
        firmata.fastled_color(LedStick, LedPos, 15, 4, 0, UpdateLed)
    elif (LedColor == 'M' and Bright == 1):
        firmata.fastled_color(LedStick, LedPos, 150, 40, 0, UpdateLed)

    # H = red for high price
    elif (LedColor == 'H' and Bright == 0):
        firmata.fastled_color(LedStick, LedPos, 12, 0, 0, UpdateLed)
    elif (LedColor == 'H' and Bright == 1):
        firmata.fastled_color(LedStick, LedPos, 120, 0, 0, UpdateLed)



    # Colors for SOLAR part:
    # N = dim blue at night
    elif (LedColor == 'N' and Bright == 0):
        firmata.fastled_color(LedStick, LedPos, 0, 0, 1, UpdateLed)
    elif (LedColor == 'N' and Bright == 1):
        firmata.fastled_color(LedStick, LedPos, 0, 0, 20, UpdateLed)

    # S = bright yellow for sunshine
    elif (LedColor == 'S' and Bright == 0):
        firmata.fastled_color(LedStick, LedPos, 20, 10, 0, UpdateLed)
    elif (LedColor == 'S' and Bright == 1):
        firmata.fastled_color(LedStick, LedPos, 200, 100, 0, UpdateLed)

    # Y = dim yellow for cloudcover
    elif (LedColor == 'Y' and Bright == 0):
        firmata.fastled_color(LedStick, LedPos, 2, 1, 0, UpdateLed)
    elif (LedColor == 'Y' and Bright == 1):
        firmata.fastled_color(LedStick, LedPos, 20, 10, 0, UpdateLed)


    # O = purple for no info
    elif (LedColor == 'O' and Bright == 0):
        firmata.fastled_color(LedStick, LedPos, 1, 0, 1, UpdateLed)
    elif (LedColor == 'O' and Bright == 1):
        firmata.fastled_color(LedStick, LedPos, 20, 0, 20, UpdateLed)


def ShowSolarData(UpdateMethod,UpdateRange):
    global cmd_led, GraphType, LED_HourOffset

    cmd_led = 1
    GraphType = 'solar'
    #runScreen(0, None) #Update clock

    connShowData = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')

    if (LED_HourOffset == 0 and UpdateRange == 'highlight'):
        # First bar of graph
        LedStick = 0
        # Get time of last whole hour
        local_time = time.localtime(time.mktime(time.localtime()) + (60 * EnergySettingsUTCoffset) - (time.mktime(time.localtime()) - time.mktime(time.gmtime())))
        cursor = connShowData.execute("SELECT ifnull(SolarLed0,''), ifnull(SolarLed1,''), ifnull(SolarLed2,''), ifnull(SolarLed3,''), ifnull(SolarLed4,''), ifnull(SolarLed5,''), ifnull(SolarLed6,''), ifnull(SolarLed7,'')"
                                      " FROM EnergyForecast WHERE Date >= '" + time.strftime("%Y-%m-%d %H:00", local_time) + "' ORDER BY Date ASC LIMIT 2;")

    elif (LED_HourOffset > 0 and LED_HourOffset < 23 and UpdateRange == 'highlight'):
        # All other 22 bars of graph
        LedStick = LED_HourOffset - 1
        # Get time of last whole hour
        local_time = time.localtime(time.mktime(time.localtime()) + (60 * EnergySettingsUTCoffset) - (time.mktime(time.localtime()) - time.mktime(time.gmtime())) + ((LED_HourOffset - 1) * 60 * 60))
        cursor = connShowData.execute("SELECT ifnull(SolarLed0,''), ifnull(SolarLed1,''), ifnull(SolarLed2,''), ifnull(SolarLed3,''), ifnull(SolarLed4,''), ifnull(SolarLed5,''), ifnull(SolarLed6,''), ifnull(SolarLed7,'')"
                                      " FROM EnergyForecast WHERE Date >= '" + time.strftime("%Y-%m-%d %H:00",local_time) + "' ORDER BY Date ASC LIMIT 3;")

    elif (LED_HourOffset == 23 and UpdateRange == 'highlight'):
        # Last bar of graph
        LedStick = 22
        # Get time of last whole hour
        local_time = time.localtime(time.mktime(time.localtime()) + (60 * EnergySettingsUTCoffset) - (time.mktime(time.localtime()) - time.mktime(time.gmtime())) + (22 * 60 * 60))
        cursor = connShowData.execute("SELECT ifnull(SolarLed0,''), ifnull(SolarLed1,''), ifnull(SolarLed2,''), ifnull(SolarLed3,''), ifnull(SolarLed4,''), ifnull(SolarLed5,''), ifnull(SolarLed6,''), ifnull(SolarLed7,'')"
                                      " FROM EnergyForecast WHERE Date >= '" + time.strftime("%Y-%m-%d %H:00", local_time) + "' ORDER BY Date ASC LIMIT 2;")

    elif (LED_HourOffset == -1 or UpdateRange == 'all'):
        # Normal graph
        LedStick = 0
        # Get time of last whole hour
        local_time = time.localtime(time.mktime(time.localtime()) + (60 * EnergySettingsUTCoffset) - (time.mktime(time.localtime()) - time.mktime(time.gmtime())))
        cursor = connShowData.execute("SELECT ifnull(SolarLed0,''), ifnull(SolarLed1,''), ifnull(SolarLed2,''), ifnull(SolarLed3,''), ifnull(SolarLed4,''), ifnull(SolarLed5,''), ifnull(SolarLed6,''), ifnull(SolarLed7,'')"
                                      " FROM EnergyForecast WHERE Date >= '" + time.strftime("%Y-%m-%d %H:00",local_time) + "' ORDER BY Date ASC LIMIT 24;")


    for row in cursor:
        if UpdateMethod == 'Animation':
            firmata.fastled_clear_bar(LedStick)
            time.sleep(0.1)

            for num in range(0, 8):
                if (row[num] <> '' and row[num] <> 'o'):
                    UpdateLED(LedStick, num, row[num], 0, FASTLED_SHOW_NOW)
                    time.sleep(0.1)

            time.sleep(0.3)

        if UpdateMethod == 'Fast':
            if LED_HourOffset == -1 or UpdateRange == 'all':
                firmata.fastled_clear_bar(LedStick)
                time.sleep(0.05)

            for num in range(0, 8):
                if (row[num] <> '' and row[num] <> 'o'):
                    if LedStick == LED_HourOffset:
                        UpdateLED(LedStick, num, row[num], 1, FASTLED_SHOW_LATER)
                    else:
                        UpdateLED(LedStick, num, row[num], 0, FASTLED_SHOW_LATER)
                    time.sleep(0.01)

            firmata.fastled_show()
            time.sleep(0.05)

        LedStick += 1

    connShowData.close()
    cmd_led = 0


def ShowPriceData(UpdateMethod,UpdateRange):
    global cmd_led, GraphType, LED_HourOffset

    cmd_led = 1
    GraphType = 'price'
    #runScreen(0, None) #Update clock

    connShowData = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')

    if (LED_HourOffset == 0 and UpdateRange == 'highlight'):
        # First bar of graph
        LedStick = 0
        # Get time of last whole hour
        local_time = time.localtime(time.mktime(time.localtime()) + (60 * EnergySettingsUTCoffset) - (time.mktime(time.localtime()) - time.mktime(time.gmtime())))
        cursor = connShowData.execute("SELECT ifnull(PriceLed0,'O'), ifnull(PriceLed1,''), ifnull(PriceLed2,''), ifnull(PriceLed3,''), ifnull(PriceLed4,''), ifnull(PriceLed5,''), ifnull(PriceLed6,''), ifnull(PriceLed7,'')"
                                      " FROM EnergyForecast WHERE Date >= '" + time.strftime("%Y-%m-%d %H:00", local_time) + "' ORDER BY Date ASC LIMIT 2;")

    elif (LED_HourOffset > 0 and LED_HourOffset < 23 and UpdateRange == 'highlight'):
        # All other 22 bars of graph
        LedStick = LED_HourOffset - 1
        # Get time of last whole hour
        local_time = time.localtime(time.mktime(time.localtime()) + (60 * EnergySettingsUTCoffset) - (time.mktime(time.localtime()) - time.mktime(time.gmtime())) + ((LED_HourOffset - 1) * 60 * 60))
        cursor = connShowData.execute("SELECT ifnull(PriceLed0,'O'), ifnull(PriceLed1,''), ifnull(PriceLed2,''), ifnull(PriceLed3,''), ifnull(PriceLed4,''), ifnull(PriceLed5,''), ifnull(PriceLed6,''), ifnull(PriceLed7,'')"
                                      " FROM EnergyForecast WHERE Date >= '" + time.strftime("%Y-%m-%d %H:00",local_time) + "' ORDER BY Date ASC LIMIT 3;")

    elif (LED_HourOffset == 23 and UpdateRange == 'highlight'):
        # Last bar of graph
        LedStick = 22
        # Get time of last whole hour
        local_time = time.localtime(time.mktime(time.localtime()) + (60 * EnergySettingsUTCoffset) - (time.mktime(time.localtime()) - time.mktime(time.gmtime())) + (22 * 60 * 60))
        cursor = connShowData.execute("SELECT ifnull(PriceLed0,'O'), ifnull(PriceLed1,''), ifnull(PriceLed2,''), ifnull(PriceLed3,''), ifnull(PriceLed4,''), ifnull(PriceLed5,''), ifnull(PriceLed6,''), ifnull(PriceLed7,'')"
                                      " FROM EnergyForecast WHERE Date >= '" + time.strftime("%Y-%m-%d %H:00", local_time) + "' ORDER BY Date ASC LIMIT 2;")

    elif (LED_HourOffset == -1 or UpdateRange == 'all'):
        # Normal graph
        LedStick = 0
        # Get time of last whole hour
        local_time = time.localtime(time.mktime(time.localtime()) + (60 * EnergySettingsUTCoffset) - (time.mktime(time.localtime()) - time.mktime(time.gmtime())))
        cursor = connShowData.execute("SELECT ifnull(PriceLed0,'O'), ifnull(PriceLed1,''), ifnull(PriceLed2,''), ifnull(PriceLed3,''), ifnull(PriceLed4,''), ifnull(PriceLed5,''), ifnull(PriceLed6,''), ifnull(PriceLed7,'')"
                                      " FROM EnergyForecast WHERE Date >= '" + time.strftime("%Y-%m-%d %H:00",local_time) + "' ORDER BY Date ASC LIMIT 24;")


    for row in cursor:
        if UpdateMethod == 'Animation':
            firmata.fastled_clear_bar(LedStick)
            time.sleep(0.1)

            for num in range(0, 8):
                if (row[num] <> '' and row[num] <> 'o'):
                    UpdateLED(LedStick, num, row[num], 0, FASTLED_SHOW_NOW)
                    time.sleep(0.1)

            time.sleep(0.3)

        if UpdateMethod == 'Fast':
            if LED_HourOffset == -1 or UpdateRange == 'all':
                firmata.fastled_clear_bar(LedStick)
                time.sleep(0.05)

            for num in range(0, 8):
                if (row[num] <> '' and row[num] <> 'o'):
                    if LedStick == LED_HourOffset:
                        UpdateLED(LedStick, num, row[num], 1, FASTLED_SHOW_LATER)
                    else:
                        UpdateLED(LedStick, num, row[num], 0, FASTLED_SHOW_LATER)
                    time.sleep(0.01)

            firmata.fastled_show()
            time.sleep(0.05)

        LedStick += 1

    connShowData.close()
    cmd_led = 0




def getData(threadName, sleepTime):
    global cmd_data

    while cmd_exit == 0:
        if cmd_joystick == 0 and cmd_led == 0 and cmd_menu == 0:
            cmd_data = 1
            firmata.digital_write(13, 1)

            # Get YR and Qurrent data
            getYRWeatherData()
            getQurrentData()

            # BEEP!
            firmata.play_tone(20, 0, 100, 200)
            time.sleep(2)

            firmata.digital_write(13, 0)
            cmd_data = 0
            time.sleep(sleepTime)
        else:
            time.sleep(10)




def sendIFTTTtrigger(IFTTTEventName, IFTTTValue1, IFTTTValue2, IFTTTValue3):
    IFTTTValue = {}
    IFTTTValue["value1"] = IFTTTValue1
    IFTTTValue["value2"] = IFTTTValue2
    IFTTTValue["value3"] = IFTTTValue3

    requests.post("https://maker.ifttt.com/trigger/" + IFTTTEventName + "/with/key/" + IFTTTMakerKey, data=IFTTTValue)


clock_hour = time.localtime(time.mktime(time.strptime(time.strftime("%Y-%m-%d %H:00", time.localtime()), "%Y-%m-%d %H:%M")) - (60 * 60))
clock_time = time.localtime()
def UpdateClock(threadName, sleepTime):
    global clock_time, menu_screen, clock_hour
    while cmd_exit == 0:
        if cmd_joystick == 0 and cmd_data == 0 and cmd_led == 0 and cmd_menu == 0:
            if (clock_time < time.localtime(time.mktime(time.localtime()) - sleepTime)) and (menu_time < time.localtime(time.mktime(time.localtime()) - 30)):
                with canvas(device) as draw:
                    draw.bitmap((0, 0), logo, fill=1)
                    draw.text((69, 17), time.strftime("%H:%M"), font=font_clock, fill=255)

                    if GraphType <> 'blank':
                        draw.text((65, 52), 'graph: ' + GraphType, font=font_small, fill=255)
                menu_screen = 0

                if clock_hour <> time.localtime(time.mktime(time.strptime(time.strftime("%Y-%m-%d %H:00", time.localtime()), "%Y-%m-%d %H:%M"))):
                    clock_hour = time.localtime(time.mktime(time.strptime(time.strftime("%Y-%m-%d %H:00", time.localtime()), "%Y-%m-%d %H:%M")))
                    triggerIFTTT()

        time.sleep(sleepTime)



def triggerIFTTT():
    connIFTTT = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')

    # Send price info
    local_time = time.localtime(time.mktime(time.localtime()) + (60 * EnergySettingsUTCoffset) - (time.mktime(time.localtime()) - time.mktime(time.gmtime())))
    cursor = connIFTTT.execute("SELECT ifnull(PriceKwh,'0'), ifnull(PriceLed0,'?') FROM EnergyForecast WHERE Date = '" + time.strftime("%Y-%m-%d %H:00",local_time) + "';")
    for row in cursor:
        sendIFTTTtrigger('EP_hourly', 'Starting ' + time.strftime("%H:00",local_time) + ' the Kwh price is ' + str(row[0]) + ' (' + row[1] + ')', '', '')
        if str(row[1]) == 'L': sendIFTTTtrigger('EP_hourly_HUE', 'Change color', 'alert', '33cc33')
        if str(row[1]) == 'M': sendIFTTTtrigger('EP_hourly_HUE', 'Change color', 'alert', 'ff6600')
        if str(row[1]) == 'H': sendIFTTTtrigger('EP_hourly_HUE', 'Change color', 'alert', 'cc0000')



    cursor = connIFTTT.execute("SELECT EnergyDevices.IFTTTEventName, EnergyDevices.IFTTTValue1, EnergyDevices.IFTTTValue2, EnergyDevices.IFTTTValue3, EnergyDevices.BeepType, EnergyDeviceSchedule.Status FROM EnergyDeviceSchedule inner join EnergyDevices on (EnergyDevices.IFTTTEventName = EnergyDeviceSchedule.IFTTTEventName) WHERE Date = '" + time.strftime("%Y-%m-%d %H:00",local_time) + "' AND EnergyDeviceSchedule.Status = 1;")
    for row in cursor:
        sendIFTTTtrigger(str(row[0]), str(row[1]), str(row[2]), str(row[3]))

        if str(row[4]) == '1':
            firmata.play_tone(20,0,300,200)
        elif str(row[4]) == '2':
            firmata.play_tone(20,0,400,200)
        elif str(row[4]) == '3':
            firmata.play_tone(20,0,500,200)
        elif str(row[4]) == '4':
            firmata.play_tone(20,0,600,200)

        connIFTTT.execute("UPDATE EnergyDeviceSchedule SET Status = 2 WHERE IFTTTEventName = '" + str(row[0]) + "' AND Date = '" + time.strftime("%Y-%m-%d %H:00",local_time) + "';")
    connIFTTT.commit()

    connIFTTT.close()




def WebServer(threadName, sleepTime):
    global cmd_webserver
    cmd_webserver = 1

    server = HTTPServer(('', 443), myHandler)
    server.socket = ssl.wrap_socket(server.socket, server_side=True, certfile='/root/EnergyPlanner/webserver/certificate.pem.crt', keyfile='/root/EnergyPlanner/webserver/private.pem.key')
    print 'Started httpserver on port 443'

    # Wait forever for incoming http requests, but doesn't work in thread
    #server.serve_forever()

    while cmd_exit == 0:
        # To create a server that doesnt run forever, but until some condition is fulfilled:
        server.handle_request()
    else:
        print 'Shutting down the web server'
        server.socket.close()

    cmd_webserver = 0



# Start actual program
if __name__ == '__main__':
    #try:
    # This is the exit trigger for the threads and while loops
    cmd_exit = 0
    cmd_led = 0
    cmd_data = 0
    cmd_menu = 0
    cmd_webserver = 0

    # Run the WebServicer function in a separate thread so that it does not hold up anything else, needs to be up before checking setup
    thread.start_new_thread(WebServer, ('',0))

    # create a PyMata instance
    firmata = None
    firmata = PyMata("/dev/ttyS0")

    # Lower the interrupt speed at the MPU side
    firmata.set_sampling_interval(250)
    time.sleep(0.2)

    # configure firmata for I2C with OLED SH1106 display
    firmata.i2c_config(0, firmata.DIGITAL, 3, 2)
    device = sh1106(firmata, address=0x3C)
    # See https://github.com/Schrottratte/sh1106 for examples

    # Load font for OLED display.
    # Some other nice fonts to try: http://www.dafont.com/bitmap.php
    # font = ImageFont.load_default()
    font_clock = ImageFont.truetype('/root/EnergyPlanner/C&C Red Alert [INET].ttf', 30)
    font_large = ImageFont.truetype('/root/EnergyPlanner/C&C Red Alert [INET].ttf', 22)
    font_small = ImageFont.truetype('/root/EnergyPlanner/C&C Red Alert [INET].ttf', 12)


    # Set the pin to digital input to receive button presses
    firmata.set_pin_mode(BUTTON_SEL, firmata.INPUT, firmata.DIGITAL, joystick_push)
    # Set the pin to analog input to receive joystick moves
    firmata.set_pin_mode(BUTTON_X, firmata.INPUT, firmata.ANALOG, joystick_move)
    firmata.set_pin_mode(BUTTON_Y, firmata.INPUT, firmata.ANALOG, joystick_move)



    # And show welcome image
    logo = Image.open('/root/EnergyPlanner/EnergyPlanner_icon.png')
    with canvas(device) as draw:
        draw.bitmap((32, 0), logo, fill=1)
    time.sleep(2)

    # SQL connection
    conn = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')

    # Clear all LEDs, set brightness and give all the same background color
    firmata.fastled_clear_all()
    time.sleep(1)
    firmata.fastled_brightness(255)
    time.sleep(0.2)
    firmata.fastled_all(1, 1, 1)
    time.sleep(1)

    # BEEP!
    firmata.play_tone(20,0,100,200)
    time.sleep(0.3)
    firmata.play_tone(20,0,500,200)
    time.sleep(0.2)

    # Check of the setup is completed, otherwise show QR code
    CheckSetup("Check Setup", 10)

    # One time getting he YR related setup
    getYRSetupData()

    EnergySettingsUTCoffset = 0
    cursor = conn.execute("SELECT YrUTCoffset, IFTTTMakerKey FROM EnergySettings LIMIT 1")
    for row in cursor:
        EnergySettingsUTCoffset = row[0]
        IFTTTMakerKey = row[1]

    EnergySelectedDatetime = time.strftime("%Y-%m-%d %H:00", time.localtime(time.mktime(time.localtime()) + (60 * EnergySettingsUTCoffset) - (time.mktime(time.localtime()) - time.mktime(time.gmtime()))))

    # run the getData function in a separate thread, running every 600 seconds
    thread.start_new_thread(getData, ('',600))
    time.sleep(1)

    firmata.fastled_clear_all()
    time.sleep(1)

    # run the UpdateClock function in a separate thread, running every 10 seconds (so it's max 10 seconds off, but doesn't consume all processing power)
    thread.start_new_thread(UpdateClock, ('',10))

    # Keep looping to not exit the program
    try:
        while True:
            while cmd_joystick == 1 or cmd_data == 1 or cmd_led == 1 or cmd_menu == 1 or (menu_time > time.localtime(time.mktime(time.localtime()) - 30)):
                time.sleep(1)
            else:
                LED_HourOffset = -1
                if GraphType == 'solar':
                    ShowPriceData('Animation', 'all')
                elif GraphType == 'price':
                    ShowSolarData('Animation', 'all')
                elif GraphType == 'blank':
                    ShowPriceData('Fast', 'all')

            time.sleep(120)


    # Close program by hitting CTRL+C or kill-3 command from service
    except (KeyboardInterrupt, SystemExit):
        cmd_exit = 1

        firmata.fastled_all(1, 1, 1)
        time.sleep(0.2)
        with canvas(device) as draw:
            draw.text((0, -2), "EnergyPlanner", font=font_small, fill=255)
            draw.line((0, 9, device.width - 1, 9), fill=255)
            draw.text((0, 32), '>>> Goodbye!', font=font_small, fill=255)
        time.sleep(1)

        requests.post("https://" + EnergyPlanner_ip_address + "/x")


    # Other errors
    except Exception as err:
        cmd_exit = 1

        firmata.fastled_all(1, 0, 0)
        time.sleep(0.2)
        with canvas(device) as draw:
            draw.text((0, -2), "EnergyPlanner", font=font_small, fill=255)
            draw.line((0, 9, device.width - 1, 9), fill=255)
            draw.text((0, 32), '>>> Error occurred!', font=font_small, fill=255)
        time.sleep(1)

        requests.post("https://" + EnergyPlanner_ip_address + "/x")


    # Close connections and end program
    finally:
        while cmd_joystick == 1 or cmd_data == 1 or cmd_led == 1 or cmd_menu == 1 or cmd_webserver == 1:
            pass

        with canvas(device) as draw:
            draw.rectangle((0, 0, device.width - 1, device.height - 1), outline=0, fill=0)
        firmata.fastled_clear_all()
        time.sleep(0.2)

        # Close SQLite connection
        conn.close()

        # Close PyMata when we are done
        firmata.close()

EnergyPlanner_Webserver.py

Python
Python script for Webserver
#!/usr/bin/python
# -*- coding: utf8 -*-

import sqlite3
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import ssl
#from os import curdir, sep
import os
import cgi
import requests
import json
import time


global YrnoEditDef, IftttEditDef
YrnoEditDef = '<a href=yrno>&raquo;&raquo; Edit setup</a>'
IftttEditDef = '<a href=ifttt?EventName=.>&raquo;&raquo; Edit setup</a>'

def FullPageRender(YrnoText,YrnoEdit,IftttText,IftttEdit,EventsText,SolarGraph,KwhPriceGraph):
    return '''
        <!DOCTYPE html>
        <html>
        <head>
            <link rel="stylesheet" href="webserver/EnergyPlanner.css">
            <link rel="icon" href="webserver/favicon.png" type="image/png">
            <title>Energy Planner setup page</title>

            <script type="text/javascript" src="webserver/jChartFX/jchartfx.system.js"></script>
            <script type="text/javascript" src="webserver/jChartFX/jchartfx.coreVector.js"></script>
            <script type="text/javascript" src="webserver/jChartFX/jchartfx.motif.whitespace.js"></script>
            <link rel="stylesheet" type="text/css" href="webserver/jChartFX/jchartfx.attributes.whitespace.css" />
            <link rel="stylesheet" type="text/css" href="webserver/jChartFX/jchartfx.palette.whitespace.css" />
            <link rel="stylesheet" type="text/css" href="webserver/jChartFX/jchartfx.EnergyPlanner.css" />

        </head>
        <body onload="loadChart1();loadChart2();">
        <table class="table table-striped">
            <tr>
                <td colspan=2><a href="/"><img src="webserver/EnergyPlanner_icon.png"><font style="font-size: 24px;"></a><b>Energy Planner setup page</b></font></td>
            </tr>
            <tr>
                <td colspan=2></td>
            </tr>

            <tr>
                <td><img src="webserver/yrno_logo.png" height="50" width="50"></td>
                <td>
    ''' + \
    str(YrnoText) + \
    '''
                </td>
            </tr>
            <tr>
                <td colspan=2>
    ''' + \
    str(YrnoEdit) + \
    '''
                <br><br><br></td>
            </tr>

            <tr>
                <td><img src="webserver/ifttt_logo.png" height="50" width="50"><br><img src="webserver/ifttt_maker_logo.png" height="50" width="50"></td>
                <td>
    ''' + \
    str(IftttText) + \
    '''
                </td>
            </tr>
            <tr>
                <td colspan=2>
    ''' + \
    str(IftttEdit) + \
    '''
                <br><br><br></td>
            </tr>

            <tr>
                <td><b>Solar power generation</b></td>
                <td>

                    <div id="Chart1Div" style="width:800px;height:200px;display:inline-block"></div>
                    <script type="text/javascript" language="javascript">
                         var chart1;
                         function loadChart1()
                         {
                            chart1 = new cfx.Chart();
                            chart1.setGallery(cfx.Gallery.Bar);
                            chart1.getAllSeries().setStackedStyle(cfx.Stacked.Normal);
                            chart1.getAxisY().setMin(0);
                            chart1.getAxisY().setMax(100);
                            chart1.getAxisY().setStep(20);
                            PopulateSolarProduction(chart1);
                            var titles = chart1.getTitles();
                            var title = new cfx.TitleDockable();
                            title.setText("");
                            titles.add(title);
                            var divHolder = document.getElementById('Chart1Div');
                            chart1.create(divHolder,"jchartfx SolarChart");
                         }
                         function PopulateSolarProduction(chart1) {
                            var items = [
    ''' + \
    str(SolarGraph) + \
    '''
                            ];
                            chart1.setDataSource(items);
                        }
                    </script>

                </td>
            </tr>
            <tr>
                <td colspan=2>
                <br><br><br></td>
            </tr>


            <tr>
                <td><b>Kwh price</b></td>
                <td>

                    <div id="Chart2Div" style="width:800px;height:200px;display:inline-table"></div>
                    <script type="text/javascript" language="javascript">
                         var chart2;
                         function loadChart2()
                         {
                            chart2 = new cfx.Chart();
                            chart2.setGallery(cfx.Gallery.Bar);
                            var bar = chart2.getGalleryAttributes();
                            bar.setOverlap(true);
                            chart2.getSeries().getItem(0).setVolume(80);
                            chart2.getSeries().getItem(1).setVolume(80);
                            chart2.getSeries().getItem(2).setVolume(80);
                            chart2.getAxisY().setMin(0.11);
                            chart2.getAxisY().setMax(0.23);
                            chart2.getAxisY().setStep(0.02);
                            chart2.getAxisY().getLabelsFormat().setDecimals(5);
                            PopulateKwhPriceProduction(chart2);
                            var titles = chart2.getTitles();
                            var title = new cfx.TitleDockable();
                            title.setText("");
                            titles.add(title);
                            var divHolder = document.getElementById('Chart2Div');
                            chart2.create(divHolder,"jchartfx KwhPriceChart");
                         }
                         function PopulateKwhPriceProduction(chart2) {
                            var items = [
    ''' + \
    str(KwhPriceGraph) + \
    '''
                            ];
                            chart2.setDataSource(items);
                        }
                    </script>

                </td>
            </tr>
            <tr>
                <td colspan=2>
                <br><br><br></td>
            </tr>


            <tr>
                <td><b>Scheduled Events</b></td>
                <td>
    ''' + \
    str(EventsText) + \
    '''
                </td>
            </tr>
            <tr>
                <td colspan=2>
                <br><br><br></td>
            </tr>

            <tr>
                <td width=100></td>
                <td></td>
            </tr>
        </table>
        </body>
    '''




def YrnoShowRender():
    connWeb = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')

    YrnoStr = ''
    # Weather forecast from Yr, delivered by the Norwegian Meteorological Institute and NRK
    YrnoCity = '???'
    cursor = connWeb.execute("SELECT YrCity FROM EnergySettings LIMIT 1")
    for row in cursor:
        if str(row[0].encode('utf8')) <> '': YrnoCity = str(row[0].encode('utf8'))

    YrnoStr += "Your location: <a href='http://www.yr.no/place/" + YrnoCity + "'>" + YrnoCity + "</a>"
    YrnoStr += "<br><br><i>Weather forecast from Yr, delivered by the Norwegian Meteorological Institute and the NRK. Please see <a href='http://www.yr.no/'>their website</a> for details</i>"

    connWeb.close()
    return YrnoStr



def YrnoEditRender(RequestCountry,RequestProvince,RequestCity):
    connYR = sqlite3.connect('/root/EnergyPlanner/YrCities.db')

    PageSetupStr = "<form method='POST' action='/yrno'>Select your location:<br>"

    RequestCountryMatch = 0
    PageSetupStr += "<select name='YrCountry' onchange='this.form.submit()'>"
    cursor = connYR.execute("SELECT DISTINCT Country FROM YrCities ORDER BY Country")
    for row in cursor:
        if RequestCountry == str(row[0].encode('utf8')):
            sSelected = ' Selected'
            RequestCountryMatch = 1
        else: sSelected = ''
        PageSetupStr += "<option value='" + str(row[0].encode('utf8')) + "'" + sSelected + ">" + str(row[0].encode('utf8')) + "</option>"
    PageSetupStr += "</select>"

    if RequestCountryMatch == 0:
        cursor = connYR.execute("SELECT DISTINCT Country FROM YrCities ORDER BY Country LIMIT 1;")
        for row in cursor:
            RequestCountry = str(row[0].encode('utf8'))


    PageSetupStr += " / "

    RequestProvinceMatch = 0
    PageSetupStr += "<select name='YrProvince' onchange='this.form.submit()'>"
    cursor = connYR.execute("SELECT DISTINCT Province FROM YrCities WHERE Country = '"+RequestCountry+"' ORDER BY Province")
    for row in cursor:
        if RequestProvince == str(row[0].encode('utf8')):
            sSelected = ' Selected'
            RequestProvinceMatch = 1
        else: sSelected = ''
        PageSetupStr += "<option value='" + str(row[0].encode('utf8')) + "'" + sSelected + ">" + str(row[0].encode('utf8')) + "</option>"
    PageSetupStr += "</select>"

    if RequestProvinceMatch == 0:
        cursor = connYR.execute("SELECT DISTINCT Province FROM YrCities WHERE Country = '" + RequestCountry + "' ORDER BY Province LIMIT 1;")
        for row in cursor:
            RequestProvince = str(row[0].encode('utf8'))

    PageSetupStr += " / "


    RequestCityMatch = 0
    PageSetupStr += "<select name='YrCity' onchange='this.form.submit()'>"
    cursor = connYR.execute("SELECT DISTINCT City FROM YrCities WHERE Country = '"+RequestCountry+"' AND Province = '"+RequestProvince+"' ORDER BY City")
    for row in cursor:
        if RequestCity == str(row[0].encode('utf8')):
            sSelected = ' Selected'
            RequestCityMatch = 1
        else: sSelected = ''
        PageSetupStr += "<option value='" + str(row[0].encode('utf8')) + "'" + sSelected + ">" + str(row[0].encode('utf8')) + "</option>"
    PageSetupStr += "</select>"

    if RequestCityMatch == 0:
        cursor = connYR.execute("SELECT DISTINCT City FROM YrCities WHERE Country = '" + RequestCountry + "' AND Province = '"+RequestProvince+"' ORDER BY City LIMIT 1;")
        for row in cursor:
            RequestCity = str(row[0].encode('utf8'))


    PageSetupStr += "<br><br>Or enter your custom location:<br>"

    PageSetupStr += "<input type='text' name='YrLink' size='100' value='"
    cursor = connYR.execute("SELECT DISTINCT Link FROM YrCities WHERE Country = '"+RequestCountry+"' AND Province = '"+RequestProvince+"' AND City = '"+RequestCity+"' ORDER BY City LIMIT 1")
    for row in cursor:
        PageSetupStr += str(row[0].encode('utf8'))
    PageSetupStr += "'>"

    PageSetupStr += "<br><br>"
    PageSetupStr += "<input type='submit' value='Save' name='Save'></form>"

    PageSetupStr += "<br><br><i>Weather forecast from Yr, delivered by the Norwegian Meteorological Institute and the NRK. Please see <a href='http://www.yr.no/'>their website</a> for details</i>"

    connYR.close()
    return PageSetupStr



def IftttShowRender():
    connWeb = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')

    IFTTTMakerKey = '???'
    cursor = connWeb.execute("SELECT IFTTTMakerKey FROM EnergySettings LIMIT 1")
    for row in cursor:
        if str(row[0]) <> '': IFTTTMakerKey = str(row[0])
    IftttText = '<b>IFTTT Maker Webhooks key:</b> ' + IFTTTMakerKey + '  <a href = "https://ifttt.com/services/maker_webhooks/settings">(Find your key)</a><br><br>'

    IftttText += '<table style="border:0;"><tr><td width="150"><b>Description</b></td><td width="150"><b>IFTTT EventName</b></td><td width="150"><b>Type</b></td><td width="150"><b>Value1</b></td><td width="150"><b>Value2</b></td><td width="150"><b>Value3</b></td><td><b>Beep</b></td></tr>'
    cursor = connWeb.execute("SELECT Description, IFTTTEventName, IFTTTType, IFTTTValue1, IFTTTValue2, IFTTTValue3, BeepType FROM EnergyDevices ORDER BY Description Asc")
    for row in cursor:
        IftttText += '<tr><td>' + str(row[0]) + '</td><td>' + str(row[1]) + '</td><td>' + str(row[2]) + '</td><td>' + str(row[3]) + '</td><td>' + str(row[4]) + '</td><td>' + str(row[5]) + '</td><td>' + str(row[5]) + '</td></tr>'
    IftttText += '</table>'

    connWeb.close()
    return IftttText




def IftttEditRender(EventName):
    connWeb = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')

    IFTTTMakerKey = ''
    cursor = connWeb.execute("SELECT IFTTTMakerKey FROM EnergySettings LIMIT 1")
    for row in cursor:
        if str(row[0]) <> '': IFTTTMakerKey = str(row[0])
    IftttText = '<b>IFTTT Maker Webhooks key:</b> <form method="POST" action="/ifttt"><input type="text" name="IFTTTMakerKey" size="70" value="' + IFTTTMakerKey + '"><input type=hidden name=EventName value="x"><input type="submit" value="Save Key" name="action"></form>  <a href = "https://ifttt.com/services/maker_webhooks/settings">(Find your key)</a><br><br>'

    IftttText += '<table style="border:0;"><tr><td width="150"><b>Description</b></td><td width="150"><b>IFTTT EventName</b></td><td width="150"><b>Type</b></td><td width="150"><b>Value1</b></td><td width="150"><b>Value2</b></td><td width="150"><b>Value3</b></td><td width="150"><b>Beep</b></td><td><b></b></td></tr>'
    cursor = connWeb.execute("SELECT Description, IFTTTEventName, IFTTTType, IFTTTValue1, IFTTTValue2, IFTTTValue3, BeepType FROM EnergyDevices ORDER BY Description Asc")
    for row in cursor:

        BeepLoop = "<select name='BeepType'>"
        for i in range(0, 5):
            if i == row[6]:
                sSelected = ' selected'
            else:
                sSelected = ''
            BeepLoop += "<option value='" + str(i) + "'" + sSelected + ">" + str(i) + "</option>"
        BeepLoop += "</select>"

        if str(row[1]) == EventName:
            IftttText += "<tr><form method='POST' action='/ifttt'>" \
                                "<td><input type='text' name='Description' size='20' value='" + str(row[0]) + "'></td>" \
                                "<td>" + str(row[1]) + "<input type=hidden name=EventName value='" + str(row[1]) + "'></td>" \
                                "<td><input type='text' name='Type' size='20' value='" + str(row[2]) + "'></td>" \
                                "<td><input type='text' name='Value1' size='20' value='" + str(row[3]) + "'></td>" \
                                "<td><input type='text' name='Value2' size='20' value='" + str(row[4]) + "'></td>" \
                                "<td><input type='text' name='Value3' size='20' value='" + str(row[5]) + "'></td>" \
                                "<td>" + BeepLoop + "</td>" \
                                "<td><input type='submit' value='Save' name='action'></form></td>" \
                            "</tr>"
        else:
            IftttText += '<tr><td>' + str(row[0]) + '</td>' \
                            '<td>' + str(row[1]) + '</td>' \
                            '<td><a href="https://ifttt.com/' + str(row[2]) + '">' + str(row[2]) + '</a></td>' \
                            '<td>' + str(row[3]) + '</td>' \
                            '<td>' + str(row[4]) + '</td>' \
                            '<td>' + str(row[5]) + '</td>' \
                            '<td>' + str(row[6]) + '</td>' \
                            '<td><form method=POST action=/ifttt><input type=hidden name=EventName value="' + str(row[1]) + '"><input type=submit value=Edit name=action> <input type=submit value=Delete name=action></form></td>' \
                        '</tr>'

    IftttText += "<tr><form method='POST' action='/ifttt'>" \
                        "<td><input type='text' name='Description' size='20' value=''></td>" \
                        "<td><input type='text' name='EventName' size='20' value=''></td>" \
                        "<td><input type='text' name='Type' size='20' value=''></td>" \
                        "<td><input type='text' name='Value1' size='20' value=''></td>" \
                        "<td><input type='text' name='Value2' size='20' value=''></td>" \
                        "<td><input type='text' name='Value3' size='20' value=''></td>" \
                        "<td>" + BeepLoop + "</td>" \
                        "<td><input type='submit' value='New' name='action'></form></td>" \
                  "</tr>"

    IftttText += '</table>'

    connWeb.close()
    return IftttText








def SolarGraphRender():
    connWeb = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')

    GrahpText = ''
    cursor = connWeb.execute("SELECT Date, case when SunHeight > 0 then (CloudEffect) else 0 end, case when SunHeight > 0 then (SunHeight - CloudEffect) else 0 end FROM EnergyForecast WHERE Date >= '" + time.strftime("%Y-%m-%d %00:00") + "' ORDER BY Date ASC LIMIT 48")
    for row in cursor:
        GrahpText += '{ "Sunshine": ' + str(row[1]) + ', "Clouds": ' + str(row[2]) + ', "Date": "' + time.strftime("%H:00",time.strptime(row[0], "%Y-%m-%d %H:%M")) + '"},'

    connWeb.close()
    return GrahpText




def KwhPriceGraphRender():
    connWeb = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')

    QurrentPriceMin = 0
    QurrentPriceMax = 0

    cursor = connWeb.execute("SELECT MAX(PriceKwh), MIN(PriceKwh) FROM EnergyForecast WHERE Date >= '" + time.strftime("%Y-%m-%d 00:00") + "' and ifnull(PriceKwh,0) <> 0;")
    for row in cursor:
        QurrentPriceMin = row[1]
        QurrentPriceMax = row[0]

    GrahpText = ''
    cursor = connWeb.execute("SELECT Date, "
                                  "CASE WHEN PriceKwh <= " + str(QurrentPriceMin + ((QurrentPriceMax-QurrentPriceMin) / 3)) + " THEN PriceKwh ELSE 0 end, "
                                  "CASE WHEN PriceKwh > " + str(QurrentPriceMin + ((QurrentPriceMax-QurrentPriceMin) / 3)) + " AND PriceKwh < " + str(QurrentPriceMax - ((QurrentPriceMax-QurrentPriceMin) / 3)) + " THEN PriceKwh ELSE 0 end, "
                                  "CASE WHEN PriceKwh >= " + str(QurrentPriceMax - ((QurrentPriceMax-QurrentPriceMin) / 3)) + " THEN PriceKwh ELSE 0 end "
                            "FROM EnergyForecast WHERE Date >= '" + time.strftime("%Y-%m-%d %00:00") + "' ORDER BY Date ASC LIMIT 48")
    for row in cursor:
        GrahpText += '{ "High": ' + str(row[3]) + ', "Medium": ' + str(row[2]) + ', "Low": ' + str(row[1]) + ', "Date": "' + time.strftime("%H:00",time.strptime(row[0], "%Y-%m-%d %H:%M")) + '"},'

    connWeb.close()
    return GrahpText









def EventsShowRender():
    connWeb = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')

    EventText = '<table style="border:0;"><tr><td width="150"><b>Day</b><br><br></td><td><b>Information</b><br><br></td></tr>'

    cursor = connWeb.execute("SELECT DISTINCT Date FROM EnergyDeviceSchedule ORDER BY Date DESC;")
    for row in cursor:
        EventText += '<tr><td width="150" style="vertical-align:top;"><b>' + str(row[0]) + '</b></td><td>'
        EventText += '<table style="border:0;">'

        cursor3 = connWeb.execute("SELECT DISTINCT Date, EnergyDevices.Description, CASE WHEN Status = '1' THEN 'Scheduled' "
                                                                                          "WHEN Status = '2' THEN 'Executed' "
                                                                                          "ELSE '?' END "
                                      "FROM EnergyDeviceSchedule INNER JOIN EnergyDevices on (EnergyDevices.IFTTTEventName = EnergyDeviceSchedule.IFTTTEventName)"\
                                      "WHERE Date = '" + str(row[0]) + "' ORDER BY EnergyDevices.Description ASC")
        for row3 in cursor3:
            EventText += '<tr><td style="vertical-align:top;" width="200">' + str(row3[1]) +'</td><td style="vertical-align:top;">' + str(row3[2]) + '</td></tr>'

        EventText += '</table></td></tr>'
    EventText += '</table>'

    connWeb.close()
    return EventText




# This class will handles any incoming request from the browser
class myHandler(BaseHTTPRequestHandler):
    def do_HEAD(self):
        '''
        Handle a HEAD request.
        '''
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()


    # Handler for the GET requests
    def do_GET(self):
        args = {}
        idx = self.path.find('?')
        if idx >= 0:
            rpath = self.path[:idx]
            args = cgi.parse_qs(self.path[idx + 1:])
        else:
            rpath = self.path

        try:
            # Check the file extension required and
            # set the right mime type

            sendReply = False

            if rpath.endswith(".html"):
                mimetype = 'text/html'
                sendReply = True
            if rpath.endswith(".png"):
                mimetype = 'image/png'
                sendReply = True
            if rpath.endswith(".jpg"):
                mimetype = 'image/jpg'
                sendReply = True
            if rpath.endswith(".gif"):
                mimetype = 'image/gif'
                sendReply = True
            if rpath.endswith(".js"):
                mimetype = 'application/javascript'
                sendReply = True
            if rpath.endswith(".css"):
                mimetype = 'text/css'
                sendReply = True

            if sendReply == True:
                # Open the static file requested and send it
                curdir = os.path.dirname(os.path.abspath(__file__))
                f = open(curdir + rpath)
                self.send_response(200)
                self.send_header('Content-type', mimetype)
                self.end_headers()
                self.wfile.write(f.read())
                f.close()
                return




            print rpath
            print args

            YrnoText = YrnoShowRender()
            YrnoEdit = YrnoEditDef
            IftttText = IftttShowRender()
            IftttEdit = IftttEditDef
            EventsText = EventsShowRender()
            SolarGraph = SolarGraphRender()
            KwhPriceGraph = KwhPriceGraphRender()


            if rpath == "/":
                pass



            if rpath == "/yrno":
                connWeb = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')
                connYR = sqlite3.connect('/root/EnergyPlanner/YrCities.db')

                YrnoCountry = ''
                YrnoProvince = ''
                YrnoCity = ''

                cursor = connWeb.execute("SELECT YrCity FROM EnergySettings LIMIT 1")
                for row in cursor:

                    cursor2 = connYR.execute("SELECT Country, Province, City FROM YrCities WHERE Link = '" + str(row[0].encode('utf8')) + "'")
                    for row2 in cursor2:
                        YrnoCountry = str(row2[0].encode('utf8'))
                        YrnoProvince = str(row2[1].encode('utf8'))
                        YrnoCity = str(row2[2].encode('utf8'))

                connWeb.close()
                connYR.close()

                YrnoText = YrnoEditRender(YrnoCountry,YrnoProvince,YrnoCity)
                YrnoEdit = ''



            if rpath == "/ifttt":
                EventName = str(args['EventName'][0])
                IftttText = IftttEditRender(EventName)
                IftttEdit = ''


            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            self.wfile.write(FullPageRender(YrnoText,YrnoEdit,IftttText,IftttEdit,EventsText,SolarGraph,KwhPriceGraph))

            return






        except IOError:
            self.send_error(404, 'File Not Found: %s' % rpath)


    # Handler for the POST requests
    def do_POST(self):
        connWeb = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')
        args = {}
        idx = self.path.find('?')
        if idx >= 0:
            rpath = self.path[:idx]
            args = cgi.parse_qs(self.path[idx + 1:])
        else:
            rpath = self.path

        form = cgi.FieldStorage(
            fp=self.rfile,
            headers=self.headers,
            environ={'REQUEST_METHOD': 'POST',
                     'CONTENT_TYPE': self.headers['Content-Type'],
                     })

        print form

        try:
            # Check the file extension required and set the right mime type
            if rpath.endswith(""):
                print rpath
                print args

                YrnoText = YrnoShowRender()
                YrnoEdit = YrnoEditDef
                IftttText = IftttShowRender()
                IftttEdit = IftttEditDef
                EventsText = EventsShowRender()
                SolarGraph = SolarGraphRender()
                KwhPriceGraph = KwhPriceGraphRender()


                if rpath == "/":
                    pass



                if rpath == "/yrno":
                    try:
                        YrnoSave = form["Save"].value
                        if YrnoSave == 'Save':
                            connWeb = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')

                            connWeb.execute("UPDATE EnergySettings SET YrCity = '" + str(form["YrLink"].value) + "';")
                            connWeb.commit()

                            connWeb.close()

                            YrnoText = YrnoShowRender()

                        else:
                            YrnoText = YrnoEditRender(form["YrCountry"].value, form["YrProvince"].value, form["YrCity"].value)
                            YrnoEdit = ''

                    except:
                        YrnoText = YrnoEditRender(form["YrCountry"].value,form["YrProvince"].value,form["YrCity"].value)
                        YrnoEdit = ''



                if rpath == "/ifttt":
                    if 'EventName' in form:
                        connWeb = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')

                        form_EventName = form["EventName"].value

                        if form["action"].value == 'New' or form["action"].value == 'Save':
                            if 'Description' in form: form_Description = form["Description"].value
                            else: form_Description = ''

                            if 'Type' in form: form_Type = form["Type"].value
                            else: form_Type = ''

                            if 'Value1' in form: form_Value1 = form["Value1"].value
                            else: form_Value1 = ''

                            if 'Value2' in form: form_Value2 = form["Value2"].value
                            else: form_Value2 = ''

                            if 'Value3' in form: form_Value3 = form["Value3"].value
                            else: form_Value3 = ''

                            if 'BeepType' in form: form_BeepType = form["BeepType"].value
                            else: form_BeepType = ''


                        elif form["action"].value == 'Save Key':
                            if 'IFTTTMakerKey' in form: form_Key = form["IFTTTMakerKey"].value
                            else: form_Key = ''
                            connWeb.execute("UPDATE EnergySettings SET IFTTTMakerKey='" + form_Key + "';")
                            connWeb.commit()
                            form_EventName = ''


                        if form["action"].value == 'New':
                            connWeb.execute("INSERT INTO EnergyDevices (Description, IFTTTType, IFTTTEventName, IFTTTValue1, IFTTTValue2, IFTTTValue3, BeepType) VALUES ('" + form_Description + "','" + form_EventName + "','" + form_Type + "','" + form_Value1 + "','" + form_Value2 + "','" + form_Value3 + "', '" + form_BeepType + "');")
                            connWeb.commit()
                            form_EventName = ''

                        elif form["action"].value == 'Save':
                            connWeb.execute("UPDATE EnergyDevices SET Description='" + form_Description + "', IFTTTType='" + form_Type + "', IFTTTValue1='" + form_Value1 + "', IFTTTValue2='" + form_Value2 + "', IFTTTValue3='" + form_Value3 + "', BeepType='" + form_BeepType + "' WHERE IFTTTEventName = '" + form_EventName + "';")
                            connWeb.commit()
                            form_EventName = ''

                        elif form["action"].value == 'Delete':
                            connWeb.execute("DELETE FROM EnergyDevices WHERE IFTTTEventName = '" + form_EventName + "';")
                            connWeb.commit()
                            form_EventName = ''

                        elif form["action"].value == 'Edit':
                            pass


                        connWeb.close()

                    else: form_EventName = ''

                    IftttText = IftttEditRender(form_EventName)
                    IftttEdit = ''



                # Send the html message
                self.send_response(200)
                self.send_header('Content-type', 'text/html')
                self.end_headers()
                self.wfile.write(FullPageRender(YrnoText,YrnoEdit,IftttText,IftttEdit,EventsText,SolarGraph,KwhPriceGraph))


        except IOError:
            self.send_error(404, 'File Not Found: %s' % rpath)

EnergyPlanner_SunPos.py

Python
Python script for Sun Position
#!/usr/bin/python
# -*- coding: utf8 -*-

import math

# Bastiaan:
# Don't remember where I got this from, I'm using it in a home automation project for some years already.
# I think I did convert the PHP code from https://github.com/mharizanov/SUN-insolation-calculator to Python.



# calculates the sun position and path throughout the day
# input params: LAT&LON
# output json

# not much commenting in code
# ported from http://www.stjarnhimlen.se/comp/tutorial.html
# MANY THANKS!
# most var-names are identical to above tutorial

def getsunpos(LAT, LON, year, month, day, hour):
    # julian date
    d = 367 * year - math.floor((7 * (year + math.floor((month + 9) / 12))) / 4) + math.floor((275 * month) / 9) + day - 730530

    # longitude of perihelion
    w = 4.9382415669097640822661983551248 + 0.00000082193663128794959930855831205818 * d
    # mean distance, a.u.
    a = 1.000000
    # eccentricity
    e = 0.016709 - 0.000000001151 * d
    # mean anomaly
    M = 6.2141924418482506287494932704807 + 0.017201969619332228715501852561964 * d

    # obliquity of the ecliptic
    oblecl = 0.40909295936270689252387465029835 - 0.0000000062186081248557962825791102081249 * d

    # sun's mean longitude
    L = w + M

    E = M + e * math.sin(M) * (1 + e * math.cos(M))

    x = math.cos(E) - e
    y = math.sin(E) * math.sqrt(1 - e * e)

    r = math.sqrt(x * x + y * y)
    v = math.atan2(y, x)

    lon = v + w

    x = r * math.cos(lon)
    y = r * math.sin(lon)
    z = 0.0

    xequat = x
    yequat = y * math.cos(oblecl) + z * math.sin(oblecl)
    zequat = y * math.sin(oblecl) + z * math.cos(oblecl)

    RA = math.atan2(yequat, xequat)
    Decl = math.asin(zequat / r)

    RAhours = r2d(RA) / 15

    GMST0 = r2d(L + math.pi) / 15
    SIDTIME = GMST0 + hour + math.degrees(LON) / 15

    HA = math.radians((SIDTIME - RAhours) * 15)

    x = math.cos(HA) * math.cos(Decl)
    y = math.sin(HA) * math.cos(Decl)
    z = math.sin(Decl)

    xhor = x * math.cos(math.pi / 2 - LAT) - z * math.sin(math.pi / 2 - LAT)
    yhor = y
    zhor = x * math.sin(math.pi / 2 - LAT) + z * math.cos(math.pi / 2 - LAT)

    # Original from script:
    # azimuth  = math.pi*3/2 -math.atan2( yhor, xhor )
    # Alternative from Stackexchange:
    azimuth = math.atan2(yhor, xhor) + math.pi
    altitude = math.asin(zhor)

    # check sunshine state
    alt_d = math.degrees(altitude)
    if (alt_d >= 0):
        sunstate = "DAY"
    elif (alt_d >= -6):
        sunstate = "CIVIL-TWILIGHT"
    elif (alt_d >= -12):
        sunstate = "NAUTICAL-TWILIGHT"
    elif (alt_d >= -18):
        sunstate = "ASTRONOMICAL-TWILIGHT"
    else:
        sunstate = "NIGHT"

    return {"azimuth": azimuth, "altitude": altitude, "sunstate": sunstate}

def r2d(r):
    d = math.degrees(r)
    if (d < 0): d = d + 360
    if (d > 360): d = d - 360
    return d

# functions below are for the sun's path through the day

def findCentre(x1, y1, x2, y2, x3, y3):
    if not (isPerpendicular(x1, y1, x2, y2, x3, y3)):
        CC = CalcCircle(x1, y1, x2, y2, x3, y3)
        cx = CC["cx"]
        cy = CC["cy"]
        r = CC["r"]
    elif not (isPerpendicular(x1, y1, x3, y3, x2, y2)):
        CC = CalcCircle(x1, y1, x3, y3, x2, y2)
        cx = CC["cx"]
        cy = CC["cy"]
        r = CC["r"]
    elif not (isPerpendicular(x2, y2, x1, y1, x3, y3)):
        CC = CalcCircle(x2, y2, x1, y1, x3, y3)
        cx = CC["cx"]
        cy = CC["cy"]
        r = CC["r"]
    elif not (isPerpendicular(x2, y2, x3, y3, x1, y1)):
        CC = CalcCircle(x2, y2, x3, y3, x1, y1)
        cx = CC["cx"]
        cy = CC["cy"]
        r = CC["r"]
    elif not (isPerpendicular(x3, y3, x2, y2, x1, y1)):
        CC = CalcCircle(x3, y3, x2, y2, x1, y1)
        cx = CC["cx"]
        cy = CC["cy"]
        r = CC["r"]
    elif not (isPerpendicular(x3, y3, x1, y1, x2, y2)):
        CC = CalcCircle(x3, y3, x1, y1, x2, y2)
        cx = CC["cx"]
        cy = CC["cy"]
        r = CC["r"]
    else:
        cx = 0
        cy = 0
        r = 0
    return {"cx": cx, "cy": cy, "r": r}

def isPerpendicular(x1, y1, x2, y2, x3, y3):
    m = 0.00000001
    dya = y2 - y1
    dxa = x2 - x1
    dyb = y3 - y2
    dxb = x3 - x2

    # checking whether the line of the two pts are vertical
    if ((abs(dxa) <= m) and (abs(dyb) <= m)):
        return False
    if ((abs(dxa) <= m) or (abs(dxb) <= m) or (abs(dya) < m) or (abs(dyb) <= m)):
        return True

def CalcCircle(x1, y1, x2, y2, x3, y3):
    m = 0.00000001
    dya = y2 - y1
    dxa = x2 - x1
    dyb = y3 - y2
    dxb = x3 - x2

    if ((abs(dxa) <= m) and (abs(dxb) <= m)):
        cx = 0.5 * (x2 + x3)
        cy = 0.5 * (y1 + y2)
        r = math.sqrt((x1 - cx) * (x1 - cx) + (y1 - cy) * (y1 - cy))
        return

    # IsPerpendicular() assure that xDelta(s) are not zero
    aSlope = dya / dxa
    bSlope = dyb / dxb
    # checking whether the given points are colinear.
    if (abs(aSlope - bSlope) <= 0.000000001):
        return

    # calc center
    cx = (aSlope * bSlope * (y1 - y3) + bSlope * (x1 + x2) - aSlope * (x2 + x3)) / (2 * (bSlope - aSlope))
    cy = -1 * (cx - (x1 + x2) / 2) / aSlope + (y1 + y2) / 2

    r = math.sqrt((x1 - cx) * (x1 - cx) + (y1 - cy) * (y1 - cy))
    return {"cx": cx, "cy": cy, "r": r}

EnergyPlanner_YRno.py

Python
Python script for getting the YR.no weather data
#!/usr/bin/python
# -*- coding: utf8 -*-

import requests
import time
import sqlite3
from EnergyPlanner_SunPos import *

city = "Netherlands/North_Holland/Amsterdam"

YR_base_url = "http://www.yr.no/place/"
YR_xml_url = "/forecast_hour_by_hour.xml"
YR_detailed_url = "/hour_by_hour_detailed.html"

# When there is a 100% cloud cover, what is the resulting effective % of the solar panels?
solar_effect = 40


def getDayTable(TableHTML):
    yrStartDate = '</strong>'
    yrEndDate = '</caption>'
    yrStartDatePos = TableHTML.find(yrStartDate) + 9
    yrEndDatePos = TableHTML.find(yrEndDate, yrStartDatePos)
    yrDate = TableHTML[yrStartDatePos:yrEndDatePos]

    yrStartData = '<tbody>'
    yrEndData = '</tbody>'

    yrStartDataPos = TableHTML.find(yrStartData) + 7
    yrEndDataPos = TableHTML.find(yrEndData, yrStartDataPos)
    yrData = TableHTML[yrStartDataPos:yrEndDataPos]

    yrData = yrData.replace('<th scope="row">', '<td>' + yrDate + '</td><td scope="row">', 1000000)
    yrData = yrData.replace("<th ", "<td ", 1000000).replace("</th>", "</td>", 1000000)
    yrData = yrData.replace("<strong>", "", 1000000).replace("</strong>", "", 1000000)
    yrData = yrData.replace("<tbody>", "", 1000000).replace("</tbody>", "", 1000000)
    yrData = yrData.replace("  ", " ", 1000000)

    return yrData


def getYRSetupData():
    connYR = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')

    # Get general data for location
    response = requests.post(YR_base_url + city + YR_xml_url)
    yrFullXML = response.text

    yrStartCityPos = yrFullXML.find('<name>') + 6
    yrEndCityPos = yrFullXML.find('</name>', yrStartCityPos)
    yrCity = yrFullXML[yrStartCityPos:yrEndCityPos]

    yrStartCountryPos = yrFullXML.find('<country>') + 9
    yrEndCountryPos = yrFullXML.find('</country>', yrStartCountryPos)
    yrCountry = yrFullXML[yrStartCountryPos:yrEndCountryPos]

    yrStartUTCoffsetPos = yrFullXML.find('utcoffsetMinutes="') + 18
    yrEndUTCoffsetPos = yrFullXML.find('"', yrStartUTCoffsetPos)
    yrUTCoffset = int(yrFullXML[yrStartUTCoffsetPos:yrEndUTCoffsetPos])

    yrStartTimezonePos = yrFullXML.find('timezone id="') + 13
    yrEndTimezonePos = yrFullXML.find('"', yrStartTimezonePos)
    yrTimezone = yrFullXML[yrStartTimezonePos:yrEndTimezonePos]

    yrStartAltitudePos = yrFullXML.find('altitude="') + 10
    yrEndAltitudePos = yrFullXML.find('"', yrStartAltitudePos)
    yrAltitude = float(yrFullXML[yrStartAltitudePos:yrEndAltitudePos])

    yrStartLatitudePos = yrFullXML.find('latitude="') + 10
    yrEndLatitudePos = yrFullXML.find('"', yrStartLatitudePos)
    yrLatitude = float(yrFullXML[yrStartLatitudePos:yrEndLatitudePos])
    LAT = math.radians(yrLatitude)

    yrStartLongitudePos = yrFullXML.find('longitude="') + 11
    yrEndLongitudePos = yrFullXML.find('"', yrStartLongitudePos)
    yrLongitude = float(yrFullXML[yrStartLongitudePos:yrEndLongitudePos])
    LON = math.radians(yrLongitude)

    # Calculate local time and print details of YR-location
    local_time = time.localtime(time.mktime(time.localtime()) + (60 * yrUTCoffset) - (
    time.mktime(time.localtime()) - time.mktime(time.gmtime())))

    print "City:" + yrCity + " Country: " + yrCountry
    print "Current Time: " + time.strftime("%Y-%m-%d %H:%M", local_time) + " Timezone: " + yrTimezone + " UTC Offset:" + str(yrUTCoffset)
    print "LAT: " + str(yrLatitude) + " LON: " + str(yrLongitude)

    # Calculate when the sun is highest: Jun 21 or Dec 12 (12 GMT at the LAT of our location, but measured at GMT where LON=0)
    LONx = math.radians(0)
    sunpos_June = getsunpos(LAT, LONx, int(time.strftime("%Y")), 6, 21, 12)
    sunpos_Dec = getsunpos(LAT, LONx, int(time.strftime("%Y")), 12, 21, 12)
    if (sunpos_June["altitude"] > sunpos_Dec["altitude"]):
        print "SunHeight Top June 21: " + str(sunpos_June["altitude"])
        sunpos_Top = sunpos_June["altitude"]
    else:
        print "SunHeight Top December 21: " + str(sunpos_Dec["altitude"])
        sunpos_Top = sunpos_Dec["altitude"]

    print ""

    connYR.execute("UPDATE EnergySettings SET YrLatitude = " + str(yrLatitude) + ","
                                            "YrLongitude = " + str(yrLongitude) + ","
                                            "YrAltitude = " + str(yrAltitude) + ","
                                            "YrTimezone = '" + yrTimezone + "',"
                                            "YrUTCoffset = " + str(yrUTCoffset) + ","
                                            "SunHeightTop = " + str(sunpos_Top) + ";")
    connYR.commit()
    connYR.close()


def getYRWeatherData():
    connYR = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')
    cursor = connYR.execute("SELECT YrCity, YrLatitude, YrLongitude, SunHeightTop, YrUTCoffset FROM EnergySettings LIMIT 1")
    for row in cursor:
        EnergySettingsCity = row[0]
        EnergySettingsLatitude = math.radians(row[1])
        EnergySettingsLongitude = math.radians(row[2])
        EnergySettingsSunHeightTop = row[3]
        EnergySettingsUTCoffset = row[4]

    # Calculate local time and print details of YR-location
    local_time = time.localtime(time.mktime(time.localtime()) + (60 * EnergySettingsUTCoffset) - (
    time.mktime(time.localtime()) - time.mktime(time.gmtime())))

    # Get table with weather data
    response = requests.post(YR_base_url + EnergySettingsCity + YR_detailed_url)
    yrFullPage = response.text
    yrStartTable = '<table id="detaljert-tabell"'
    yrEndTable = '</table>'

    # Get data for each reported day (48 hours, can be on 3 days)
    yrStartTablePos = yrFullPage.find(yrStartTable)
    yrEndTablePos = yrFullPage.find(yrEndTable, yrStartTablePos) + 8
    yrFullTable1 = getDayTable(yrFullPage[yrStartTablePos:yrEndTablePos])

    yrStartTablePos = yrFullPage.find(yrStartTable, yrEndTablePos)
    yrEndTablePos = yrFullPage.find(yrEndTable, yrStartTablePos) + 8
    yrFullTable2 = getDayTable(yrFullPage[yrStartTablePos:yrEndTablePos])

    yrStartTablePos = yrFullPage.find(yrStartTable, yrEndTablePos)
    yrEndTablePos = yrFullPage.find(yrEndTable, yrStartTablePos) + 8
    yrFullTable3 = getDayTable(yrFullPage[yrStartTablePos:yrEndTablePos])

    # Create our own heading
    yrTableHeadings = '''<tr>
            <th>Date</th>
            <th>Time</th>
            <th>Weather</th>
            <th>Temp.</th>
            <th>Precipitation</th>
            <th>Wind</th>
            <th>Pressure</th>
            <th>Humidity</th>
            <th>Dew point</th>
            <th>Cloud cover Total</th>
            <th>Cloud cover Fog</th>
            <th>Cloud cover Low clouds</th>
            <th>Cloud cover Middle clouds</th>
            <th>Cloud cover High clouds</th>
          </tr>
    '''

    # Merge own heading with the 3 tables
    yrFullTable = "<table>" + yrTableHeadings + yrFullTable1 + yrFullTable2 + yrFullTable3 + "</table>"

    # Extract the tml table logic into an Element Tree
    from xml.etree import ElementTree as ET
    s = yrFullTable.encode('ascii', 'ignore').decode('ascii')

    table = ET.XML(s)
    rows = iter(table)
    headers = [col.text for col in next(rows)]

    # Find the position of the headers and corresponding columns
    posDate = headers.index('Date')
    posTime = headers.index('Time')
    posCloud = headers.index('Cloud cover Total')

    # Fake 999% cloud cover to be able to show current hour (will be fixed in led_Range function
    yrCloudCover = 999

    forecastBit = 0
    prev_time = time.localtime(time.mktime(time.strptime(time.strftime("%Y-%m-%d %H:00", local_time), "%Y-%m-%d %H:%M")) - (60 * 60))

    # For each row, get the column data and apply calculations
    for row in rows:
        values = [col.text for col in row]

        # To print the raw column data:
        # print dict(zip(headers, values))

        # Convert the sting-time to a time.time value
        yrtime = time.strptime(values[posDate].strip() + " " + values[posTime].strip(), "%B %d, %Y %H:%M")
        # YR does put 00:00 in previous day, where it should be next day
        if (str(time.strftime("%H", yrtime)) == "00"):
            yrtime = time.localtime(time.mktime(yrtime) + (60 * 60 * 24))

        while (time.mktime(prev_time) < time.mktime(yrtime)):

            # Get the YR Cloud Cover and the correct time
            if (time.mktime(prev_time) + (60 * 60) == time.mktime(yrtime)):
                yrCloudCover = int(values[posCloud].replace(" %", "").strip())
                calc_time = yrtime
            else:
                calc_time = time.localtime(time.mktime(prev_time) + (60 * 60))

            # If line does not exists for time, add line
            connYR.execute("INSERT INTO EnergyForecast (Date) SELECT '" + time.strftime("%Y-%m-%d %H:%M",calc_time) + "' WHERE NOT EXISTS (SELECT 1 FROM EnergyForecast WHERE Date = '" + time.strftime("%Y-%m-%d %H:%M", calc_time) + "');")

            # Convert time to GMT for SunHeight calculation
            atTime = time.gmtime(time.mktime(calc_time) - (60 * EnergySettingsUTCoffset) + (time.mktime(time.localtime()) - time.mktime(time.gmtime())))
            year = int(time.strftime("%Y", atTime))
            month = int(time.strftime("%m", atTime))
            day = int(time.strftime("%d", atTime))
            hour = int(time.strftime("%H", atTime))

            # Get the Sun Position data
            sunpos = getsunpos(EnergySettingsLatitude, EnergySettingsLongitude, year, month, day, hour)
            sunpos_SunHeight = sunpos["altitude"]
            calc_SunHeight = ((float(sunpos_SunHeight) / float(EnergySettingsSunHeightTop)) * 100)

            if (yrCloudCover <> 999):
                # Calculate the % of sun height compared to June/Dec 21 and the effect of Clouds on the solar production
                # Daytime:
                if (calc_SunHeight > 0):
                    calc_CloudEffect = round( calc_SunHeight * (0.01 * (100 - (yrCloudCover*(0.01 * (100 - solar_effect))) )) , 2)
                    #calc_CloudEffect = round(((100 - ((yrCloudCover - (yrCloudCover / 100) * solar_effect))) * calc_SunHeight / 100), 2)
                    led_Night = 0
                    led_CloudEffect = (float(calc_CloudEffect) * 0.08)  # 8 LED's will be used for solar effective hours
                    led_SunHeight = (float(calc_SunHeight) * 0.08)  # 8 LED's will be used for solar ineffective hours
                # Nighttime:
                else:
                    led_Night = 1
                    led_SunHeight = 0
                    led_CloudEffect = 0
                    calc_CloudEffect = 0

                # forecastBit and led_Bit is used for numbering the ledStick
                led_Range = ['', '', '', '', '', '', '', '', '']
                led_Bit = 0
                led_ShowNow = 1

                if (led_Night == 1):
                    # It's night, show 1 blue bit
                    led_Range[led_Bit] = "N"
                    led_Bit += 1
                else:
                    # It's day
                    # Cloud was guessed, show Orange:
                    if (int(math.ceil(led_CloudEffect)) < 0):
                        led_Range[led_Bit] = "O"
                        led_Bit += 1
                    # show Bright Yellow for solar effect
                    while (int(math.ceil(led_CloudEffect)) > led_Bit):
                        led_Range[led_Bit] = "S"
                        led_Bit += 1
                    # show Dim Yellow for sun without effect
                    while (int(math.ceil(led_SunHeight)) > led_Bit):
                        led_Range[led_Bit] = "Y"
                        led_Bit += 1

                # Loop all empty spaces, for example when night
                while (8 > led_Bit):
                    # show nothing to fill up the day
                    led_Range[led_Bit] = "o"
                    led_Bit += 1

                connYR.execute("UPDATE EnergyForecast SET SolarLed0 = '" + led_Range[0] + "', "
                                                          "SolarLed1 = '" + led_Range[1] + "', "
                                                          "SolarLed2 = '" + led_Range[2] + "', "
                                                          "SolarLed3 = '" + led_Range[3] + "', "
                                                          "SolarLed4 = '" + led_Range[4] + "', "
                                                          "SolarLed5 = '" + led_Range[5] + "', "
                                                          "SolarLed6 = '" + led_Range[6] + "', "
                                                          "SolarLed7 = '" + led_Range[7] + "', "
                                                          "SunHeight = " + str(calc_SunHeight) + ", "
                                                          "CloudCover = " + str(yrCloudCover) + ", "
                                                          "CloudEffect = " + str(calc_CloudEffect) + " "
                                        "WHERE Date = '" + time.strftime("%Y-%m-%d %H:%M", calc_time) + "';")

                print "Solar: " + time.strftime("%Y-%m-%d %H:%M", calc_time) + " " + led_Range[0] + led_Range[1] + led_Range[
                    2] + led_Range[3] + led_Range[4] + led_Range[5] + led_Range[6] + led_Range[7]

            else:
                connYR.execute("UPDATE EnergyForecast SET SunHeight = " + str(calc_SunHeight) + " "
                                        "WHERE Date = '" + time.strftime("%Y-%m-%d %H:%M", calc_time) + "';")
                print "Solar: " + time.strftime("%Y-%m-%d %H:%M", calc_time) + " ??????"

            # Prepare for next hour in loop
            prev_time = time.localtime(time.mktime(calc_time))
            forecastBit += 1

        connYR.commit()
    connYR.close()

EnergyPlanner_Qurrent.py

Python
Python script for getting Kwh price data from Qurrent
#!/usr/bin/python
# -*- coding: utf8 -*-

import requests
import time
import sqlite3

Qurrent_xml_url = "https://www.qurrent.nl/api/HighResTariff"

def getQurrentData():
    connQurrent = sqlite3.connect('/root/EnergyPlanner/EnergyPlanner.db')
    cursor = connQurrent.execute("SELECT YrUTCoffset FROM EnergySettings LIMIT 1")
    for row in cursor:
        EnergySettingsUTCoffset = row[0]

    # Get table with weather data
    QurrentData = requests.get(Qurrent_xml_url)

    for QurrentDataLine in QurrentData.json():
        QurrentDateTime = time.strptime(QurrentDataLine["StartDateTime"], "%Y-%m-%dT%H:%M:%S")
        QurrentPriceKwh = round(QurrentDataLine["Total"], 5)

        connQurrent.execute("UPDATE EnergyForecast SET PriceKwh = " + str(QurrentPriceKwh) + " WHERE Date = '" + time.strftime("%Y-%m-%d %H:%M", QurrentDateTime) + "' AND ifnull(PriceKwh,'') = '';")
        print "Price: " + time.strftime("%Y-%m-%d %H:%M", QurrentDateTime) + " " + str(QurrentPriceKwh)
    connQurrent.commit()

    # Get time of last whole hour, used as TODAY
    local_time = time.localtime(time.mktime(time.localtime()) + (60 * EnergySettingsUTCoffset) - (
    time.mktime(time.localtime()) - time.mktime(time.gmtime())))

    cursor = connQurrent.execute("SELECT MAX(PriceKwh), MIN(PriceKwh), AVG(PriceKwh) FROM EnergyForecast WHERE Date >= '" + time.strftime("%Y-%m-%d 00:00", local_time) + "' AND ifnull(PriceKwh,0) <> 0;")
    for row in cursor:
        QurrentPriceMax = row[0]
        QurrentPriceMin = row[1]
        QurrentPriceAvg = row[2]
        print 'MAX: ' + str(QurrentPriceMax)
        print 'MIN: ' + str(QurrentPriceMin)
        print 'AVG: ' + str(QurrentPriceAvg)

    connQurrent.execute("UPDATE EnergyForecast SET PriceLed0 = CASE WHEN PriceKwh <= " + str(QurrentPriceMin + (float(QurrentPriceMax-QurrentPriceMin)/3)) + "                                                                                          THEN 'L'"
                                                                    "WHEN PriceKwh > " + str(QurrentPriceMin + (float(QurrentPriceMax-QurrentPriceMin)/3)) + " AND PriceKwh < " + str(QurrentPriceMax - (float(QurrentPriceMax-QurrentPriceMin)/3)) + " THEN 'M'"
                                                                    "WHEN PriceKwh >= " + str(QurrentPriceMax - (float(QurrentPriceMax-QurrentPriceMin)/3)) + "                                                                                         THEN 'H'"
                                                                    "ELSE 'o' END, "
                                                    "PriceLed1 = CASE WHEN PriceKwh <= " + str(QurrentPriceMin + (float(QurrentPriceMax-QurrentPriceMin)/3)) + "                                                                                        AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (1*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'L'"
                                                                    "WHEN PriceKwh > " + str(QurrentPriceMin + (float(QurrentPriceMax-QurrentPriceMin)/3)) + " AND PriceKwh < " + str(QurrentPriceMax - (float(QurrentPriceMax-QurrentPriceMin)/3)) + " AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (1*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'M'"
                                                                    "WHEN PriceKwh >= " + str(QurrentPriceMax - (float(QurrentPriceMax-QurrentPriceMin)/3)) + "                                                                                         AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (1*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'H'"
                                                                    "ELSE 'o' END, "
                                                    "PriceLed2 = CASE WHEN PriceKwh <= " + str(QurrentPriceMin + (float(QurrentPriceMax-QurrentPriceMin)/3)) + "                                                                                        AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (2*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'L'"
                                                                    "WHEN PriceKwh > " + str(QurrentPriceMin + (float(QurrentPriceMax-QurrentPriceMin)/3)) + " AND PriceKwh < " + str(QurrentPriceMax - (float(QurrentPriceMax-QurrentPriceMin)/3)) + " AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (2*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'M'"
                                                                    "WHEN PriceKwh >= " + str(QurrentPriceMax - (float(QurrentPriceMax-QurrentPriceMin)/3)) + "                                                                                         AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (2*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'H'"
                                                                    "ELSE 'o' END, "
                                                    "PriceLed3 = CASE WHEN PriceKwh <= " + str(QurrentPriceMin + (float(QurrentPriceMax-QurrentPriceMin)/3)) + "                                                                                        AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (3*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'L'"
                                                                    "WHEN PriceKwh > " + str(QurrentPriceMin + (float(QurrentPriceMax-QurrentPriceMin)/3)) + " AND PriceKwh < " + str(QurrentPriceMax - (float(QurrentPriceMax-QurrentPriceMin)/3)) + " AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (3*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'M'"
                                                                    "WHEN PriceKwh >= " + str(QurrentPriceMax - (float(QurrentPriceMax-QurrentPriceMin)/3)) + "                                                                                         AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (3*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'H'"
                                                                    "ELSE 'o' END, "
                                                    "PriceLed4 = CASE WHEN PriceKwh <= " + str(QurrentPriceMin + (float(QurrentPriceMax-QurrentPriceMin)/3)) + "                                                                                        AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (4*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'L'"
                                                                    "WHEN PriceKwh > " + str(QurrentPriceMin + (float(QurrentPriceMax-QurrentPriceMin)/3)) + " AND PriceKwh < " + str(QurrentPriceMax - (float(QurrentPriceMax-QurrentPriceMin)/3)) + " AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (4*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'M'"
                                                                    "WHEN PriceKwh >= " + str(QurrentPriceMax - (float(QurrentPriceMax-QurrentPriceMin)/3)) + "                                                                                         AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (4*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'H'"
                                                                    "ELSE 'o' END, "
                                                    "PriceLed5 = CASE WHEN PriceKwh <= " + str(QurrentPriceMin + (float(QurrentPriceMax-QurrentPriceMin)/3)) + "                                                                                        AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (5*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'L'"
                                                                    "WHEN PriceKwh > " + str(QurrentPriceMin + (float(QurrentPriceMax-QurrentPriceMin)/3)) + " AND PriceKwh < " + str(QurrentPriceMax - (float(QurrentPriceMax-QurrentPriceMin)/3)) + " AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (5*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'M'"
                                                                    "WHEN PriceKwh >= " + str(QurrentPriceMax - (float(QurrentPriceMax-QurrentPriceMin)/3)) + "                                                                                         AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (5*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'H'"
                                                                    "ELSE 'o' END, "
                                                    "PriceLed6 = CASE WHEN PriceKwh <= " + str(QurrentPriceMin + (float(QurrentPriceMax-QurrentPriceMin)/3)) + "                                                                                        AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (6*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'L'"
                                                                    "WHEN PriceKwh > " + str(QurrentPriceMin + (float(QurrentPriceMax-QurrentPriceMin)/3)) + " AND PriceKwh < " + str(QurrentPriceMax - (float(QurrentPriceMax-QurrentPriceMin)/3)) + " AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (6*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'M'"
                                                                    "WHEN PriceKwh >= " + str(QurrentPriceMax - (float(QurrentPriceMax-QurrentPriceMin)/3)) + "                                                                                         AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (6*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'H'"
                                                                    "ELSE 'o' END, "
                                                    "PriceLed7 = CASE WHEN PriceKwh <= " + str(QurrentPriceMin + (float(QurrentPriceMax-QurrentPriceMin)/3)) + "                                                                                        AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (7*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'L'"
                                                                    "WHEN PriceKwh > " + str(QurrentPriceMin + (float(QurrentPriceMax-QurrentPriceMin)/3)) + " AND PriceKwh < " + str(QurrentPriceMax - (float(QurrentPriceMax-QurrentPriceMin)/3)) + " AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (7*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'M'"
                                                                    "WHEN PriceKwh >= " + str(QurrentPriceMax - (float(QurrentPriceMax-QurrentPriceMin)/3)) + "                                                                                         AND PriceKwh-" + str(QurrentPriceMin) + " >= " + str( (7*(float(QurrentPriceMax-QurrentPriceMin)/7)) ) + " THEN 'H'"
                                                                    "ELSE 'o' END "
                                                "WHERE Date >= '" + time.strftime("%Y-%m-%d 00:00", local_time) + "' AND ifnull(PriceKwh,0) <> 0;")
    connQurrent.commit()
    connQurrent.close()

EnergyPlanner_Firmata.ino

C/C++
Arduino code for running FirmataPlus and FastLED
/*
  Firmata is a generic protocol for communicating with microcontrollers
  from software on a host computer. It is intended to work with
  any host computer software package.

  To download a host software package, please clink on the following link
  to open the list of Firmata client libraries your default browser.

  https://github.com/firmata/arduino#firmata-client-libraries

  Copyright (C) 2006-2008 Hans-Christoph Steiner.  All rights reserved.
  Copyright (C) 2010-2011 Paul Stoffregen.  All rights reserved.
  Copyright (C) 2009 Shigeru Kobayashi.  All rights reserved.
  Copyright (C) 2009-2016 Jeff Hoefs.  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  See file LICENSE.txt for further informations on licensing terms.

  Last updated by Jeff Hoefs: January 10th, 2016

*/

/*
  README

  StandardFirmataPlus adds additional features that may exceed the Flash and
  RAM sizes of Arduino boards such as ATMega328p (Uno) and ATMega32u4
  (Leonardo, Micro, Yun, etc). It is best to use StandardFirmataPlus with higher
  memory boards such as the Arduino Mega, Arduino Due, Teensy 3.0/3.1/3.2.

  All Firmata examples that are appended with "Plus" add the following features:

  - Ability to interface with serial devices using UART, USART, or SoftwareSerial
    depending on the capatilities of the board.




  At the time of this writing, StandardFirmataPlus will still compile and run
  on ATMega328p and ATMega32u4-based boards, but future versions of this sketch
  may not as new features are added.
*/

#include <Servo.h>
#include <Wire.h>
#include <FirmataPlus.h>

#define I2C_WRITE                   B00000000
#define I2C_READ                    B00001000
#define I2C_READ_CONTINUOUSLY       B00010000
#define I2C_STOP_READING            B00011000
#define I2C_READ_WRITE_MODE_MASK    B00011000
#define I2C_10BIT_ADDRESS_MODE_MASK B00100000
#define I2C_END_TX_MASK             B01000000
#define I2C_STOP_TX                 1
#define I2C_RESTART_TX              0
#define I2C_MAX_QUERIES             8
#define I2C_REGISTER_NOT_SPECIFIED  -1

// the minimum interval for sampling analog input
#define MINIMUM_SAMPLING_INTERVAL   1


/* TONE for PIEZO */
#define TONE_TONE 0
#define TONE_NO_TONE 1


/* FASTLED */
#include <FastLED.h>
#define FASTLED_TYPE               WS2812
#define FASTLED_PIN_1              5
#define FASTLED_PIN_2              7
#define FASTLED_COLOR_ORDER        GRB
#define FASTLED_BRIGHTNESS         96
#define FASTLED_FRAMES_PER_SECOND  120
#define FASTLED_NUM_LEDS           96
CRGB FastLEDs_1[FASTLED_NUM_LEDS];
CRGB FastLEDs_2[FASTLED_NUM_LEDS];

/* FASTLED FIRMATA */
#define FASTLED_DATA 0x74
#define FASTLED_COLOR 0
#define FASTLED_COLOR_BAR 1
#define FASTLED_SHOW 2
#define FASTLED_BRIGHTNESS 3
#define FASTLED_CLEAR_ALL 4
#define FASTLED_CLEAR_BAR 5
#define FASTLED_TEST 6
#define FASTLED_ALL 7

#define FASTLED_SHOW_NOW 0
#define FASTLED_SHOW_LATER 1


/*==============================================================================
 * GLOBAL VARIABLES
 *============================================================================*/

#ifdef FIRMATA_SERIAL_FEATURE
SerialFirmata serialFeature;
#endif

/* analog inputs */
int analogInputsToReport = 0; // bitwise array to store pin reporting

/* digital input ports */
byte reportPINs[TOTAL_PORTS];       // 1 = report this port, 0 = silence
byte previousPINs[TOTAL_PORTS];     // previous 8 bits sent

/* pins configuration */
byte pinConfig[TOTAL_PINS];         // configuration of every pin
byte portConfigInputs[TOTAL_PORTS]; // each bit: 1 = pin in INPUT, 0 = anything else
int pinState[TOTAL_PINS];           // any value that has been written
/* timer variables */
unsigned long currentMillis;        // store the current value from millis()
unsigned long previousMillis;       // for comparison with currentMillis
unsigned int samplingInterval = 19; // how often to run the main loop (in ms)

/* i2c data */
struct i2c_device_info {
  byte addr;
  int reg;
  byte bytes;
  byte stopTX;
};

/* for i2c read continuous more */
i2c_device_info query[I2C_MAX_QUERIES];

byte i2cRxData[64];
boolean isI2CEnabled = false;
signed char queryIndex = -1;
// default delay time between i2c read request and Wire.requestFrom()
unsigned int i2cReadDelayTime = 0;

Servo servos[MAX_SERVOS];
byte servoPinMap[TOTAL_PINS];
byte detachedServos[MAX_SERVOS];
byte detachedServoCount = 0;
byte servoCount = 0;

boolean isResetting = false;

// Forward declare a few functions to avoid compiler errors with older versions
// of the Arduino IDE.
//void setPinModeCallback(byte, int);
//void reportAnalogCallback(byte analogPin, int value);
//void sysexCallback(byte, byte, byte*);

/* utility functions */
void wireWrite(byte data)
{
#if ARDUINO >= 100
  Wire.write((byte)data);
#else
  Wire.send(data);
#endif
}

byte wireRead(void)
{
#if ARDUINO >= 100
  return Wire.read();
#else
  return Wire.receive();
#endif
}

/*==============================================================================
 * FUNCTIONS
 *============================================================================*/

void attachServo(byte pin, int minPulse, int maxPulse)
{
  if (servoCount < MAX_SERVOS) {
    // reuse indexes of detached servos until all have been reallocated
    if (detachedServoCount > 0) {
      servoPinMap[pin] = detachedServos[detachedServoCount - 1];
      if (detachedServoCount > 0) detachedServoCount--;
    } else {
      servoPinMap[pin] = servoCount;
      servoCount++;
    }
    if (minPulse > 0 && maxPulse > 0) {
      servos[servoPinMap[pin]].attach(PIN_TO_DIGITAL(pin), minPulse, maxPulse);
    } else {
      servos[servoPinMap[pin]].attach(PIN_TO_DIGITAL(pin));
    }
  } else {
    Firmata.sendString("Max servos attached");
  }
}

void detachServo(byte pin)
{
  servos[servoPinMap[pin]].detach();
  // if we're detaching the last servo, decrement the count
  // otherwise store the index of the detached servo
  if (servoPinMap[pin] == servoCount && servoCount > 0) {
    servoCount--;
  } else if (servoCount > 0) {
    // keep track of detached servos because we want to reuse their indexes
    // before incrementing the count of attached servos
    detachedServoCount++;
    detachedServos[detachedServoCount - 1] = servoPinMap[pin];
  }

  servoPinMap[pin] = 255;
}


void readAndReportData(byte address, int theRegister, byte numBytes, byte stopTX) {
  // allow I2C requests that don't require a register read
  // for example, some devices using an interrupt pin to signify new data available
  // do not always require the register read so upon interrupt you call Wire.requestFrom()
  if (theRegister != I2C_REGISTER_NOT_SPECIFIED) {
    Wire.beginTransmission(address);
    wireWrite((byte)theRegister);
    Wire.endTransmission(stopTX); // default = true
    // do not set a value of 0
    if (i2cReadDelayTime > 0) {
      // delay is necessary for some devices such as WiiNunchuck
      delayMicroseconds(i2cReadDelayTime);
    }
  } else {
    theRegister = 0;  // fill the register with a dummy value
  }

  Wire.requestFrom(address, numBytes);  // all bytes are returned in requestFrom

  // check to be sure correct number of bytes were returned by slave
  if (numBytes < Wire.available()) {
    Firmata.sendString("I2C: Too many bytes received");
  } else if (numBytes > Wire.available()) {
    Firmata.sendString("I2C: Too few bytes received");
  }

  i2cRxData[0] = address;
  i2cRxData[1] = theRegister;

  for (int i = 0; i < numBytes && Wire.available(); i++) {
    i2cRxData[2 + i] = wireRead();
  }

  // send slave address, register and received bytes
  Firmata.sendSysex(SYSEX_I2C_REPLY, numBytes + 2, i2cRxData);
}

void outputPort(byte portNumber, byte portValue, byte forceSend)
{
  // pins not configured as INPUT are cleared to zeros
  portValue = portValue & portConfigInputs[portNumber];
  // only send if the value is different than previously sent
  if (forceSend || previousPINs[portNumber] != portValue) {
    Firmata.sendDigitalPort(portNumber, portValue);
    previousPINs[portNumber] = portValue;
  }
}

/* -----------------------------------------------------------------------------
 * check all the active digital inputs for change of state, then add any events
 * to the Serial output queue using Serial.print() */
void checkDigitalInputs(void)
{
  /* Using non-looping code allows constants to be given to readPort().
   * The compiler will apply substantial optimizations if the inputs
   * to readPort() are compile-time constants. */
  if (TOTAL_PORTS > 0 && reportPINs[0]) outputPort(0, readPort(0, portConfigInputs[0]), false);
  if (TOTAL_PORTS > 1 && reportPINs[1]) outputPort(1, readPort(1, portConfigInputs[1]), false);
  if (TOTAL_PORTS > 2 && reportPINs[2]) outputPort(2, readPort(2, portConfigInputs[2]), false);
  if (TOTAL_PORTS > 3 && reportPINs[3]) outputPort(3, readPort(3, portConfigInputs[3]), false);
  if (TOTAL_PORTS > 4 && reportPINs[4]) outputPort(4, readPort(4, portConfigInputs[4]), false);
  if (TOTAL_PORTS > 5 && reportPINs[5]) outputPort(5, readPort(5, portConfigInputs[5]), false);
  if (TOTAL_PORTS > 6 && reportPINs[6]) outputPort(6, readPort(6, portConfigInputs[6]), false);
  if (TOTAL_PORTS > 7 && reportPINs[7]) outputPort(7, readPort(7, portConfigInputs[7]), false);
  if (TOTAL_PORTS > 8 && reportPINs[8]) outputPort(8, readPort(8, portConfigInputs[8]), false);
  if (TOTAL_PORTS > 9 && reportPINs[9]) outputPort(9, readPort(9, portConfigInputs[9]), false);
  if (TOTAL_PORTS > 10 && reportPINs[10]) outputPort(10, readPort(10, portConfigInputs[10]), false);
  if (TOTAL_PORTS > 11 && reportPINs[11]) outputPort(11, readPort(11, portConfigInputs[11]), false);
  if (TOTAL_PORTS > 12 && reportPINs[12]) outputPort(12, readPort(12, portConfigInputs[12]), false);
  if (TOTAL_PORTS > 13 && reportPINs[13]) outputPort(13, readPort(13, portConfigInputs[13]), false);
  if (TOTAL_PORTS > 14 && reportPINs[14]) outputPort(14, readPort(14, portConfigInputs[14]), false);
  if (TOTAL_PORTS > 15 && reportPINs[15]) outputPort(15, readPort(15, portConfigInputs[15]), false);
}

// -----------------------------------------------------------------------------
/* sets the pin mode to the correct state and sets the relevant bits in the
 * two bit-arrays that track Digital I/O and PWM status
 */
void setPinModeCallback(byte pin, int mode)
{
  if (Firmata.getPinMode(pin) == PIN_MODE_IGNORE)
    return;

  if (Firmata.getPinMode(pin) == PIN_MODE_I2C && isI2CEnabled && mode != PIN_MODE_I2C) {
    // disable i2c so pins can be used for other functions
    // the following if statements should reconfigure the pins properly
    disableI2CPins();
  }
  if (IS_PIN_DIGITAL(pin) && mode != PIN_MODE_SERVO) {
    if (servoPinMap[pin] < MAX_SERVOS && servos[servoPinMap[pin]].attached()) {
      detachServo(pin);
    }
  }
  if (IS_PIN_ANALOG(pin)) {
    reportAnalogCallback(PIN_TO_ANALOG(pin), mode == PIN_MODE_ANALOG ? 1 : 0); // turn on/off reporting
  }
  if (IS_PIN_DIGITAL(pin)) {
    if (mode == INPUT || mode == PIN_MODE_PULLUP) {
      portConfigInputs[pin / 8] |= (1 << (pin & 7));
    } else {
      portConfigInputs[pin / 8] &= ~(1 << (pin & 7));
    }
  }
  Firmata.setPinState(pin, 0);
  switch (mode) {
    case PIN_MODE_ANALOG:
      if (IS_PIN_ANALOG(pin)) {
        if (IS_PIN_DIGITAL(pin)) {
          pinMode(PIN_TO_DIGITAL(pin), INPUT);    // disable output driver
#if ARDUINO <= 100
          // deprecated since Arduino 1.0.1 - TODO: drop support in Firmata 2.6
          digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable internal pull-ups
#endif
        }
        Firmata.setPinMode(pin, PIN_MODE_ANALOG);
      }
      break;
    case INPUT:
      if (IS_PIN_DIGITAL(pin)) {
        pinMode(PIN_TO_DIGITAL(pin), INPUT);    // disable output driver
#if ARDUINO <= 100
        // deprecated since Arduino 1.0.1 - TODO: drop support in Firmata 2.6
        digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable internal pull-ups
#endif
        Firmata.setPinMode(pin, INPUT);
      }
      break;
    case PIN_MODE_PULLUP:
      if (IS_PIN_DIGITAL(pin)) {
        pinMode(PIN_TO_DIGITAL(pin), INPUT_PULLUP);
        Firmata.setPinMode(pin, PIN_MODE_PULLUP);
        Firmata.setPinState(pin, 1);
      }
      break;
    case OUTPUT:
      if (IS_PIN_DIGITAL(pin)) {
        if (Firmata.getPinMode(pin) == PIN_MODE_PWM) {
          // Disable PWM if pin mode was previously set to PWM.
          digitalWrite(PIN_TO_DIGITAL(pin), LOW);
        }
        pinMode(PIN_TO_DIGITAL(pin), OUTPUT);
        Firmata.setPinMode(pin, OUTPUT);
      }
      break;
    case PIN_MODE_PWM:
      if (IS_PIN_PWM(pin)) {
        pinMode(PIN_TO_PWM(pin), OUTPUT);
        analogWrite(PIN_TO_PWM(pin), 0);
        Firmata.setPinMode(pin, PIN_MODE_PWM);
      }
      break;
    case PIN_MODE_SERVO:
      if (IS_PIN_DIGITAL(pin)) {
        Firmata.setPinMode(pin, PIN_MODE_SERVO);
        if (servoPinMap[pin] == 255 || !servos[servoPinMap[pin]].attached()) {
          // pass -1 for min and max pulse values to use default values set
          // by Servo library
          attachServo(pin, -1, -1);
        }
      }
      break;
    case PIN_MODE_I2C:
      if (IS_PIN_I2C(pin)) {
        // mark the pin as i2c
        // the user must call I2C_CONFIG to enable I2C for a device
        Firmata.setPinMode(pin, PIN_MODE_I2C);
      }
      break;
    case PIN_MODE_SERIAL:
#ifdef FIRMATA_SERIAL_FEATURE
      serialFeature.handlePinMode(pin, PIN_MODE_SERIAL);
#endif
      break;
    case PIN_MODE_TONE:
      Firmata.setPinMode(pin, PIN_MODE_TONE);
      //pinConfig[pin] = TONE ;
      break ;
    default:
      Firmata.sendString("Unknown pin mode"); // TODO: put error msgs in EEPROM
      break ;
  }
  // TODO: save status to EEPROM here, if changed
}

/*
 * Sets the value of an individual pin. Useful if you want to set a pin value but
 * are not tracking the digital port state.
 * Can only be used on pins configured as OUTPUT.
 * Cannot be used to enable pull-ups on Digital INPUT pins.
 */
void setPinValueCallback(byte pin, int value)
{
  if (pin < TOTAL_PINS && IS_PIN_DIGITAL(pin)) {
    if (Firmata.getPinMode(pin) == OUTPUT) {
      Firmata.setPinState(pin, value);
      digitalWrite(PIN_TO_DIGITAL(pin), value);
    }
  }
}

void analogWriteCallback(byte pin, int value)
{
  if (pin < TOTAL_PINS) {
    switch (Firmata.getPinMode(pin)) {
      case PIN_MODE_SERVO:
        if (IS_PIN_DIGITAL(pin))
          servos[servoPinMap[pin]].write(value);
        Firmata.setPinState(pin, value);
        break;
      case PIN_MODE_PWM:
        if (IS_PIN_PWM(pin))
          analogWrite(PIN_TO_PWM(pin), value);
        Firmata.setPinState(pin, value);
        break;
    }
  }
}

void digitalWriteCallback(byte port, int value)
{
  byte pin, lastPin, pinValue, mask = 1, pinWriteMask = 0;

  if (port < TOTAL_PORTS) {
    // create a mask of the pins on this port that are writable.
    lastPin = port * 8 + 8;
    if (lastPin > TOTAL_PINS) lastPin = TOTAL_PINS;
    for (pin = port * 8; pin < lastPin; pin++) {
      // do not disturb non-digital pins (eg, Rx & Tx)
      if (IS_PIN_DIGITAL(pin)) {
        // only write to OUTPUT and INPUT (enables pullup)
        // do not touch pins in PWM, ANALOG, SERVO or other modes
        if (Firmata.getPinMode(pin) == OUTPUT || Firmata.getPinMode(pin) == INPUT) {
          pinValue = ((byte)value & mask) ? 1 : 0;
          if (Firmata.getPinMode(pin) == OUTPUT) {
            pinWriteMask |= mask;
          } else if (Firmata.getPinMode(pin) == INPUT && pinValue == 1 && Firmata.getPinState(pin) != 1) {
            // only handle INPUT here for backwards compatibility
#if ARDUINO > 100
            pinMode(pin, INPUT_PULLUP);
#else
            // only write to the INPUT pin to enable pullups if Arduino v1.0.0 or earlier
            pinWriteMask |= mask;
#endif
          }
          Firmata.setPinState(pin, pinValue);
        }
      }
      mask = mask << 1;
    }
    writePort(port, (byte)value, pinWriteMask);
  }
}


// -----------------------------------------------------------------------------
/* sets bits in a bit array (int) to toggle the reporting of the analogIns
 */
//void FirmataClass::setAnalogPinReporting(byte pin, byte state) {
//}
void reportAnalogCallback(byte analogPin, int value)
{
  if (analogPin < TOTAL_ANALOG_PINS) {
    if (value == 0) {
      analogInputsToReport = analogInputsToReport & ~ (1 << analogPin);
    } else {
      analogInputsToReport = analogInputsToReport | (1 << analogPin);
      // prevent during system reset or all analog pin values will be reported
      // which may report noise for unconnected analog pins
      if (!isResetting) {
        // Send pin value immediately. This is helpful when connected via
        // ethernet, wi-fi or bluetooth so pin states can be known upon
        // reconnecting.
        Firmata.sendAnalog(analogPin, analogRead(analogPin));
      }
    }
    // TODO: save status to EEPROM here, if changed
  }
}

void reportDigitalCallback(byte port, int value)
{
  if (port < TOTAL_PORTS) {
    reportPINs[port] = (byte)value;
    // Send port value immediately. This is helpful when connected via
    // ethernet, wi-fi or bluetooth so pin states can be known upon
    // reconnecting.
    if (value) outputPort(port, readPort(port, portConfigInputs[port]), true);
  }
  // do not disable analog reporting on these 8 pins, to allow some
  // pins used for digital, others analog.  Instead, allow both types
  // of reporting to be enabled, but check if the pin is configured
  // as analog when sampling the analog inputs.  Likewise, while
  // scanning digital pins, portConfigInputs will mask off values from any
  // pins configured as analog
}

/*==============================================================================
 * SYSEX-BASED commands
 *============================================================================*/

void sysexCallback(byte command, byte argc, byte *argv)
{
  byte mode;
  byte stopTX;
  byte slaveAddress;
  byte data;
  int slaveRegister;
  unsigned int delayTime;

  // used for tone
  byte pin;
  int frequency;
  int duration;
  
  switch (command) {
    case I2C_REQUEST:
      mode = argv[1] & I2C_READ_WRITE_MODE_MASK;
      if (argv[1] & I2C_10BIT_ADDRESS_MODE_MASK) {
        Firmata.sendString("10-bit addressing not supported");
        return;
      }
      else {
        slaveAddress = argv[0];
      }

      // need to invert the logic here since 0 will be default for client
      // libraries that have not updated to add support for restart tx
      if (argv[1] & I2C_END_TX_MASK) {
        stopTX = I2C_RESTART_TX;
      }
      else {
        stopTX = I2C_STOP_TX; // default
      }

      switch (mode) {
        case I2C_WRITE:
          Wire.beginTransmission(slaveAddress);
          for (byte i = 2; i < argc; i += 2) {
            data = argv[i] + (argv[i + 1] << 7);
            wireWrite(data);
          }
          Wire.endTransmission();
          delayMicroseconds(70);
          break;
        case I2C_READ:
          if (argc == 6) {
            // a slave register is specified
            slaveRegister = argv[2] + (argv[3] << 7);
            data = argv[4] + (argv[5] << 7);  // bytes to read
          }
          else {
            // a slave register is NOT specified
            slaveRegister = I2C_REGISTER_NOT_SPECIFIED;
            data = argv[2] + (argv[3] << 7);  // bytes to read
          }
          readAndReportData(slaveAddress, (int)slaveRegister, data, stopTX);
          break;
        case I2C_READ_CONTINUOUSLY:
          if ((queryIndex + 1) >= I2C_MAX_QUERIES) {
            // too many queries, just ignore
            Firmata.sendString("too many queries");
            break;
          }
          if (argc == 6) {
            // a slave register is specified
            slaveRegister = argv[2] + (argv[3] << 7);
            data = argv[4] + (argv[5] << 7);  // bytes to read
          }
          else {
            // a slave register is NOT specified
            slaveRegister = (int)I2C_REGISTER_NOT_SPECIFIED;
            data = argv[2] + (argv[3] << 7);  // bytes to read
          }
          queryIndex++;
          query[queryIndex].addr = slaveAddress;
          query[queryIndex].reg = slaveRegister;
          query[queryIndex].bytes = data;
          query[queryIndex].stopTX = stopTX;
          break;
        case I2C_STOP_READING:
          byte queryIndexToSkip;
          // if read continuous mode is enabled for only 1 i2c device, disable
          // read continuous reporting for that device
          if (queryIndex <= 0) {
            queryIndex = -1;
          } else {
            queryIndexToSkip = 0;
            // if read continuous mode is enabled for multiple devices,
            // determine which device to stop reading and remove it's data from
            // the array, shifiting other array data to fill the space
            for (byte i = 0; i < queryIndex + 1; i++) {
              if (query[i].addr == slaveAddress) {
                queryIndexToSkip = i;
                break;
              }
            }

            for (byte i = queryIndexToSkip; i < queryIndex + 1; i++) {
              if (i < I2C_MAX_QUERIES) {
                query[i].addr = query[i + 1].addr;
                query[i].reg = query[i + 1].reg;
                query[i].bytes = query[i + 1].bytes;
                query[i].stopTX = query[i + 1].stopTX;
              }
            }
            queryIndex--;
          }
          break;
        default:
          break;
      }
      break;
    case I2C_CONFIG:
      delayTime = (argv[0] + (argv[1] << 7));

      if (delayTime > 0) {
        i2cReadDelayTime = delayTime;
      }

      if (!isI2CEnabled) {
        enableI2CPins();
      }

      break;
    case SERVO_CONFIG:
      if (argc > 4) {
        // these vars are here for clarity, they'll optimized away by the compiler
        byte pin = argv[0];
        int minPulse = argv[1] + (argv[2] << 7);
        int maxPulse = argv[3] + (argv[4] << 7);

        if (IS_PIN_DIGITAL(pin)) {
          if (servoPinMap[pin] < MAX_SERVOS && servos[servoPinMap[pin]].attached()) {
            detachServo(pin);
          }
          attachServo(pin, minPulse, maxPulse);
          setPinModeCallback(pin, PIN_MODE_SERVO);
        }
      }
      break;
    case SAMPLING_INTERVAL:
      if (argc > 1) {
        samplingInterval = argv[0] + (argv[1] << 7);
        if (samplingInterval < MINIMUM_SAMPLING_INTERVAL) {
          samplingInterval = MINIMUM_SAMPLING_INTERVAL;
        }
      } else {
        //Firmata.sendString("Not enough data");
      }
      break;
    case EXTENDED_ANALOG:
      if (argc > 1) {
        int val = argv[1];
        if (argc > 2) val |= (argv[2] << 7);
        if (argc > 3) val |= (argv[3] << 14);
        analogWriteCallback(argv[0], val);
      }
      break;
    case CAPABILITY_QUERY:
      Firmata.write(START_SYSEX);
      Firmata.write(CAPABILITY_RESPONSE);
      for (byte pin = 0; pin < TOTAL_PINS; pin++) {
        if (IS_PIN_DIGITAL(pin)) {
          Firmata.write((byte)INPUT);
          Firmata.write(1);
          Firmata.write((byte)PIN_MODE_PULLUP);
          Firmata.write(1);
          Firmata.write((byte)OUTPUT);
          Firmata.write(1);
        }
        if (IS_PIN_ANALOG(pin)) {
          Firmata.write(PIN_MODE_ANALOG);
          Firmata.write(10); // 10 = 10-bit resolution
        }
        if (IS_PIN_PWM(pin)) {
          Firmata.write(PIN_MODE_PWM);
          Firmata.write(DEFAULT_PWM_RESOLUTION);
        }
        if (IS_PIN_DIGITAL(pin)) {
          Firmata.write(PIN_MODE_SERVO);
          Firmata.write(14);
        }
        if (IS_PIN_I2C(pin)) {
          Firmata.write(PIN_MODE_I2C);
          Firmata.write(1);  // TODO: could assign a number to map to SCL or SDA
        }
#ifdef FIRMATA_SERIAL_FEATURE
        serialFeature.handleCapability(pin);
#endif
        Firmata.write(127);
      }
      Firmata.write(END_SYSEX);
      break;
    case PIN_STATE_QUERY:
      if (argc > 0) {
        byte pin = argv[0];
        Firmata.write(START_SYSEX);
        Firmata.write(PIN_STATE_RESPONSE);
        Firmata.write(pin);
        if (pin < TOTAL_PINS) {
          Firmata.write(Firmata.getPinMode(pin));
          Firmata.write((byte)Firmata.getPinState(pin) & 0x7F);
          if (Firmata.getPinState(pin) & 0xFF80) Firmata.write((byte)(Firmata.getPinState(pin) >> 7) & 0x7F);
          if (Firmata.getPinState(pin) & 0xC000) Firmata.write((byte)(Firmata.getPinState(pin) >> 14) & 0x7F);
        }
        Firmata.write(END_SYSEX);
      }
      break;
    case ANALOG_MAPPING_QUERY:
      Firmata.write(START_SYSEX);
      Firmata.write(ANALOG_MAPPING_RESPONSE);
      for (byte pin = 0; pin < TOTAL_PINS; pin++) {
        Firmata.write(IS_PIN_ANALOG(pin) ? PIN_TO_ANALOG(pin) : 127);
      }
      Firmata.write(END_SYSEX);
      break;

    case SERIAL_MESSAGE:
#ifdef FIRMATA_SERIAL_FEATURE
      serialFeature.handleSysex(command, argc, argv);
#endif
      break;
      
    /* TONE for PIEZO */
    case TONE_DATA:
      byte toneCommand, pin;
      int frequency, duration;

      toneCommand = argv[0];
      pin = argv[1];

      if (toneCommand == TONE_TONE) {
        frequency = argv[2] + (argv[3] << 7);
        // duration is currently limited to 16,383 ms
        duration = argv[4] + (argv[5] << 7);
        tone(pin, frequency, duration);
      }
      else if (toneCommand == TONE_NO_TONE) {
        noTone(pin);
      }
      break ;

    /* FASTLEDS */
    case FASTLED_DATA:
      {
        if (argv[0] == FASTLED_COLOR)
        {
          int FastLED_X = argv[1];
          int FastLED_Y = argv[2];
          int FastLED_Red = argv[3] + (argv[4] << 7);
          int FastLED_Green = argv[5] + (argv[6] << 7);
          int FastLED_Blue = argv[7] + (argv[8] << 7);
          int FastLED_Show = argv[9];

          if (FastLED_X >= 0 and FastLED_X <= 11) {
            FastLEDs_1[(FastLED_X*8)+FastLED_Y].setRGB( FastLED_Red, FastLED_Green, FastLED_Blue);
          } else if (FastLED_X >= 12 and FastLED_X <= 23) {
            FastLEDs_2[((FastLED_X-12)*8)+FastLED_Y].setRGB( FastLED_Red, FastLED_Green, FastLED_Blue);
          }
          if (FastLED_Show == FASTLED_SHOW_NOW) {
            FastLED.show();
          }
        }
        
        if (argv[0] == FASTLED_BRIGHTNESS)
        {
          int FastLED_Bright = argv[1] + (argv[2] << 7);
          FastLED.setBrightness(FastLED_Bright);
        }
        
        if (argv[0] == FASTLED_SHOW)
        {
          FastLED.show();
        }
        
        if (argv[0] == FASTLED_CLEAR_ALL)
        {
          FastLED.clear();
          FastLED.show();
          for (byte i = 0; i < FASTLED_NUM_LEDS; i++) {
            FastLEDs_1[i].setRGB( 0, 0, 0);
            FastLEDs_2[i].setRGB( 0, 0, 0);
          }
          FastLED.show();
        }
        
        if (argv[0] == FASTLED_CLEAR_BAR)
        {
          int FastLED_X = argv[1];
          for (byte i = 0; i < 8; i++) {
            if (FastLED_X >= 0 and FastLED_X <= 11) {
              FastLEDs_1[(FastLED_X*8)+i].setRGB( 0, 0, 0);
            } else if (FastLED_X >= 12 and FastLED_X <= 23) {
              FastLEDs_2[((FastLED_X-12)*8)+i].setRGB( 0, 0, 0);
            }
          }
          FastLED.show();
        }

        if (argv[0] == FASTLED_TEST)
        {
          int FastLED_Red = argv[1] + (argv[2] << 7);
          int FastLED_Green = argv[3] + (argv[4] << 7);
          int FastLED_Blue = argv[5] + (argv[6] << 7);

          for (byte i = 0; i < FASTLED_NUM_LEDS; i++) {
            FastLEDs_1[i].setRGB( FastLED_Red, FastLED_Green, FastLED_Blue);
            FastLEDs_2[i].setRGB( FastLED_Red, FastLED_Green, FastLED_Blue);
          }

          FastLED.setBrightness(60);
          FastLED.show();
          delay(1000);
          
          FastLED.setBrightness(30);
          FastLED.show();
          delay(1000);
          
          FastLED.setBrightness(10);
          FastLED.show();
        }

        if (argv[0] == FASTLED_ALL)
        {
          int FastLED_Red = argv[1] + (argv[2] << 7);
          int FastLED_Green = argv[3] + (argv[4] << 7);
          int FastLED_Blue = argv[5] + (argv[6] << 7);

          for (byte i = 0; i < FASTLED_NUM_LEDS; i++) {
            FastLEDs_1[i].setRGB( FastLED_Red, FastLED_Green, FastLED_Blue);
            FastLEDs_2[i].setRGB( FastLED_Red, FastLED_Green, FastLED_Blue);
          }

          FastLED.show();
        }
        while(Serial1.available() > 0) { Serial1.flush(); } 
      }
      break;

  }
}

void enableI2CPins()
{
  byte i;
  // is there a faster way to do this? would probaby require importing
  // Arduino.h to get SCL and SDA pins
  for (i = 0; i < TOTAL_PINS; i++) {
    if (IS_PIN_I2C(i)) {
      // mark pins as i2c so they are ignore in non i2c data requests
      setPinModeCallback(i, PIN_MODE_I2C);
    }
  }

  isI2CEnabled = true;

  Wire.begin();
}

/* disable the i2c pins so they can be used for other functions */
void disableI2CPins() {
  isI2CEnabled = false;
  // disable read continuous mode for all devices
  queryIndex = -1;
}

/*==============================================================================
 * SETUP()
 *============================================================================*/

void systemResetCallback()
{
  isResetting = true;

  // initialize a defalt state
  // TODO: option to load config from EEPROM instead of default

#ifdef FIRMATA_SERIAL_FEATURE
  serialFeature.reset();
#endif

  if (isI2CEnabled) {
    disableI2CPins();
  }

  for (byte i = 0; i < TOTAL_PORTS; i++) {
    reportPINs[i] = false;    // by default, reporting off
    portConfigInputs[i] = 0;  // until activated
    previousPINs[i] = 0;
  }

  for (byte i = 0; i < TOTAL_PINS; i++) {
    // pins with analog capability default to analog input
    // otherwise, pins default to digital output

    if (25 == i) {
      // This is the FASTLED pin D6, it cannot double as an Analog pin A7, so first set it to DIGITAL
      setPinModeCallback(i, OUTPUT);
    } else if (IS_PIN_ANALOG(i)) {
      // turns off pullup, configures everything
      setPinModeCallback(i, PIN_MODE_ANALOG);
    } else if (IS_PIN_DIGITAL(i)) {
      // sets the output to 0, configures portConfigInputs
      setPinModeCallback(i, OUTPUT);
    }

    servoPinMap[i] = 255;
  }
  // by default, do not report any analog inputs
  analogInputsToReport = 0;

  detachedServoCount = 0;
  servoCount = 0;

  /* send digital inputs to set the initial state on the host computer,
   * since once in the loop(), this firmware will only send on change */
  /*
  TODO: this can never execute, since no pins default to digital input
        but it will be needed when/if we support EEPROM stored config
  for (byte i=0; i < TOTAL_PORTS; i++) {
    outputPort(i, readPort(i, portConfigInputs[i]), true);
  }
  */
  isResetting = false;
}

void setup()
{
  //Serial.begin(9600);
  //while (!Serial);             // wait for serial monitor
  //Serial.println("\nFirmata debug");

  Firmata.setFirmwareVersion(FIRMATA_FIRMWARE_MAJOR_VERSION, FIRMATA_FIRMWARE_MINOR_VERSION);

  Firmata.attach(ANALOG_MESSAGE, analogWriteCallback);
  Firmata.attach(DIGITAL_MESSAGE, digitalWriteCallback);
  Firmata.attach(REPORT_ANALOG, reportAnalogCallback);
  Firmata.attach(REPORT_DIGITAL, reportDigitalCallback);
  Firmata.attach(SET_PIN_MODE, setPinModeCallback);
  Firmata.attach(SET_DIGITAL_PIN_VALUE, setPinValueCallback);
  Firmata.attach(START_SYSEX, sysexCallback);
  Firmata.attach(SYSTEM_RESET, systemResetCallback);

  // Save a couple of seconds by disabling the startup blink sequence.
  Firmata.disableBlinkVersion();

  // to use a port other than Serial, such as Serial1 on an Arduino Leonardo or Mega,
  // Call begin(baud) on the alternate serial port and pass it to Firmata to begin like this:
  Serial1.begin(57600);
  Firmata.begin(Serial1);
  // However do not do this if you are using SERIAL_MESSAGE

  //Firmata.begin(57600);
  while (!Serial1); // wait for serial port to connect. Needed for ATmega32u4-based boards

  systemResetCallback();  // reset to default config

  /* FASTLEDS */
  // tell FastLED about the LED strip configuration
  FastLED.addLeds<FASTLED_TYPE,FASTLED_PIN_1,FASTLED_COLOR_ORDER>(FastLEDs_1, FASTLED_NUM_LEDS);
  FastLED.addLeds<FASTLED_TYPE,FASTLED_PIN_2,FASTLED_COLOR_ORDER>(FastLEDs_2, FASTLED_NUM_LEDS);
  // set master brightness control
  FastLED.setBrightness(FASTLED_BRIGHTNESS);

}

/*==============================================================================
 * LOOP()
 *============================================================================*/
void loop()
{
  byte pin, analogPin;

  /* DIGITALREAD - as fast as possible, check for changes and output them to the
   * FTDI buffer using Serial.print()  */
  checkDigitalInputs();

  /* STREAMREAD - processing incoming messagse as soon as possible, while still
   * checking digital inputs.  */
  while (Firmata.available())
    Firmata.processInput();

  // TODO - ensure that Stream buffer doesn't go over 60 bytes

...

This file has been truncated, please download it to see its full contents.

Credits

Bastiaan Slee

Bastiaan Slee

5 projects • 34 followers
Tinkerer in the field of Home Automation, with the main goal: fun! Using Raspberry Pi, Arduino (+clones), LoRaWAN, NodeRed, 3D Printing

Comments