# import the necessary packages
import imutils
import cv2
import numpy as np
import time
from picamera import PiCamera
import RPi.GPIO as GPIO
import os
import urllib.request
import calendar

GPIO.setwarnings(False)

# Enter Your API Write key here
myAPI = '****************' 

# URL where we will send the data, Don't change it
baseURL = 'https://api.thingspeak.com/update?api_key=%s' % myAPI 
 
LED1_PIN = 27
LED2_PIN = 17

GPIO.setmode(GPIO.BCM)
GPIO.setup(LED1_PIN, GPIO.OUT)
GPIO.setup(LED2_PIN, GPIO.OUT)

camera = PiCamera()
camera.resolution = (1280, 720)
#camera.vflip = True
camera.contrast = 10
#camera.image_effect = "watercolor"
time.sleep(2)

# from google.colab.patches import cv2_imshow
# OpenCV's cv2.imshow() does not work on Google Colab

# define the dictionary of digit segments so we can identify each digit
DIGITS_LOOKUP = {
    (1, 1, 1, 0, 1, 1, 1): 0,
    (0, 0, 1, 0, 0, 1, 0): 1,
    (1, 0, 1, 1, 1, 0, 1): 2,
    (1, 0, 1, 1, 0, 1, 1): 3,
    (0, 1, 1, 1, 0, 1, 0): 4,
    (1, 1, 0, 1, 0, 1, 1): 5,
    (1, 1, 0, 1, 1, 1, 1): 6,
    (1, 0, 1, 0, 0, 1, 0): 7,
    (1, 1, 1, 1, 1, 1, 1): 8,
    (1, 1, 1, 1, 0, 1, 1): 9,
}

#       1111111
#       2     3
#       2     3
#       4444444
#       5     6
#       5     6
#       7777777

#    (x, y, w, h)
DIGITS_LOCATION = np.array(
    [[20, 40, 90, 171], 
    [127, 40, 90, 171],
    [244, 40, 90, 171],
    [360, 40, 90, 171],
    [477, 39, 90, 171],
    [590, 41, 45, 87],
    [651, 41, 45, 87],
    [711, 41, 45, 87]]
)


#######################

# convert a list of integers into a single integer
def convert(list):
    
    # Converting integer list to string list
    s = [str(i) for i in list]
    
    # Join list items using join()
    res = int("".join(s))
    
    return(res)
######################

# The purpose of this is to eliminate the rotation issues caused by assembling the housing on top of the water meter.
def calibration():
    
    i = 0
    sum = 0
    angle = -3
    control_LEDs(1)
    time.sleep(1)
    camera.capture("kuva.jpg")
    control_LEDs(0)
    time.sleep(10)
    
    #for angle in range(-5, 5, 1):
    for angle in np.arange(-1.8, -0.2, 0.10):
        try:
            readPicture(angle)
            i = i + 1
            sum = sum + angle
            print(angle)
        except:
            print("error " + str(angle))
        
    if i == 0:
        i = 1
            
    angle = sum/i
    print("result angle " + str(angle))
    return angle
#######################

def control_LEDs(switch):
    
    if switch == 0:
        GPIO.output(LED1_PIN, GPIO.LOW)
        GPIO.output(LED2_PIN, GPIO.LOW)
    if switch == 1:
        GPIO.output(LED1_PIN, GPIO.HIGH)
        GPIO.output(LED2_PIN, GPIO.HIGH)

    return
######################

def readPicture(angle):
    image = cv2.imread('kuva.jpg')

    y= 312
    x = 230
    h = 250
    w = 800
    image = image[y:y+h, x:x+w]
    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    thresh = cv2.adaptiveThreshold(image, 255,cv2.ADAPTIVE_THRESH_MEAN_C,\
            cv2.THRESH_BINARY_INV,25,5)

    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (1, 5))
    thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)

    # Join the fragmented digit parts
    kernel = np.ones((5,5),np.uint8)
    dilation = cv2.dilate(thresh,kernel,iterations = 3)
    erosion = cv2.erode(dilation,kernel,iterations = 3)

    # Rotate the image, so the digits are straight
    erosion = imutils.rotate_bound(erosion, angle)

    # loop over the digit area candidates
    image_w_bbox = erosion
    image_w_bbox = cv2.cvtColor(image_w_bbox, cv2.COLOR_GRAY2RGB)

    digits = []
    
    for k in [1,2,3,4,5,6,7]:

        (x, y, w, h) = DIGITS_LOCATION[k]
        
        #  print(DIGITS_LOCATION[k]) 
        roi = erosion[y:y + h, x:x + w]
  
        # compute the width and height of each of the 7 segments we are going to examine
        (roiH, roiW) = roi.shape
        (dW, dH) = (int(roiW * 0.20), int(roiH * 0.11))
        dHC = int(roiH * 0.05)

        
        # define the set of 7 segments
        segments = [
        ((dW, 0),            (w-dW, dH)),  # top
        ((0, dH),            (dW, (h//2)-dHC)), # top-left
        ((w-dW, dH),         (w, (h//2)-dHC)), # top-right
        ((dW-dHC, (h//2) - dHC), (w-dW+dHC, (h//2)+dHC)), # center
        ((0, (h//2)+dHC),     (dW, h-dH)), # bottom-left
        ((w-dW, (h//2)+dW),  (w, h-dHC)), # bottom-right
        ((dW, h-dH),         (w-dW, h))   # bottom
        ]
        on = [0] * len(segments)
        
        # loop over the segments
        for (i, ((xA, yA), (xB, yB))) in enumerate(segments):
        
        # extract the segment ROI, count the total number of thresholded pixels
        # in the segment, and then compute the area of the segment
            segROI = roi[yA:yB, xA:xB]
            total = cv2.countNonZero(segROI)
            area = (xB - xA) * (yB - yA)
            
            # if the total number of non-zero pixels is greater than
            # 40% of the area, mark the segment as "on"
            if total / float(area) > 0.71:
                on[i]= 1
                
        # lookup the digit and draw it on the image
        digit = DIGITS_LOOKUP[tuple(on)]
        digits.append(digit)

    #print("Here are the digits from left to right...")
    return(digits)

#######################

def resultToFile(ltr_cum, ltr_current):


    GPU_T = os.popen('/opt/vc/bin/vcgencmd measure_temp').readline().strip()
    CPU_T = os.popen('./my-pi-temp.sh').readline().strip()

    now = time.ctime()
    parsed_now = time.strptime(now) 

    results = open('results.txt', 'a')
    results.write(time.strftime("%Y%b%d %H:%M:%S", parsed_now) + (' '))
    results.write(str(ltr_current) + (' ') + str(ltr_cum) + (' ') + str(CPU_T) + (' ') + str(GPU_T) + str(angle) + ('\n'))
    results.close()

    return 0    
#######################

def sendToCloud1(ltr_current):
    try:
        conn = urllib.request.urlopen(baseURL + '&field1=%s' % (ltr_current))
        print(conn.read())
        conn.close()
        print("current consumption sent to server")

    except urllib.error.URLError as err:
        print("no internet connection to send current value")

    return 0

#######################

def sendToCloud2(ltr_cum):
    try:
        conn = urllib.request.urlopen(baseURL + '&field2=%s' % (ltr_cum-ltr_earlier_years))
        print(conn.read())
        conn.close()
        print("current consumption sent to server")

    except urllib.error.URLError as err:
        print("no internet connection to send current value")

    return 0

#######################
#####    MAIN   #######
#######################

ltr_cum = 0
ltr_cum_previous = 0
ltr_current = 0
ltr_previous = 0
ltr_earlier_years = 100454

control_LEDs(1)
time.sleep(2)
camera.capture("kuva.jpg")
#print("pic captured")
time.sleep(1)
control_LEDs(0)

time.sleep(10)
angle = calibration()
digits = readPicture(angle)
ltr_cum = (convert(digits))
print(ltr_cum)
resultToFile(ltr_cum, ltr_current)

while True:

    ltr_cum_previous = ltr_cum
    ltr_previous = ltr_current
    
    control_LEDs(1)
    time.sleep(1)
    
    results = open('results.txt', 'a')
    results.close()
    
    camera.capture("kuva.jpg")
    print("pic captured")
    time.sleep(6)
    
    results = open('results.txt', 'a')
    results.close()
    
    control_LEDs(0)

    time.sleep(2)
    
    try:
        digits = readPicture(angle)
    except:
        print("read error")
        
    time.sleep(2)

    try:    
        ltr_cum = (convert(digits))
    except:
        results = open('results.txt', 'a')
        results.write(str(ltr_current) + (' converting') + ('\n'))
        results.close()
        
        
    time.sleep(4)    
    resultToFile(ltr_cum, ltr_current)
    
    ltr_current = ltr_cum - ltr_cum_previous

    # Due to reflections or other factors, the digits may be invalid, e.g., 0 might be misidentified as 8 or vice versa.
    # The following section implements error handling to address such issues.
    if (ltr_cum_previous > ltr_cum is True):
  
        print("error1")
        ltr_cum = ltr_cum_previous
        time.sleep(5)

    elif ((ltr_cum - ltr_cum_previous) > 799 is True):
  
        print("error 800")
        ltr_cum = ltr_cum_previous
        time.sleep(4)

    elif ((ltr_current > 999) is True):

        print("error3")
        ltr_cum = ltr_cum_previous
        time.sleep(4)
               
    elif ((ltr_current == 0) & (ltr_previous == 0) is False):  
        sendToCloud1(ltr_current)
        time.sleep(30)
        sendToCloud2(ltr_cum)
        time.sleep(20)

    
    else:
        time.sleep(30)
        
    time.sleep(1)