#!/usr/bin/python3
#
'''
#
# Read data from serial connected to the digital meter P1 port
#
# Created by Jens Depuydt, modified luc berger
# https://www.jensd.be
# https://github.com/jensdepuydt
# https://github.com/jensdepuydt/belgian_digitalmeter_p1/blob/main/read_p1.py

# Luc Berger added
#SendThingSpeak()
#def SendMainPC()

LB NOTES
https://thingspeak.com/prices#:~:text=License%20Options,%2C%20Academic%2C%20Student%20and%20Home.
8200 msg/day
-> 1 msg every 15 sec:  min 86400÷8000=10.8
. 4msg/min 
. =240 msg/h 
. =5760 /day
This example shows how to use the RaspberryPi running Python 2.7 to collect CPU temperature and CPU utilization as a percentage. The 
data is collected once every 15 seconds and the channel is updated once every 2 minutes. Since the RaspberryPi
does not have a real-time clock, relative time stamps are used. 
'''

from urllib.parse import urlencode, quote_plus
import http.client
#from urllib.parse import quote
import serial
#import sys
import crcmod.predefined
import re
import re
from tabulate import tabulate
#import numpy as np
import json
import time
import os
import array as arr
import traceback

# Change your serial port here:
serialport = '/dev/ttyUSB0'

# Enable debug if needed:

debug = True
debug = False

#print data file ?
makeTxt=False 
makeTxt=True
#every x telegram (aprox 1sec)
makeTxtEvery=30

debugThingSpeak= True
debugThingSpeak= False

debugSendMainPC= True
debugSendMainPC= False

OneTime=True

# Set the global variables to collect data once every 15 seconds and update the channel once every 2 minutes
#lastConnectionTime = time.time() # Track the last connection time
lastUpdateTime = time.time() # Track the last update time float epoch 

#connectionInterval = 15   # 120 # Post data once every 2 minutes
updateInterval     =  60  # 15  # get P1 values once every xx seconds

writeAPIkey = "YourThingspeakAPI"  
channelID = "YourThingspeakID"  

/home/luc/Documents/electronique/raspi_image/P1_ReadPortSendThingspeak.py

#url = "https://api.thingspeak.com/channels/"+channelID+"/bulk_update.json" # ThingSpeak server settings
messageBuffer = []

#thingspeakFields = np.array( [2.965589e+03, 3.902753e+03, 1.171337e+03, 5.905980e+02, 6.840000e-01,  0.000000e+00, 6.840000e-01] )
thingspeakFields =  arr.array('d', [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
#thingspeakFields = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])

# Add/update OBIS codes here:
obiscodes = {
    "0-0:1.0.0": "Timestamp",
    "0-0:96.3.10": "Switch electricity",
    "0-1:24.4.0": "Switch gas",
    "0-0:96.1.1": "Meter serial electricity",
    "0-1:96.1.1": "Meter serial gas",
    "0-0:96.14.0": "Current rate (1=day,2=night)",

    "1-0:1.8.1": "* Rate 1 (day) 1.8.1- total consumption",
    "1-0:1.8.2": "* Rate 2 (night) 1.8.2 - total consumption",
    "1-0:2.8.1": "* Rate 1 (day) 2.8.1- total production",
    "1-0:2.8.2": "* Rate 2 (night) 2.8.2- total production",

    "1-0:21.7.0": "L1 consumption",
    "1-0:41.7.0": "L2 consumption",
    "1-0:61.7.0": "L3 consumption",

    "1-0:1.7.0":  "* 1.7.0 ALL PHASES CONSUMPTION",

    "1-0:22.7.0": "L1 production",
    "1-0:42.7.0": "L2 production",
    "1-0:62.7.0": "L3 production",

    "1-0:2.7.0":  "* 2.7.0  All phases production",

    "1-0:32.7.0": "L1 voltage",
    "1-0:52.7.0": "L2 voltage",
    "1-0:72.7.0": "L3 voltage",
    "1-0:31.7.0": "L1 current",
    "1-0:51.7.0": "L2 current",
    "1-0:71.7.0": "L3 current",
    "0-1:24.2.3": "Gas consumption"
    }


def checkcrc(p1telegram):
    # check CRC16 checksum of telegram and return False if not matching
    # split telegram in contents and CRC16 checksum (format:contents!crc)
    for match in re.compile(b'\r\n(?=!)').finditer(p1telegram):
        p1contents = p1telegram[:match.end() + 1]
        # CRC is in hex, so we need to make sure the format is correct
        givencrc = hex(int(p1telegram[match.end() + 1:].decode('ascii').strip(), 16))
    # calculate checksum of the contents
    calccrc = hex(crcmod.predefined.mkPredefinedCrcFun('crc16')(p1contents))
    # check if given and calculated match
    if debug:
        print(f"Given checksum: {givencrc}, Calculated checksum: {calccrc}")
    if givencrc != calccrc:
        if debug:
            print("Checksum incorrect, skipping...")
        return False
    return True


def parsetelegramline(p1line):
    # parse a single line of the telegram and try to get relevant data from it
    unit = ""
    timestamp = "0.0"
    if debug:
        print(f"Parsing:{p1line}")
    # get OBIS code from line (format:OBIS(value)
    obis = p1line.split("(")[0]
    if debug:
        print(f"OBIS:{obis}")
    # check if OBIS code is something we know and parse it
    if obis in obiscodes:
        # get values from line.
        # format:OBIS(value), gas: OBIS(timestamp)(value)
        values = re.findall(r'\(.*?\)', p1line)
        value = values[0][1:-1]
        # timestamp requires removal of last char
        if obis == "0-0:1.0.0" or len(values) > 1:
            value = value[:-1]
            timestamp = value
        # report of connected gas-meter...
        if len(values) > 1:
            value = values[1][1:-1]
        # serial numbers need different parsing: (hex to ascii)
        if "96.1.1" in obis:
            value = bytearray.fromhex(value).decode()
        else:
            # separate value and unit (format:value*unit)
            lvalue = value.split("*")
            value = float(lvalue[0])
            if len(lvalue) > 1:
                unit = lvalue[1]
            #########################################################
            # if thingview values, store to send:
            #thingspeakFields = arr.array('f', [])
            # 0  1.8.1- total consumption (kWh)
            # 1  1.8.2 off-peak consumption (kWh)
            # 2  2.8.1 peak injection (kWh)
            # 3  2.8.2 off-peak injection (kWh)
            # 4  1.7.0 current consumption (W)
            # 5  2.7.0 current injection (W)
            # 6  sum net consumption: (1.7.0) - (2.7.0)  [4]-[5]
            # 7  timestamp  yy mm dd hh mm ss . dd   # 23 12 27 16 05 52 .0
            obis_short = p1line.split("(")[0].split(":")[1]

            if obis_short=="1.8.1":
                thingspeakFields[0]=float(value)
                if debug:
                    print("Record thingsview ", obis_short,value)
            if obis_short =="1.8.2":
                thingspeakFields[1]=float(value)
                if debug:
                    print("Record thingsview ", obis_short,value)
            if obis_short=="2.8.1" :
                thingspeakFields[2]=float(value)
                if debug:
                    print("Record thingsview ", obis_short,value)
            if obis_short =="2.8.2" :
                thingspeakFields[3]=float(value)
                if debug:
                    print("Record thingsview ", obis_short,value)
            if obis_short =="1.7.0" :
                thingspeakFields[4]=float(value)
                if debug:
                    print("Record thingsview ", obis_short,value)
            if obis_short =="2.7.0" :
                thingspeakFields[5]=float(value)
                if debug:
                    print("Record thingsview ", obis_short,value)
            # [6] is computed
            if obis_short=="1.0.0":
                thingspeakFields[7]=float(value)
                if debug:
                    print("Record thingsview ", obis_short,value)
            #########################################################
        # return result in tuple: description,value,unit,timestamp
        if debug:
            print (f"description:{obiscodes[obis]}, \
                     value:{value}, \
                     unit:{unit}")
        return (obiscodes[obis], value, unit)
    else:
        return ()



def SendThingSpeak():
    #while True:
    payload =({'key': writeAPIkey,'field1': thingspeakFields[4],'field2': thingspeakFields[5],'field3': thingspeakFields[6] })
    #print ("--- st 1 :")
    params =urlencode(payload, quote_via=quote_plus)
    #print ("--- st 2 :")
    headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain"}
    #print ("--- st 3 ", headers,  params )         
    conn = http.client.HTTPConnection("api.thingspeak.com:80")
    #print ("--- st 4 :")
    try:
        conn.request("POST", "/update", params, headers)
        #print ("--- st 5 ")
        response = conn.getresponse()
        #print ("--- st 6 ")
        data = response.read()
        global debugThingSpeak
        if debugThingSpeak:
            print (response.status, response.reason)
            print ("response.read()=", data)
        conn.close()
    except:
        print ("connection failed")
    #break

def SendMainPC():
    # send to main as request site , will extract and push to rrd
    #while True:
    global debugSendMainPC
    POSTfile="/rrdpower/P1_get.php"
    payload =({ 'epo': time.time(),
        'f1': thingspeakFields[0],
        'f2': thingspeakFields[1],
        'f3': thingspeakFields[2],
        'f4': thingspeakFields[3],
        'f5': thingspeakFields[4],
        'f6': thingspeakFields[5],
        'f7': thingspeakFields[6],
        'f8': thingspeakFields[7]
        } )
    #print ("--- st 1 :")
    params =urlencode(payload, quote_via=quote_plus)
    #print ("--- st 2 :")
    headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain"}
    #print ("--- st 3 ", headers,  params )         
    conn = http.client.HTTPConnection("192.168.0.11:80")
    #print ("--- st 4 :", conn)
    try:
        conn.request("POST", POSTfile, params, headers)
        #print ("--- st 5 ")
        response = conn.getresponse()
        #print ("--- st 6 ")        #if debugThingSpeak:
        #print (response.status, response.reason)  #200 OK
        data = response.read()
        
        if debugSendMainPC:
           print (headers,params)
           print ("response.read()=", data)
        conn.close()
    except:
        print ("connection failed")
    #break

def main():
    '''
    global debug
    debug=False
    opts, args = getopt.getopt(argv,"hd")
    for opt, arg in opts:
        if opt == '-h':
            print ('test.py [-d] [-h]')
            sys.exit()
        elif opt in ("-d"):
            debug = True
            print ('debug ')
    '''
    print ("------ main ")
    global lastUpdateTime
    global OneTime
    global OneTime
    ser = serial.Serial(serialport, 115200, xonxoff=1)
    p1telegram = bytearray()
    #utility to have obis embedded ?  TBC but need numpy now is arrays
    #thingspeakFields = np.array([  ["a",0.0], ["b",0.0] , ["c",0.0] , ["d",0.0] , ["e",0.0] , ["f",0.0] ])
    while True:
        try:
            
            # read input from serial port
            p1line = ser.readline()
            if debug:
                print ("Reading: ", p1line.strip())
            # P1 telegram starts with /
            # We need to create a new empty telegram
            if "/" in p1line.decode('ascii'):
                if debug:
                    print ("Found beginning of P1 telegram")
                p1telegram = bytearray()
                #print('*' * 60 + "\n")
            # add line to complete telegram
            p1telegram.extend(p1line)
            # P1 telegram ends with ! + CRC16 checksum
            if "!" in p1line.decode('ascii'):
                if debug:
                    print("Found end, printing full telegram")
                    print('*' * 40)
                    print(p1telegram.decode('ascii').strip())
                    print('*' * 40)
                if checkcrc(p1telegram):
                    # parse telegram contents, line by line
                    output = []
                    for line in p1telegram.split(b'\r\n'):
                        r = parsetelegramline(line.decode('ascii'))  # here assign thingspeakFields[x] to obis val
                        if r:
                            output.append(r)
                            if debug:
                                print(f"desc:{r[0]}, val:{r[1]}, u:{r[2]}")                 
                    thingspeakFields[6]=thingspeakFields[4]-thingspeakFields[5]
                    #print to a file for RT visu (TB replaced by push to pc)
                    if (makeTxt):
                        sourceFile = open('P1_data.txt', 'w')
                        # use file created by main pc with SendMainPC (GET_P1.php)  data
                        print(tabulate(output, headers=['Description', 'Value', 'Unit'], tablefmt='github'), file = sourceFile)
                        sourceFile.close()
                    #x = datetime.datetime.now()
                    #print(x.strftime("%Y %m %d %H:%M:%S"))
                    SendMainPC()
                    ti=time.time()
                    #print (time.asctime( time.localtime(ti) ), ti  )
                    #print (" thingspeakFields data : ", thingspeakFields)
                    #print (" next update     (",updateInterval, ")",time.asctime( time.localtime(lastUpdateTime +  updateInterval  )))
                    #print (" next connection (",connectionInterval,")", time.asctime( time.localtime(lastConnectionTime + connectionInterval ) ))
                    #document.getElementById("demo").innerHTML = thingspeakFields;
                    if OneTime==True:
                        print (" next update, interval ",updateInterval,":",time.asctime( time.localtime(lastUpdateTime + updateInterval  )))
                        OneTime=False
                    if (time.time() - lastUpdateTime) >= updateInterval :
                        lastUpdateTime =  time.time()
                        #print (time.time()) 
                        #print (" next call " ,updateInterval + lastUpdateTime)
                        #updatesJson()
                        #print ("Call SendThingSpeak " )
                        SendThingSpeak()
                        print (" next SendThingSpeak update (",updateInterval,")",time.asctime( time.localtime(lastUpdateTime +  updateInterval  )))
                    #else:
                        #print (".")

        except KeyboardInterrupt:
            print("Stopping...")
            ser.close()
            break
        except:
            if debug:
                print(traceback.format_exc())
            # print(traceback.format_exc())
            print ("Something went wrong...")
            ser.close()
        # flush the buffer
        ser.flush()

if __name__ == '__main__':
    #print ("__name__ == '__main__':")
    #print ("time.time()= %f " % time.time())
    #print (time.time())  # float epoch
    #print (time.localtime( time.time() ))
    print (time.asctime( time.localtime(time.time()) ))
    main()
