#!/usr/bin/python
# -*- coding: utf-8 -*-

import os
import sys
import datetime
import signal
import time
import threading
import paho.mqtt.client as mqtt

traceenabled   = False
logenabled     = True

lastpowercount = 0
sensordata     = {}
sensordata_lock = threading.Lock()

thirdfourthrowindex=0

LCDROW1 = "/mnt/1wire/FF.CA0800000100/line20.0"
LCDROW2 = "/mnt/1wire/FF.CA0800000100/line20.1"
LCDROW3 = "/mnt/1wire/FF.CA0800000100/line20.2"
LCDROW4 = "/mnt/1wire/FF.CA0800000100/line20.3"

def TracePrint(text):
  global traceenabled
  if traceenabled == True:
    print(str(datetime.datetime.now()) + " " + text)
    sys.stdout.flush()

def LogPrint(text):
  global logenabled
  if logenabled == True or traceenabled == True:
    print(str(datetime.datetime.now()) + " " + text)
    sys.stdout.flush()

def MQTT_on_subscribe(client, userdata, mid, granted_qos):
    LogPrint("Subscription registered in MQTT broker")

def MQTT_on_connect(client, userdata, rc):
    LogPrint("Connected to MQTT broker with result code "+str(rc))
    (result, mid) = client.subscribe("/#")
    if result != mqtt.MQTT_ERR_SUCCESS:
      LogPrint("Subscribe failed with result code "+str(result))

def MQTT_on_disconnect(client, userdata, rc):
    LogPrint("Disconnected from MQTT broker with result code "+str(rc))

def MQTT_on_message(client, userdata, msg):
    global sensordata
    if not msg.retain:
      TracePrint(msg.topic+" "+str(msg.payload))
      sensordata_lock.acquire(True)
      sensordata[msg.topic] = ( datetime.datetime.now(), msg.payload )
      sensordata_lock.release()

      if msg.topic == "/ELECTRICITY_METER/CURRENT_POWER":
        UpdateFirstRow()
      
      if ( msg.topic == "/MOTIONDETECTION/DRIVEWAY" or msg.topic == "/MOTIONDETECTION/GARAGE_DOOR" ):
        UpdateFirstRow()

      if msg.topic == "/HEAT_METER/EFFECT" or msg.topic == "/GPIO_STATUS" or msg.topic == "/WATER_METER/FLOWSTATUS":
        UpdateSecondRow()


def MQTT_on_log(client, userdata, level, buf):
    TracePrint(" - LOG " + str(level) + " " + str(buf) )

def InitializeMQTT():
  global client

  # Create a MQTT client
  client = mqtt.Client()

  # Setup callbacks for connect, subscribe, disconnect, message and log
  client.on_connect    = MQTT_on_connect
  client.on_subscribe  = MQTT_on_subscribe
  client.on_disconnect = MQTT_on_disconnect
  client.on_message    = MQTT_on_message
  client.on_log        = MQTT_on_log

  LogPrint("Connecting to MQTT broker")

  # Connect to broker
  client.connect("localhost", 1883, 60)

  # Start background thread
  client.loop_start()


def WriteLCDRow(row, string):
  # Warn if too long string is passed. In any case, only print first 20 characters, left adjusted.
  if len(string)>20:
    LogPrint("WriteLCDRow: '%s' is too long (%d characters)." % (string, len(string)) )
  if os.path.isfile(row):
    try:
      f=open(row, 'w')
      f.write("%-20s" % string[0:20])
      f.close()
    except IOError as e:
      LogPrint("WriteLCDRow: I/O error({0}): {1}".format(e.errno, e.strerror))
      LogPrint("%s: '%s'" % (row, string) )

def SleepToNextMinute(offset=0):
  secstonextwholeminute = ( 60 - time.time() % 60 ) + offset
  if secstonextwholeminute < 0:
    secstonextwholeminute = secstonextwholeminute + 60;
  TracePrint("About to sleep " + str(secstonextwholeminute) + " seconds")
  time.sleep(secstonextwholeminute)

def ReadSensorValue(key,timeout=60):
  sensordata_lock.acquire(True)
  if sensordata.has_key(key):
    (timestamp, value) = sensordata[key]
    deltatime = datetime.datetime.now() - timestamp
    if timeout>0 and deltatime.seconds > timeout:
      value=""
  else:
    value=""
  sensordata_lock.release()
  return(value)

def StartThreads():
  pingthread = threading.Thread(target=PingThread)
  pingthread.daemon = True
  pingthread.start()

  thirdandfourthrowthread = threading.Thread(target=ThirdAndFourthRowThread)
  thirdandfourthrowthread.daemon = True
  thirdandfourthrowthread.start()


def PingThread():
  while True:
    TracePrint("Pinging")
    # Ping PRE32/MM30 in living room
    if os.system("ping -c 1 192.168.1.163 > /dev/null 2>&1") == 0:
      str = "S"
    else:
      str = ""

    # Ping Sonos Connect in cinema room
    if os.system("ping -c 1 192.168.1.153 > /dev/null 2>&1") == 0:
      str += "B"

    sensordata_lock.acquire(True)
    sensordata["/PING"] = ( datetime.datetime.now(), str )
    sensordata_lock.release()

    SleepToNextMinute(-10)

def WindChillFactor(temp, wind):
  if (wind<0.5):
    return(temp);
  else:
    return(13.126667 + 0.6215*temp - 13.924748*pow(wind,0.16) + 0.4875195*temp*pow(wind,0.16));


def ThirdAndFourthRowThread():
  while True:
    UpdateThirdAndFourthRow()
    time.sleep(10)

def UpdateFirstRow():
#####################
#El 1140 W GDGU 18:10  <---
#Fv 25.9kW
#In 21.7   41%
#Ut  6.6   83% 1000mb
#####################
  TracePrint("Entering UpdateFirstRow")
  if ReadSensorValue("/MOTIONDETECTION/GARAGE_DOOR", 30):
    md="GD"
  else:
    md="  "
  if ReadSensorValue("/MOTIONDETECTION/DRIVEWAY", 30):
    md=md+"GU"
  else:
    md=md+"  "
  value=ReadSensorValue("/ELECTRICITY_METER/CURRENT_POWER", 120)
  if value:
    WriteLCDRow(LCDROW1, "El%5d W %s %s" % (int(value), md[0:4], time.strftime('%H:%M')))
  else:
    WriteLCDRow(LCDROW1, "El ---- W %s %s" % (md[0:4], time.strftime('%H:%M')))
  TracePrint("Leaving UpdateFirstRow")

def UpdateSecondRow():
#####################
#El 1140W       18:10
#Fv 25.9kW   VHEGPBSW  <---
#In 21.7   41%
#Ut  6.6   83% 1000mb
#####################
  TracePrint("Entering UpdateSecondRow")
  heat=ReadSensorValue("/HEAT_METER/EFFECT", 180)     # Accept two lost readings
  gpio=ReadSensorValue("/GPIO_STATUS")
  ping=ReadSensorValue("/PING")
  wate=ReadSensorValue("/WATER_METER/FLOWSTATUS", 0)  # Need to retain any existing value

  statusstring = gpio + ping
  if wate == "ON":
    statusstring += "W"

  if heat:
    WriteLCDRow(LCDROW2, "Fv %4.1fkW %10s" % (float(heat), statusstring))
  else:
    WriteLCDRow(LCDROW2, "Fv ----kW %10s" % statusstring)
  TracePrint("Leaving UpdateSecondRow")

def UpdateThirdAndFourthRow():
#####################
#El 1140W       18:10
#Fv 25.9kW   VHEGPBSW  
#In 21.7o  41%         <---
#Ut  6.6o  83% 1000mb  <---
#####################

#In 21.7o      41 %RH 
#Ut  6.6o      83 %RH

#In 21.7o   1234  lux
#Ut- 1.7o   1000 mbar

#In 21.7o      SE m/s  IF wind
#Ut- 1.7o 12.1 (12.2)

#In 21.7o   Kylfaktor  IF wind
#Ut- 1.7o   -12 (-20)

#In 21.7o   13.5 mm/h  IF rain
#Ut  6.6o   34.1 mm/d  

#In 21.7o   Mk  12.1o
#Ut 21.7o   Bi  12.1o

#In 21.7o   Ga  12.1o
#Ut 21.7o   Vi  12.1o

#In 21.7o   Fv  12.1o
#Ut 21.7o     121 l/h
  global thirdfourthrowindex

  TracePrint("Entering UpdateThirdAndFourthRow with index=" + str(thirdfourthrowindex))
  intemp=ReadSensorValue("/INDOOR/TEMP")
  outtemp=ReadSensorValue("/OUTDOOR/TEMP")

  if thirdfourthrowindex == 0:
    r0=ReadSensorValue("/HEAT_METER/EFFECT")
    r1=ReadSensorValue("/HEAT_METER/FEED")
    r2=ReadSensorValue("/HEAT_METER/FLOW")
    if r0 and float(r0) == 0:
      thirdfourthrowindex=1
      # At zero effect, skip presenting district heating temp and flow.
    else:
      if r1:
        data12 = "   Fv %5.1f\xDF" % float(r1)
      else:
        data12 = "   Fv  ----\xDF"
      if r2:
        data22 = "    %4.0f l/h" % float(r2)
      else:
        data22 = "     --- l/h"

  if thirdfourthrowindex == 1:
    r1=ReadSensorValue("/WIND_METER/DIRECTION")
    r2=ReadSensorValue("/WIND_METER/WIND")
    r3=ReadSensorValue("/WIND_METER/WIND5MINUTEMAX")
    if r2 and r3 and float(r2)==0 and float(r3)==0:
      thirdfourthrowindex=3
      # At zero wind speed, skip presenting wind and chillfactor
    else:
      if r1:
        data12 = "      %-2s m/s" % r1
      else:
        data12 = "      -- m/s"
      if r2 and r3:
        data22 = " %4.1f (%4.1f)" % (float(r2),float(r3))
      else:
        data22 = " ---- (----)"

  if thirdfourthrowindex == 2:
    r1=ReadSensorValue("/OUTDOOR/TEMP")
    r2=ReadSensorValue("/WIND_METER/WIND")
    r3=ReadSensorValue("/WIND_METER/WIND5MINUTEMAX")
    if r1 and r2 and r3:
      wcfl = int(WindChillFactor(float(r1),float(r2))) 
      wcf5 = int(WindChillFactor(float(r1),float(r3)))
      # Only display WCF if the last or the 5 minute values are less than actual temperature.
      if wcfl < int(float(r1)) or wcf5 < int(float(r1)):
        data12 = "   Kylfaktor"
        data22 = "   %3d (%3d)" % (wcfl,wcf5)
      else:
        thirdfourthrowindex=3
    else:
      thirdfourthrowindex=3

  if thirdfourthrowindex == 3:
    r1=ReadSensorValue("/RAIN_METER/RAIN1HOUR")
    r2=ReadSensorValue("/RAIN_METER/RAIN1DAY")
    if r1 and r2 and float(r1) == 0 and float(r2) == 0:
      thirdfourthrowindex=4
    else:
      if r1:
        data12 = "   %4.1f mm/h" % float(r1)
      else:
        data12 = "   ---- mm/h"
      if r2:
        data22 = "   %4.1f mm/d" % float(r2)
      else:
        data22 = "   ---- mm/d"

  if thirdfourthrowindex == 4:
    r1=ReadSensorValue("/INDOOR/HUMIDITY")
    r2=ReadSensorValue("/OUTDOOR/HUMIDITY")
    if r1:
      data12 = "     %3.0f %%RH" % float(r1)
    else:
      data12 = "     --- %RH"
    if r2:
      data22 = "     %3.0f %%RH" % float(r2)
    else:
      data22 = "     --- %RH"

  if thirdfourthrowindex == 5:
    r1=ReadSensorValue("/INDOOR/PRESSURE")
    r2=ReadSensorValue("/OUTDOOR/LIGHT")
    if r1:
      data12 = "   %4.0f mbar" % float(r1)
    else:
      data12 = "   ---- mbar"
    if r2:
      data22 = "   %4.0f lux " % float(r2)
    else:
      data22 = "   ---- lux "

  if thirdfourthrowindex == 6:
    r1=ReadSensorValue("/BIORUM/TEMP", 900)     # Accept up to 15 minutes late RF temp
    r2=ReadSensorValue("/MATKÄLLARE/TEMP", 900)
    if r1:
      data12 = "   Bi  %4.1f\xDF" % float(r1)
    else:
      data12 = "   Bi  ----\xDF"
    if r2:
      data22 = "   Mk  %4.1f\xDF" % float(r2)
    else:
      data22 = "   Mk  ----\xDF"

  if thirdfourthrowindex == 7:
    r1=ReadSensorValue("/GARAGE/TEMP")
    r2=ReadSensorValue("/ROOF/TEMP")
    if r1:
      data12 = "   Ga  %4.1f\xDF" % float(r1)
    else:
      data12 = "   Ga  ----\xDF"
    if r2:
      data22 = "   Vi %5.1f\xDF" % float(r2)
    else:
      data22 = "   Vi  ----\xDF"

  if intemp:
    WriteLCDRow(LCDROW3, "In %4.1f\xDF%s" % (float(intemp), data12) )
  else:
    WriteLCDRow(LCDROW3, "In ----\xDF%s" % (data12) )

  if outtemp:
    WriteLCDRow(LCDROW4, "Ut%5.1f\xDF%s" % (float(outtemp), data22) )
  else:
    WriteLCDRow(LCDROW4, "Ut ----\xDF%s" % (data22) )

  thirdfourthrowindex = thirdfourthrowindex+1
  if thirdfourthrowindex>7:
    thirdfourthrowindex = 0
  TracePrint("Leaving UpdateThirdAndFourthRow")


# Signal handler
def signal_handler(signal, frame):
   LogPrint("Program terminated by SIGTERM signal.")
   client.loop_stop()
   client.disconnect()
   sys.exit(0)


# Start of main program

# Catch SIGTERM gracefully
signal.signal(signal.SIGTERM, signal_handler)

# Setup MQTT
InitializeMQTT()

# Output first rows
UpdateFirstRow()
UpdateSecondRow()

# Start background threads
StartThreads()

while True:
  try:
    SleepToNextMinute()
    UpdateFirstRow()
#    UpdateSecondRow() # No need to redraw this row on cyclic basis
#    sensordata_lock.acquire(True)
#    for key in sensordata:
#      (timestamp, value) = sensordata[key]
#      print("  " + str(key) + ": " + str(timestamp) + ": " + str(value))
#    sensordata_lock.release()

  except KeyboardInterrupt:
      print("Program terminated by keyboard interrupt.")
      exit(0)
