Advertisement
Grayerbeard

thrm17.py

Feb 20th, 2018
352
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 49.49 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. #b
  4. #   for use with Python 3
  5.  
  6. #   therm17.py.py :  February 10th 2018
  7. #                    and errors where wrong target temp is taken from schedule file
  8.  
  9.  
  10. #   Copyright 2016  <djgtorrens@gmail.com>
  11. #  
  12. #   This program is free software; you can redistribute it and/or modify
  13. #   it under the terms of the GNU General Public License as published by
  14. #   the Free Software Foundation; either version 2 of the License, or
  15. #   (at your option) any later version.
  16. #
  17. #   This program is distributed in the hope that it will be useful,
  18. #   but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20. #   GNU General Public License for more details.
  21. #  
  22. #   You should have received a copy of the GNU General Public License
  23. #   along with this program; if not, write to the Free Software
  24. #   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  25. #   MA 02110-1301, USA.
  26. #
  27. #list
  28. #   read set up from a file
  29. #   create that file from the values in the directory
  30. #   log continuosly until  midnight  or any time based on argument passed from commandline
  31. #   get date and time format output suitable for input to php or excel or anything else
  32. #
  33.  
  34. # socket is for smartplug control
  35. import socket
  36.  
  37. # all other imports for rest of program
  38. import time
  39. import subprocess
  40. import datetime
  41. import csv
  42. # For Python 3
  43. import configparser
  44. # For Python 2.x would be
  45. #import ConfigParser as configparser
  46. #but this prog no good on Python 2
  47. from ftplib import FTP
  48. from os import listdir
  49. from os import path
  50. from os import fsync
  51. import sys, getopt
  52. import webbrowser
  53. from shutil import copyfile
  54. #Use with
  55. #copyfile(src, dst)
  56.  
  57. #   data class "config"  is used to hold configuration information
  58. #   the values set in initializing are the default values
  59. #   If no configuration file is found these values are writen to a file "config.cfg"
  60. # put in same directory as the python file.
  61. # the line
  62. # "  config.prog_path = path.dirname(path.realpath(__file__)) + "/"  "
  63. # using "path" from "os" module.
  64. # is used to get where to look for and put this file.
  65.  
  66. class class_config:
  67.     def __init__(self):
  68.         self.scan_delay = 1     # delay in seconds between each scan (not incl sensor responce times)
  69.         self.max_scans = 3          # number of scans to do, set to zero to scan for ever (until type "ctrl C")
  70.         self.log_directory = "/log/"        # where to send log files
  71.         self.ftp_credentials_filename = 'ftp_credentials.csv'   #
  72.         self.ftp_credentials_log_filename = 'ftp_credentials_log.csv'
  73.         self.ftp_credentials_status_filename = 'ftp_credentials_status.csv'
  74.         self.ftp_credentials_log_html_filename = 'ftp_credentials_log_html.csv'
  75.         self.mount_point = "/rtr/"      # the location to mount the share (must exist) e.g. /home/pi/rtr
  76.         self.test_file = "test.txt"         # a file that should be present at the share if its already mounted e.g.  test.txt
  77.         self.mount_arg1 = "sudo"        # first part of mount command (usually sudo)
  78.         self.mount_arg2 = "/etc/mount_log.sh"       # second part of mount command (usualy name of script to run)
  79.         self.delay_limit = 2        # Number of Seconds delay between temperature scans
  80.         self.delay_increment = 2        # Number of scans to do, Zero for only stopped by Ctrl C on Keyboard
  81.         self.ftplog = 2     # Number of Value Changes before Log File is Saved to remote website
  82.         self.heaterIPa = '192.168.111.100'      # IP for First Heater
  83.         self.heaterIPb = '192.168.111.108'      # IP for Second Heater
  84.         self.sensor4readings = '28-0315a80584ff'  #The code for the sensor to be used to measure room temperature
  85.         self.change4log = 0.6 # change in temperature required before logging and displaying etc
  86.         self.control_hysteresis = 0.15
  87.         # These parameters are not saved to the config.cfg file
  88.         # First three use the program pathname
  89.         self.prog_path = ""
  90.         self.config_filename = ""
  91.         self.sensor_info_filename = ""
  92.         self.logging_filename = ""
  93.         self.html_filename = ""
  94.         self.status_filename = ""
  95.         self.log_html_filename = ""
  96.         self.local_www_html_filename = ""
  97.         self.local_www_log_html_filename = ""
  98.         self.local_www_status_htlm_filename = ""
  99.         self.local_www_log_csv = ""
  100.         self.logging_on = False
  101.         self.sensor_present = False
  102.         self.logging_outfile = ""
  103.         self.scan_count = 0
  104.         self.ftplog_count = 0
  105.         self.ref_sensor_index = 0
  106.         self.heater1_on = 0
  107.         self.heater2_on = 0
  108.         self.one_first = 1
  109.         self.last_target = 0
  110.  
  111.                                 #  SENSORS
  112. class class_sensors:
  113.     def __init__(self):
  114.         self.number = []        # number designation of sensor to use for display
  115.         self.code = []          # the code that the sensor is internally programmed with
  116.         self.connected = []     # true/false flag indicating if his sensor is connected obtained by scanning for its file
  117.         self.reading = []       # the last temperature reading read from the sensor in degrees Centigrade,
  118.                                 # wil be a negative value < -100 if there is an error in reading
  119.         self.last_logged = []   # the value last logged for that sensor
  120.         self.code_seen = []     # a trie/false flag indicating that this senso code has been seen during this run
  121.         self.code_seen_but_disconnected = []    # Flag for when we have seen a sensor then its disconnected
  122.         self.location = []      # text read in from the sensor data file for the sensors location
  123.         self.stype = []         # text read in from the sensor data file for the sensors type
  124.         self.comment = []       # text read in from the sensor data file for a comment
  125.         self.delay = []         # if the sesor is not responding, maybe has become disconnected
  126.         self.error_number = []  # then its file will still be present for a while and this number is
  127.         self.last_logged_error_number = [] # Last logged error
  128.         self.status_text = []   # used to count down before retrying, initially set to the observed delay
  129.                                 # then 0.5 subtracted each scan until value less than 0.5.
  130.                                 # delay is usually around 2 secosnds so it will be about 3 scans before another attempt is made.
  131.  
  132.                    
  133. class class_schedule:
  134.     def __init__(self):
  135.         self.index = []     # Index of the array holding the Temperature Schedule
  136.         self.year = []      # Year
  137.         self.month = []     # Month
  138.         self.day = []       # Day
  139.         self.hour = []      # Hour
  140.         self.minute = []        # Minute
  141.         self.target_temp = []   # Target Temperature
  142.  
  143. class class_smartplug():
  144.     def __init__(self,number_of_plugs):
  145.                             # NOTE Set for 2 plugs,  
  146.                             # must introduce way to set number if need more
  147.         self.state  = [1.234]*number_of_plugs       # state on is "1" off "0"
  148.         self.ip = ["text"]*number_of_plugs          # ip address
  149.         self.current = [1.234]*number_of_plugs      # current
  150.         self.voltage = [1.234]*number_of_plugs      # voltage
  151.         self.power = [1.234]*number_of_plugs        # power now
  152.         self.total = [1.234]*number_of_plugs        # total power (today ?)
  153.         self.error = [1.234]*number_of_plugs        # error code
  154.         self.sent = ["command"]*number_of_plugs     # last command sent
  155.         self.received = ["reply"]*number_of_plugs   # last reply received
  156.  
  157. class textbffr(object):
  158.     # Rotating Buffer Class
  159.     # Initiate with just the size required Parameter
  160.     # Get data with just a position in buffer Parameter
  161.     def __init__(self, size_max):
  162.         #initialization
  163.         self.size_max = size_max
  164.         self._data = [""]*(size_max)
  165.         self.posn = self.size_max-1
  166.  
  167.     def replace(self, value):
  168.         #replace current element
  169.         self._data[self.posn] = value  
  170.  
  171.     def append(self, value):
  172.         #append an element
  173.         if self.posn == self.size_max-1:
  174.             self.posn = 0
  175.             self._data[self.posn] = value  
  176.         else:
  177.             self.posn += 1
  178.             self._data[self.posn] = value
  179.  
  180.     def __getitem__(self, key):
  181.         #return stored element
  182.         if (key + self.posn+1) > self.size_max-1:
  183.             return(self._data[key - (self.size_max-self.posn-1)])
  184.         else:
  185.             return(self._data[key + self.posn+1])
  186.  
  187. # **********************************
  188. # Start of small service functions *
  189. # **********************************
  190.  
  191. def in_GUI_mode():
  192.     mode = 1
  193.     try:
  194.         if sys.stdin.isatty():
  195.             mode = 0
  196.     except AttributeError:  # stdin is NoneType if not in terminal mode
  197.         pass
  198.     if mode == 0:
  199.         #in terminal mode
  200.         return(False)
  201.     else:
  202.         #in gui mode ...
  203.         return(True)
  204.  
  205. def list_files(path,exclude):
  206.     # List all files in path "path" bu exclude items matching "exclude"
  207.     # Result is returned as a list
  208.     emptylist= []
  209.     try:
  210.         files = listdir(path)
  211.         #for ind in range(0,len(files)):
  212.         #   if (files[ind] == exclude) or (files[ind][:2] == "00"):
  213.         #       remove(files[ind])
  214.         while exclude in files:
  215.             files.remove(exclude)
  216.         return(files)
  217.     except:
  218.         return(emptylist)  
  219.  
  220. def pt(where, message):
  221.     global debug
  222.     # routine for use debugging time taken each stage of program
  223.     if debug:
  224.         print("debug(pt) in: " , where , " : ", message, " at : " , str(datetime.datetime.now()), "\n")
  225.     return
  226.  
  227. def pr(where,messabge,var_val):
  228.     global debug
  229.     # routine for debugging that prints message then a variables value
  230.     if debug:
  231.         print("debug(pr) in : ", where , " : ", message, str(var_val))
  232.     return
  233.  
  234. def pr_status(appnd,ref,message):
  235.     global status_bffr
  236.     global last_ref
  237.     # print to screen and to status log and update html file
  238.     print(str(config.scan_count), " : ", message)
  239.     if appnd:
  240.         status_bffr.append(str(config.scan_count) + " : " + make_time_text(datetime.datetime.now()) + " : " + message)
  241.     else :
  242.         if ref == last_ref:
  243.             status_bffr.replace(str(config.scan_count) + " : " + make_time_text(datetime.datetime.now()) + " : " + message)
  244.         else:
  245.             status_bffr.append(str(config.scan_count) + " : " + make_time_text(datetime.datetime.now()) + " : " + message)
  246.     write_html(config.status_filename,status_bffr)
  247.     try:
  248.         copyfile(config.status_filename, config.local_www_status_htlm_filename)
  249.     except:
  250.         pr_log (True,"Fail with send html file to " + config.local_www_status_htlm_filename)
  251.     ftp_result = send_by_ftp(config.ftp_credentials_status_filename, config.status_filename)   
  252.     # uncoment next lines for more info on FTP reults
  253.     # for pres_ind in range(0, len(ftp_result)):
  254.         #print (ftp_result[pres_ind])
  255.     last_ref = ref
  256.     return
  257.  
  258. def pr_log(appnd,message):
  259.     global log_bffr
  260.     # print to screen and to status log and update html file
  261.     print(str(config.scan_count) + " : ",message)
  262.     if appnd :
  263.         log_bffr.append(str(config.scan_count) + " : " + message)
  264.     else:
  265.         log_bffr.replace(str(config.scan_count) + " : " + message)
  266.     write_html(config.log_html_filename,log_bffr)
  267.     try:
  268.         copyfile(config.log_html_filename, config.local_www_log_html_filename)
  269.     except:
  270.         pr_status(True,0, "Fail with send html file to " + config.local_www_log_html_filename)
  271.     ftp_result = send_by_ftp(config.ftp_credentials_log_html_filename, config.log_html_filename)   
  272.     # uncoment next lines for more info on FTP reults
  273.     # for pres_ind in range(0,len(ftp_result)):
  274.         # print (ftp_result[pres_ind]   )
  275.     return
  276.  
  277.  
  278. def fileexists(filename):
  279.     #This checks for file but does not detect disconnected sensor
  280.     try:
  281.             with open(filename): pass
  282.     except IOError:
  283.             return False
  284.     return True
  285.  
  286. def mount_log_drive(mount_point,test_file,mount_arg1,mount_arg2):
  287.    
  288.     # NOT IN USE AT THE MOMENT IN thermoxx.py
  289.    
  290.     #  mount_point  :   the location to mount the share (must exist) e.g. /rtr
  291.     #  test_file    :   a file that should be present at the share if its already mounted e.g.  test.txt
  292.     #  mount_arg1   :   first part of mount command (usually sudo)
  293.     #  mount_arg2   :   second part of mount command (usualy name of script to run such as 'sudo /etc/mount_log.sh')
  294.     #       typical script command "sudo mount -t cifs //192.168.1.1./log /home/pi/rtr -o credentials=/etc/mountcred,sec=ntlm
  295.     #       credentials_file:  a file containing the username and password (located in \etc) for the share e.g.  mountcred
  296.     #               typical credentials file:
  297.     #                   username=fredblogs
  298.     #                   password=reallysecurepassword
  299.  
  300.     #check to see if the network drive for logging is mounted, if not then mount it
  301.     here = "mount_log_drive"
  302.     if (fileexists(mount_point + test_file)):
  303.             pr_status(True,0, "Log Drive already mounted because " + mount_point + test_file + " exists")
  304.             return ("Log Drive already mounted\n")
  305.     else:
  306.             pr_status(True,0, "Will use mount command: " + mount_arg1 +  " " + mount_arg2 + "\n")
  307.             subprocess.call([mount_arg1,mount_arg2]) # e.g. applies 'sudo /etc/mount_log.sh' as two parts of the command
  308.             return("Log Drive now mounted\n")
  309.  
  310. def show_html(html_filename):
  311.     # open a file in the default program
  312.     here = "show_html"
  313.     pr(here, "Show file at this url : ", " file://" + html_filename)
  314.     url = "file://" + html_filename
  315.     webbrowser.open(url,new=2) # new=2 signals new tab
  316.  
  317. def print_bffr(bffr):
  318.     #print to screen contaents of a buffer
  319.     for ind in range(bffr.size_max-1,-1,-1):
  320.         stored = bffr[ind]
  321.         if stored != "":
  322.             print(stored)
  323.     print ( '\n' )
  324.    
  325. def write_html(html_filename,bffr):
  326.     #send contemts of buffer to website
  327.     with open(html_filename,'w') as htmlfile:
  328.         htmlfile.write("<p>" + html_filename + " : " + make_time_text(datetime.datetime.now())  + "</p>")
  329.         for ind in range(bffr.size_max-1, -1,-1):
  330.             htmlfile.write("<p>" + bffr[ind] + "</p>")
  331.    
  332. def make_time_text(time_value):
  333.     #make a time stamp in format mm:dd hr:mn:sc
  334.     return(str(time_value.month).zfill(2) + ":" + str(time_value.day).zfill(2) + "   "
  335.       + str(time_value.hour).zfill(2) + ":" + str(time_value.minute).zfill(2) +":"
  336.       + str(time_value.second).zfill(2))
  337.  
  338.  
  339.  
  340.  
  341. # **********************************
  342. #  End  of small service functions *
  343. # **********************************
  344.  
  345. # ************************************
  346. #  Start smartplug service functions *
  347. # ************************************
  348.  
  349. # Check if IP is valid
  350. def validIP(ip):
  351.     try:
  352.         socket.inet_pton(socket.AF_INET, ip)
  353.     except socket.error:
  354.         parser.error("Invalid IP Address.")
  355.     return ip
  356.  
  357. # Predefined Smart Plug Commands
  358. # For a full list of commands, consult tplink_commands.txt
  359. commands = {'info'     : '{"system":{"get_sysinfo":{}}}',
  360.             'on'       : '{"system":{"set_relay_state":{"state":1}}}',
  361.             'off'      : '{"system":{"set_relay_state":{"state":0}}}',
  362.             'read'     : '{"emeter":{"get_realtime":{}}}'
  363. }
  364.  
  365. # Encryption and Decryption of TP-Link Smart Home Protocol
  366. # XOR Autokey Cipher with starting key = 171
  367. #revied code refer https://github.com/softScheck/tplink-smartplug/issues/20
  368. def encrypt(string):
  369.     key = 171
  370.     result = b"\0\0\0"+ chr(len(string)).encode('latin-1')
  371.     for i in string.encode('latin-1'):
  372.         a = key ^ i
  373.         key = a
  374.         result += chr(a).encode('latin-1')
  375.     return result
  376.  
  377. def decrypt(string):
  378.     key = 171
  379.     result = ""
  380.     for i in string:
  381.         a = key ^ i
  382.         key = i
  383.         result += chr(a)
  384.     return result
  385.    
  386. def get_json(string,value):
  387.     try:
  388.         pos = string.find(":",string.find(value))
  389.         if pos == -1 :
  390.             return -1
  391.         else:  
  392.             end1 = string.find(",",pos)
  393.             end2 = string.find("}",pos)
  394.         try:
  395.             return float(string[pos+1:end1])
  396.         except:
  397.             try:
  398.                 return float(string[pos+1:end2])
  399.             except:
  400.                 return -1
  401.     except: return -99
  402.  
  403. def  send_command(cmded,ip,port) :
  404.     try:
  405.         sock_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  406.         sock_tcp.connect((ip, port))
  407.         sock_tcp.send(encrypt(cmded))
  408.         data = sock_tcp.recv(2048)
  409.         sock_tcp.close()
  410.         return decrypt(data[4:])
  411.     except:
  412.         return ("error")
  413.        
  414. def get_smartplug_status():
  415.     global smartplug_info
  416.  
  417.     #temporary until imlement file to hold smartplug info separatly
  418.     smartplug_info.ip[0] = config.heaterIPa
  419.     smartplug_info.ip[1] = config.heaterIPb
  420.     #smartplug_info.ip[2] = "192.168.222.63"
  421.     #smartplug_info.ip[3] = "192.168.222.64"
  422.  
  423.     for index in range(0,len(smartplug_info.ip),1):
  424.         cmd = commands["read"]
  425.         result = send_command(cmd,smartplug_info.ip[index],9999)
  426.         if result != "error" :
  427.             st_cmd = commands["info"]
  428.             smartplug_info.state[index] = get_json(send_command(st_cmd,smartplug_info.ip[index],9999),"relay_state")
  429.             smartplug_info.current[index] = get_json(result,"current")
  430.             smartplug_info.voltage[index] = get_json(result,"voltage")
  431.             smartplug_info.power[index] = get_json(result,"power")
  432.             smartplug_info.total[index] = get_json(result,"total")
  433.             smartplug_info.error[index] = get_json(result,"err_code")
  434.             smartplug_info.sent[index] = cmd
  435.             smartplug_info.received[index] = result
  436.         else:
  437.             pr_status(True,0, "Error connecting to Smartplug on : " + smartplug_info.ip[index])
  438.  
  439. def turn_on_smartplug(index):
  440.     global config
  441.     cmd = commands["on"]
  442.     send_command(cmd,smartplug_info.ip[index],9999)        
  443.  
  444. def turn_off_smartplug(index):
  445.     global config
  446.     cmd = commands["off"]
  447.     send_command(cmd,smartplug_info.ip[index],9999)
  448.  
  449.        
  450.  
  451. # ************************************
  452. # End of smartplug service functions *
  453. # ************************************
  454.  
  455. def init(margs):
  456.     global config
  457.     global sensors
  458.     global temps_dir
  459.     global exclude_filename
  460.     global debug
  461.     global error
  462.     global run_mode
  463.     global smartplug_info
  464.     global status_bffr
  465.     global log_bffr
  466.    
  467.     here = "init"
  468.     debug = False
  469.     config = class_config() # set all to defaults
  470.     config.last_target = 0
  471.     starttime = datetime.datetime.now()
  472.     timestamp = make_time_text(starttime)
  473.  
  474.     #timestamp = str(starttime.month).zfill(2) + "_" + str(starttime.day).zfill(2) + "_" +  str(starttime.hour).zfill(2)  + "_" +  str(starttime.minute).zfill(2)
  475.    
  476.     #*****************************************************
  477.     # (1) determin the programs location and mode
  478.     #       () 
  479.     config.prog_path = path.dirname(path.realpath(__file__)) + "/"
  480.     config.config_filename = config.prog_path + "config.cfg"
  481.     config.sensor_info_filename = config.prog_path + "sensor_data.csv"
  482.     config.logging_filename =  config.prog_path + config.log_directory + timestamp + "lg.csv"
  483.     config.html_filename = config.prog_path + "index.html"
  484.     config.status_filename = config.prog_path + "status.html"
  485.     config.log_html_filename = config.prog_path + "log.html"
  486.     config.local_www = "/var/www/html/"
  487.     config.local_www_html_filename = config.local_www + "index.html"
  488.     config.local_www_log_html_filename = config.local_www + "log.html"
  489.     config.local_www_status_htlm_filename = config.local_www + "status.html"
  490.     config.local_www_log_csv = config.local_www + "log.csv"
  491.     #config.local_www = config.local_www +
  492.  
  493.     #*****************************************************
  494.     # (2) Load in Configuration from config.cfg
  495.     #       (If no file then save defaults to config.cfg file)
  496.  
  497.     pr(here, "Will look for this config file : ",  config.config_filename)
  498.     #set up configuration
  499.     if fileexists(config.config_filename):
  500.         pr(here,  "Reading config file : ", config.config_filename)
  501.         config_read(config.config_filename) # overwrites from file
  502.     else:
  503.         pr(here,  "File not found so writing config file : ", config.config_filename)
  504.         config_write(config.config_filename,config) # writes defaults to file
  505.     pr(here, "configuration Info : ",config.__dict__)
  506.    
  507.     #*****************************************************
  508.     # (3) Load in Configuration from config.cfg
  509.     #       (If no file then save defaults to config,cfg file)
  510.     try:
  511.         opts, args = getopt.getopt(margs,"hdpsc")
  512.     except getopt.GetoptError:
  513.         pr_status(True,0, "NOT correct ption Usage try -h")
  514.         sys.exit(2)
  515.     for opt, arg in opts:
  516.         if opt == '-d':
  517.             debug = True
  518.             pr(here, "Debug on : ", opt)
  519.         if opt == '-h':
  520.             pr(here, "Set option show help file : ", opt)
  521.             if in_GUI_mode():
  522.                 show_html(config.prog_path + "/"+"help.html")
  523.             else:
  524.                 subprocess.call(['nano', config.prog_path + "/"+"help.html"])
  525.             #show_html(config.prog_path + "/"+"help.html")
  526.             sys.exit()
  527.         if opt == '-p':
  528.             pr ("\"-p\" option on: (does nothing) : ", opt)
  529.         if opt == '-s':
  530.             if not fileexists(config.sensor_info_filename):
  531.                 pr_status(True,0,"Creating config.cfg file with default values")
  532.                 # sensors = class_sensors()  not needed I think
  533.                 new_codes_count = check_what_is_connected()
  534.                 # then if there are any new we have not seen before write to the sensor file
  535.                 if new_codes_count >0 :
  536.                     pr_status(True,0,"Sensor Data filecreated and can now be edited. It has "+ new_codes_count + " entries" )
  537.                     write_sensor_data(new_codes_count,True)
  538.                 else:
  539.                     pr_status(True,0,"No sensors connected Pleae connect at least one")
  540.                     sys.exit()
  541.             pr(here, "Set option show sensor data file file : ", opt)
  542.             if in_GUI_mode():
  543.                 show_html(config.sensor_info_filename)
  544.             else:
  545.                 subprocess.call(['nano', config.sensor_info_filename])
  546.             pr_status(True,0,"Sensor Data file can now be edited.")
  547.             sys.exit()
  548.         if opt == '-c':
  549.             if not fileexists(config.config_filename):
  550.                 pr_status(True,0,"Creating config.cfg file with default values")
  551.                 config_write(config.config_filename,config)
  552.                 #create sensor data file
  553.             pr_status(True,0,"Config file can now be edited")
  554.             pr(here, "Set option show config file : ", opt)
  555.             if in_GUI_mode():
  556.                 show_html(config.config_filename)
  557.             else:
  558.                 subprocess.call(['nano', config.config_filename])
  559.             sys.exit()
  560.  
  561.     #*****************************************************
  562.     # (4) mount remote drive using parameters from config.cfg
  563.     #       (mount_point,test_file,mount_arg1,mount_arg2)
  564.    
  565.     # NOT USED AT THE MOMENT IN thermoxx.py
  566.     #print(mount_log_drive(config.mount_point,config.test_file,config.mount_arg1,config.mount_arg2),"\n")
  567.  
  568.     #*****************************************************
  569.     # (5) set up log file using parameters from config.cfg
  570.     #       (file based is based on current time)
  571.  
  572.     if len(config.log_directory) > 0:
  573.         config.logging_outfile = open(config.logging_filename,'w')
  574.         config.logging_on = True
  575.     else:
  576.         config.logging_on = False
  577.         config.logging_filename = None
  578.         config.logging_outfile = ""
  579.  
  580.     #*****************************************************
  581.     # (6) Make sure the Gpio and thermometer modules are loaded
  582.     #       ()
  583.    
  584.     #Commented out for test
  585.     #subprocess.call(['sudo','modprobe', 'w1-gpio'])
  586.     #subprocess.call(['sudo','modprobe', 'w1-therm'])
  587.    
  588.     #*****************************************************
  589.     # (7) set up empty lists to hold sensor data
  590.     #       (see "class_sensors" for iformation)
  591.     sensors = class_sensors()  
  592.  
  593.  
  594.    
  595.     #*****************************************************
  596.     # (8) set up empty lists to hold smartplug info
  597.     #       (see "class_smartplug" for information)
  598.     #  NOTE: Class is set from here for 2 (two) plugs
  599.     smartplug_info = class_smartplug(2)
  600.    
  601.     #*****************************************************
  602.     # (9) set up error codes
  603.     #       ()
  604.  
  605.     error=["OK","1File only","2New no Data","3Timeout","4CRC er",
  606.         "5Read Err","6Retry Err","7Error","8No Data","9No Dev","10Disconn"]
  607.        
  608.     #*****************************************************
  609.     # (10) set up buffers
  610.     #
  611.     log_bffr = textbffr(100)
  612.     status_bffr = textbffr(200)
  613.  
  614. def write_sensor_data(new_data_count, new_file):
  615.     # add a new record to the sensor file
  616.     global sensors
  617.     global config
  618.     global smartplug_info
  619.    
  620.     here = "write_sensor_data"
  621.     pr(here, "write_sensor_data will write : ", new_data_count)
  622.     fields = ['number','code','location','stype','comment']
  623.     # 'at' mode adds to end of the file and opens file as text
  624.     if new_file:
  625.         mode = 'wt'
  626.     else:
  627.         mode = 'at'
  628.     try:
  629.         with open(config.sensor_info_filename, mode) as sensorcsv_file:
  630.             writer = csv.DictWriter(sensorcsv_file, fieldnames = fields)
  631.             if new_file: # this is a blank file
  632.                 writer.writeheader() # new file needs headings.
  633.             for line_count in  range(len(sensors.code)-new_data_count,len(sensors.code),1):
  634.                 # We need to write to the new line with "number,code,location,stype,comment"
  635.                 writer.writerow({
  636.                 'number': sensors.number[line_count],
  637.                 'code': sensors.code[line_count],
  638.                 'location': sensors.location[line_count],
  639.                 'stype': sensors.stype[line_count],
  640.                 'comment': sensors.comment[line_count]
  641.                 })
  642.     except:
  643.         pr_status(True,0,"Error accessing the existing sensor info file")
  644.         pr_status(True,0,"Close the file if you are editing it!")
  645.         sys.exit()
  646.     return 0
  647.  
  648. def config_read(c_filename):
  649.     here = "config_read"
  650.     config_read = configparser.RawConfigParser()
  651.     config_read.read(c_filename)
  652.     config.scan_delay = float(config_read.getint('SetUp', 'scan_delay'))
  653.     config.max_scans = int(config_read.getint('SetUp', 'max_scans'))
  654.     config.log_directory = config_read.get('SetUp', 'log_directory')
  655.     config.ftp_credentials_filename = config_read.get('SetUp', 'ftp_credentials_filename')
  656.     config.ftp_credentials_log_filename = config_read.get('SetUp', 'ftp_credentials_log_filename')
  657.     config.ftp_credentials_status_filename = config_read.get('SetUp', 'ftp_credentials_status_filename')
  658.     config.ftp_credentials_log_html_filename= config_read.get('SetUp', 'ftp_credentials_log_html_filename')
  659.     config.mount_point = config_read.get('SetUp', 'mount_point')
  660.     config.test_file = config_read.get('SetUp', 'test_file')
  661.     config.mount_arg1 = config_read.get('SetUp', 'mount_arg1')
  662.     config.mount_arg2 = config_read.get('SetUp', 'mount_arg2')
  663.     config.delay_limit = float(config_read.get('SetUp', 'delay_limit'))
  664.     config.delay_increment = float(config_read.get('SetUp', 'delay_increment'))
  665.     config.ftplog = float(config_read.get('SetUp', 'ftplog'))
  666.     config.heaterIPa = config_read.get('SetUp', 'heaterIPa')
  667.     config.heaterIPb = config_read.get('SetUp', 'heaterIPb')
  668.     config.sensor4readings = config_read.get('SetUp', 'sensor4readings')
  669.     config.change4log = config_read.get('SetUp', 'change4log')
  670.     config.control_hysteresis = config_read.get('SetUp', 'control_hysteresis')
  671.     return
  672.  
  673. def config_write(c_filename,default_config):
  674.     here = "config_write"
  675.     config_write = configparser.RawConfigParser()
  676.     config_write.add_section('SetUp')
  677.     config_write.set('SetUp', 'scan_delay',default_config.scan_delay)
  678.     config_write.set('SetUp', 'max_scans',default_config.max_scans)
  679.     config_write.set('SetUp', 'log_directory',default_config.log_directory)
  680.     config_write.set('SetUp', 'ftp_credentials_filename',default_config.ftp_credentials_filename)
  681.     config_write.set('SetUp', 'ftp_credentials_log_filename',default_config.ftp_credentials_log_filename)
  682.     config_write.set('SetUp', 'ftp_credentials_status_filename',default_config.ftp_credentials_status_filename)
  683.     config_write.set('SetUp', 'ftp_credentials_log_html_filename',default_config.ftp_credentials_log_html_filename)
  684.     config_write.set('SetUp', 'mount_point',default_config.mount_point)
  685.     config_write.set('SetUp', 'test_file',default_config.test_file)
  686.     config_write.set('SetUp', 'mount_arg1',default_config.mount_arg1)
  687.     config_write.set('SetUp', 'mount_arg2',default_config.mount_arg2)
  688.     config_write.set('SetUp', 'scan_delay',default_config.scan_delay)
  689.     config_write.set('SetUp', 'delay_limit',default_config.delay_limit)
  690.     config_write.set('SetUp', 'delay_increment',default_config.delay_increment)
  691.     config_write.set('SetUp', 'ftplog',default_config.ftplog)
  692.     config_write.set('SetUp', 'heaterIPa',default_config.heaterIPa)
  693.     config_write.set('SetUp', 'pheaterIPb',default_config.heaterIPb)
  694.     config_write.set('SetUp', 'sensor4readings',default_config.sensor4readings)
  695.     config_write.set('SetUp', 'change4log',default_config.change4log)
  696.     config_write.set('SetUp', 'control_hysteresis',default_config.control_hysteresis)
  697. # Writing our configuration file to 'c_filename'
  698.     pr(here, "ready to writenew : " , c_filename)
  699.     with open(c_filename, 'w+') as configfile:
  700.         config_write.write(configfile)
  701.     return 0
  702.  
  703. def read_in_sensor_data(s_filename):
  704.     #   Set sensor data lists with initial values
  705.     #   read in from file if it exists if not then set up
  706.     #   just defaults for one sensor
  707.     #   later any sensors that are connected will be added
  708.     global sensors
  709.     global smartplug_info
  710.     global config
  711.     here = "read_in_sensor_data"
  712.     pr(here, "dictionary of sensors : ", sensors.__dict__ )
  713.     with open(s_filename, 'r') as csvfile:
  714.         d_file = csv.DictReader(csvfile)
  715.         ind = 0
  716.         for row in d_file:
  717.             sensors.number.append(row['number'])
  718.             sensors.code.append(row['code'])
  719.             sensors.connected.append(False)
  720.             sensors.reading.append(-108)
  721.             sensors.last_logged.append(-108)
  722.             sensors.code_seen.append(False)
  723.             sensors.code_seen_but_disconnected.append(False)
  724.             sensors.location.append(row['location'])
  725.             sensors.stype.append(row['stype'])
  726.             sensors.comment.append(row['comment'])
  727.             sensors.delay.append(0)
  728.             sensors.error_number.append(2)
  729.             sensors.last_logged_error_number.append(2)
  730.             sensors.status_text.append("?")
  731.         ind += 1
  732.     return(True)
  733.  
  734. def read_in_schedule_data(sch_filename):
  735.     #   Read in Schedule data from schedule.csv
  736.     global sensors
  737.     global smartplug_info
  738.     global config
  739.     here = "read_in_schedule_data"
  740.     pr(here, "Schedulke Data read in : ", schedule.__dict__ )
  741.     with open( config.prog_path + "schedule.csv", 'r') as csvfile:
  742.         d_file = csv.DictReader(csvfile)
  743.         ind = 0
  744.         for row in d_file:
  745.             schedule.index.append(row['index'])
  746.             schedule.year.append(row['year'])
  747.             schedule.month.append(row['month'])
  748.             schedule.day.append(row['day'])
  749.             schedule.hour.append(row['hour'])
  750.             schedule.minute.append(row['minute'])
  751.             schedule.target_temp.append(row['target_temp'])
  752.         ind += 1
  753.     return(True)
  754.    
  755. def send_by_ftp(ftp_cred,send_filename):
  756.     here = "send_by_ftp"
  757.     result = ["FTP attempt for :" + send_filename]
  758.     try:
  759.         with open(ftp_cred, 'r') as csvfile:
  760.             cred_file = csv.DictReader(csvfile)
  761.             ind = 0
  762.             for row in cred_file:
  763.                 if ind == 0:
  764.                     ftp_user = row['user']
  765.                     pr(here ,"ftpuser : ",ftp_user)
  766.                     ftp_password = row['password']
  767.                     pr(here,"ftp password : ", ftp_password)
  768.                     file_2_send = str(send_filename)
  769.                     file_as = str(row['file_as'])
  770.                     ftp_directory = str(row['directory'])
  771.                     pr(here, "ftp directory : ",ftp_directory)
  772.                     ftp_site =  str(row['site'])
  773.                     pr(here, "ftp site : ", ftp_site)
  774.                 else:
  775.                     result.append("Error more than one line in FTP Credentials file")
  776.                     return(result)
  777.                 ind += 1
  778.         ftp = FTP()
  779.         pr(here,"Will try to connect to : ", ftp_site)
  780.         ftp.connect(ftp_site, 21)
  781.         pr(here, "logging in here is ftp welcome message : ",ftp.getwelcome())
  782.         ftp.login(user=ftp_user,passwd=ftp_password)
  783.         pr(here, "logged in to : ",ftp_site)
  784.         ftp.cwd(ftp_directory)
  785.         pr(here, "directory changed to : ", ftp_directory)  
  786.    
  787.         sendfile  = open(send_filename,'rb')
  788.         # remove # on next line for debug
  789.         result.append("Will try to send : " + send_filename + " : as : "
  790.                      + file_as + " to : " +  ftp_site + "/" + ftp_directory)
  791.         ftp.storbinary('STOR ' + file_as,sendfile)
  792.         sendfile.close()
  793.        
  794.         ftp.quit()
  795.         pr(here, "ftp quitedfrom : ", ftp_site)
  796.         return(result)
  797.     except:
  798.         result.append("Error Trying To Send " + send_filename + " file by FTP")
  799.         return(result)
  800.    
  801.  
  802. def send_temperature_data_using_ftp(ftp_credentials_file):
  803.     global config # set n init() used to hold FTP credentials
  804.     global sensors # sensor data
  805.     global smartplug_info
  806.     global target_temp
  807.  
  808.     here = "send_temperature_data_using_ftp"
  809.     ftp_text_top = ["\t<!--"]
  810.     ftp_text_top.append("\Temperature Logging and Control")
  811.     ftp_text_top.append("\t-->")
  812.     ftp_text_top.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"")
  813.     ftp_text_top.append("\t\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">")
  814.     ftp_text_top.append("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">")
  815.     ftp_text_top.append("<head>")
  816.     ftp_text_top.append("\t<title>Temperature Logging and Control</title>")
  817.     ftp_text_top.append("\t<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\" />")
  818.     ftp_text_top.append("\t<meta name=\"generator\" content=\"Geany 1.27\" />")
  819.     ftp_text_top.append("\t<meta http-equiv=\"refresh\" content=\"15\" />")
  820.     ftp_text_top.append("</head>")
  821.     ftp_text_top.append("Scan Count: " + str(config.scan_count) + " System Time: "
  822.                         + make_time_text(datetime.datetime.now()) + " Log File: "  + str(config.logging_filename))
  823.     ftp_text_top.append("<body>")
  824.     ftp_text_top.append("<table style=\"background-color: #f4e7e7; width: 350px; height: 150px; border: 1px solid #1b58e4;\" cellpadding=\"5\" align=\"center\"><caption>Temperature Monitoring Degrees C  01</caption>")
  825.     ftp_text_top.append("<tbody>")
  826.     ftp_text_linestart = "<tr align=\"center\" valign=\"middle\"><td>"
  827.     ftp_text_between = "</td><td>"
  828.     ftp_text_line_end = "</td></tr>"
  829.     ftp_text_end = ["</tbody>"]
  830.     ftp_text_end.append("</table>")
  831.     ftp_text_end.append("</body>")
  832.     ftp_text_end.append("</html>")
  833.     with open(config.html_filename,'w') as htmlfile:
  834.         for element in ftp_text_top:
  835.             htmlfile.write(element)
  836.         s_numb = 0
  837.         for element in sensors.number:
  838.             htmlfile.write(ftp_text_linestart + str(element) + ftp_text_between + str(sensors.location[s_numb]) + ftp_text_between + str(sensors.status_text[s_numb]) + ftp_text_line_end)
  839.             s_numb +=1
  840.         htmlfile.write(ftp_text_linestart + "Plug 1 Power and Total" + ftp_text_between + str(smartplug_info.power[0]) + ftp_text_between + str(smartplug_info.total[0]) + ftp_text_line_end)
  841.         htmlfile.write(ftp_text_linestart + "Plug 2 Power and Total"+ ftp_text_between + str(smartplug_info.power[1]) + ftp_text_between + str(smartplug_info.total[1]) + ftp_text_line_end)
  842.         htmlfile.write(ftp_text_linestart + "Scehduled" + ftp_text_between + "Target Temp : " + ftp_text_between + str(target_temp) + ftp_text_line_end)
  843.         for element in ftp_text_end:
  844.             htmlfile.write(element)
  845.    
  846.     ftp_result = send_by_ftp(ftp_credentials_file, config.html_filename)
  847.     # uncomment following two for print of FTP
  848.     # for pres_ind in range(0,len(ftp_result)):
  849.     #   print (ftp_result[pres_ind])
  850.    
  851.     try:
  852.         # send the same html file to the local web site
  853.         copyfile(config.html_filename, config.local_www_html_filename)
  854.         # remove # on next line for debug
  855.         # pr_status(True,0, "Sent : " + config.html_filename + " to : " + config.local_www_html_filename)
  856.     except:
  857.         pr_status(True,0,"Fail with copy " + config.html_filename + " to : " + config.local_www_html_filename)
  858.    
  859.  
  860.  
  861. def check_what_is_connected():
  862.     # check which sensors are connected and update relavant flags
  863.     # build a list of all the new sensors and then add them to the data in use
  864.     global sensors
  865.     global dropped_list
  866.     global smartplug_info
  867.     global config
  868.     here = "check_what_is_connected"
  869.     # Look for what sensors are connected
  870.     temps_dir = '/sys/bus/w1/devices'
  871.     exclude_filename = "w1_bus_master1"
  872.     connected_codes = list_files(temps_dir,exclude_filename)
  873.     if len(connected_codes) > 0:
  874.         config.sensor_present = True
  875.         pr(here, "Sensor present Set True with count equal to : ", str(len(connected_codes)))
  876.     else:
  877.         pr(here, "Sensor present not Set with count equal to : ", str(len(connected_codes)))
  878.         config.sensor_present = False
  879.     new_codes = []
  880.     count_data = 0
  881.     config.ref_sensor_index = -1
  882.     dropped_list = ""
  883.     for element in sensors.code:
  884.         if element in connected_codes:
  885.             # set flag to indicate has been seen during this run of program
  886.             sensors.code_seen[count_data] = True
  887.             if sensors.code[count_data] == config.sensor4readings:
  888.                 config.ref_sensor_index = count_data
  889.             sensors.code_seen_but_disconnected[count_data] = False
  890.             # set flag showing it is connected now or very recently
  891.             # (file persists for a while after sensor removed)
  892.             sensors.connected[count_data] = True
  893.             sensors.reading[count_data] = -101 # should be over writen by temp data
  894.         else:
  895.             sensors.connected[count_data] = False
  896.             if sensors.code_seen[count_data]:
  897.                 sensors.code_seen_but_disconnected[count_data] = True
  898.                 dropped_list = dropped_list + " " + sensors.number[count_data]
  899.         count_data  += 1
  900.     count_connected = 0
  901.     new_codes = []
  902.     for element in connected_codes:
  903.         if not element in sensors.code:
  904.             new_codes.append(element)
  905.         count_connected  += 1
  906.     if len(new_codes) > 0:
  907.         pr_log(True, str(len(new_codes))  + " new sensors found")
  908.         pr(here, " New sensors found ",len(new_codes))
  909.         for ind in range(0, len(new_codes)):
  910.             if len(sensors.number) > 0:
  911.                 sensors.number.append("n" + str(count_connected-len(new_codes)+ind+1))
  912.             else:
  913.                 sensors.number.append("n" + str(1))
  914.             sensors.code.append(new_codes[ind])
  915.             sensors.connected.append(True)
  916.             sensors.reading.append(-102)
  917.             sensors.last_logged.append(-102)
  918.             sensors.code_seen.append(True)
  919.             sensors.code_seen_but_disconnected.append(False)
  920.             sensors.location.append("New Sensor " + str(sensors.number[ind]) + " Location")
  921.             sensors.stype.append("New Sensor " + str(sensors.number[ind]) + " Type")
  922.             sensors.comment.append("New Sensor " + str(sensors.number[ind]) + " Comment")
  923.             sensors.delay.append(0)
  924.             sensors.error_number.append(2)
  925.             sensors.status_text.append("New")
  926.             sensors.last_logged_error_number.append(2)
  927.     else:
  928.         pr(here, "no new codes, still only : ", str(count_connected) + " connected")
  929.     return(len(new_codes))
  930.  
  931. def get_temperature(no):
  932.     global sensors
  933.     global config
  934.     global smartplug_info
  935.  
  936.     here = "get_temperature"   
  937.     file_data = {}
  938.     filename = "/sys/bus/w1/devices/"+sensors.code[no]+"/w1_slave"
  939.     #Routine to read the temperature
  940.     #Read the sensor 3 times checking the CRC until we have a good read
  941.     for retries in range(1, 4):
  942.         start_look = datetime.datetime.now()
  943.         pr(here, "sensor : " + str(no + 1) + " retry : " + str(retries) + " delay_count : ", sensors.delay[no])
  944.         try:
  945.             with open(filename, newline='') as csvfile:
  946.                 temps_reader = csv.reader(csvfile, delimiter=' ', quotechar='|')
  947.                 rownum = 0
  948.                 maxcol = 0
  949.                 try:
  950.                     for row in temps_reader:
  951.                         if rownum == 0:
  952.                             elapsed = datetime.datetime.now()- start_look
  953.                         if (elapsed.total_seconds())>2:
  954.                             sensors.delay[no] = elapsed.total_seconds() # remember the delay
  955.                             pr(here, "Sensor Delay : " + str(sensors.number[no]) + " with delay of : " + str(sensors.delay[no]) + " delay count set to : ",sensors.delay[no])
  956.                             sensors.reading[no] = -103
  957.                             sensors.error_number[no] = 3
  958.                             return # delay too big
  959.                         colnum =0
  960.                         for col in row:
  961.                             file_data[rownum,colnum] = col
  962.                             if colnum > maxcol:
  963.                                 maxcol = colnum
  964.                             colnum += 1
  965.                         rownum += 1
  966.                 except:
  967.                     sensors.reading[no] = -109
  968.                     sensors.error_number = 9
  969.                     return
  970.                 try:   
  971.                     pr(here, "max col found was : ", maxcol)
  972.                     pr(here, "file_data[0,11](CRC check) was : ", file_data[0,11])
  973.                     pr(here, "temp_str was : ", file_data[1,9])
  974.                 except:
  975.                     pr_status(True,0, here + "not getting data from file read time was : " +  elapsed.total_seconds())
  976.                 try:
  977.                     if (maxcol == 11) and (file_data[0,11] == "YES"): # file and CRC reading OK
  978.                         temp_str = file_data[1,9] # get temperature data in format "t=NN.NNN"
  979.                         temp_int = float(temp_str[2:]) # extract the number
  980.                         temp_val = temp_int / 1000 # move decimal point
  981.                         sensors.reading[no] = float(temp_val)
  982.                         sensors.error_number[no] = 0
  983.                         return # convert to float and return
  984.                     else:
  985.                         sensors.reading[no] = -104
  986.                         sensors.error_number[no] = 4
  987.                         return # return read error
  988.                 except:
  989.                     sensors.reading[no] = -105
  990.                     sensors.error_number[no] = 5
  991.                     return # signals that read file but CRC bad
  992.         except:
  993.             #comes her if file goes between the "check_what_is_connected()" scan and now
  994.             sensors.reading[no] = -110
  995.             sensors.error_number[no] = 10
  996.             return # file gone
  997.     if retries > 3:
  998.         sensors.reading[no] = -106
  999.         sensors.error_number[no] = 6
  1000.         return
  1001.     sensors.reading[no] = -107
  1002.     sensors.error_number[no] = 7
  1003.     return # return read error
  1004.  
  1005. def log_temperature_data_to_file():
  1006.     global config
  1007.     global sensors
  1008.     global smartplug_info
  1009.     here =  "log_temperature_data_to_file"
  1010.     #write the time at the start of the line in logging file
  1011.     logtime = datetime.datetime.now()
  1012.     config.logging_outfile.write(str(logtime.day).zfill(2) + "/" + str(logtime.month).zfill(2) +
  1013.         "/" + str(logtime.year).zfill(2) + " " + str(logtime.hour).zfill(2) + ":" +
  1014.         str(logtime.minute).zfill(2) + ":" + str(logtime.second).zfill(2))
  1015.     if (config.sensor_present == False):
  1016.         config.logging_outfile.write(" : no sensors with Trg Temp of : " + str(target_temp) + "\n")
  1017.     else:
  1018.         config.logging_outfile.write(",TrgTemp: ," + str(target_temp) + ",")
  1019.         for z in range(0,len(sensors.code),1):
  1020.             #record the data last saved for this sensor
  1021.             #send data to the file only if the sensor is connected
  1022.             if sensors.code_seen[z]:
  1023.                 config.logging_outfile.write(" , " + str(sensors.number[z]) + " , " + str(sensors.status_text[z]))
  1024.                 sensors.last_logged[z] = sensors.reading[z]
  1025.                 sensors.last_logged_error_number[z] = sensors.error_number[z]
  1026.        
  1027.         get_smartplug_status()
  1028.                
  1029.         config.logging_outfile.write("," + str(smartplug_info.state[0]))
  1030.         config.logging_outfile.write("," + str(smartplug_info.current[0]))
  1031.         config.logging_outfile.write("," + str(smartplug_info.voltage[0]))
  1032.         config.logging_outfile.write(" , " + str(smartplug_info.power[0]))
  1033.         config.logging_outfile.write("," + str(smartplug_info.total[0]))
  1034.         config.logging_outfile.write("," + str(smartplug_info.error[0]))
  1035.         config.logging_outfile.write("," + str(smartplug_info.state[1]))
  1036.         config.logging_outfile.write("," + str(smartplug_info.current[1]))
  1037.         config.logging_outfile.write("," + str(smartplug_info.voltage[1]))
  1038.         config.logging_outfile.write("," + str(smartplug_info.power[1]))
  1039.         config.logging_outfile.write("," + str(smartplug_info.total[1]))
  1040.         config.logging_outfile.write("," + str(smartplug_info.error[1]))
  1041.         config.logging_outfile.write("\n")
  1042.         config.logging_outfile.flush()
  1043.  
  1044.     return
  1045.    
  1046. def set_status_text():
  1047.     # set the status text based on the results of the last scan
  1048.     error_count = 0
  1049.     for z in range(0,len(sensors.code),1):
  1050.         if sensors.error_number[z] == 0 :
  1051.             sensors.status_text[z] = ("{0:.4}".format(sensors.reading[z]))
  1052.         else:
  1053.             error_count +=1
  1054.             if sensors.delay[z] >= config.delay_limit:
  1055.                 sensors.status_text[z] = ("Wait" + str(int(sensors.delay[z])))
  1056.             else:
  1057.                 sensors.status_text[z] = (error[sensors.error_number[z]])
  1058.    
  1059. def make_printout_for_screen(datachange):
  1060.     global config
  1061.     global sensors
  1062.     global smartplug_info
  1063.     global target_temp
  1064.     here = "make_printout_for_screen"
  1065.     error_count = 0
  1066.     #set printout for start of the line
  1067.     printout = str(config.scan_count) + " of " + str(config.max_scans) + " " + datetime.datetime.now().strftime("%d:%m:%Y %H:%M:%S") + " Target: " +  str(target_temp)
  1068.     for z in range(0,len(sensors.code),1):
  1069.         if sensors.connected[z]:
  1070.             printout += "[" + str(sensors.number[z]) + "=" + sensors.status_text[z] + "]"
  1071.         else:
  1072.             if sensors.code_seen[z]:
  1073.                 printout += "[" + str(sensors.number[z]) + "= disconn ]"
  1074.     printout += "  "
  1075.     #if not datachange:
  1076.     #   printout += "[no changes]"
  1077.     if max(sensors.delay) >= config.delay_limit:
  1078.         printout +="[max delay count:" + "{0:.4}".format(max(sensors.delay)) +"]"
  1079.     if len(dropped_list) > 0:
  1080.         printout += "[disconnected:" + dropped_list + "]"
  1081.     if error_count >0 :
  1082.         printout += "[error count=" + str(error_count) +"]"
  1083.     if not max(sensors.connected):
  1084.         printout += "[no sensors]"
  1085.     return(printout)
  1086.    
  1087. def get_target_temp(year,month,day,hour,minute):
  1088.     global schedule
  1089.     global config
  1090.        
  1091. # From Scedule get Target Temperature
  1092.             # Search for Target Temp
  1093.     target_temp = -100
  1094.     ind_result = 1
  1095.    
  1096.     for ind in range(1,len(schedule.year)):
  1097.         if int(year) == int(schedule.year[ind]):
  1098.             if int(month) == int(schedule.month[ind]):
  1099.                 if int(day) == int(schedule.day[ind]):
  1100.                     if int(hour) == int(schedule.hour[ind]):
  1101.                         if int(minute) >= int(schedule.minute[ind]):
  1102.                             if int(minute) <= int(schedule.minute[ind+1]):
  1103.                                 ind_result = ind
  1104.                                 break
  1105.                             else:
  1106.                                 ind_result = ind   
  1107.     if config.last_target != schedule.target_temp[ind_result+1]:
  1108.         pr_status(True,0, " config.last_target : " + str(config.last_target) + "   New Target : " +
  1109.                 str(schedule.target_temp[ind_result+1]))       
  1110.     config.last_target = schedule.target_temp[ind_result+1]
  1111.  
  1112.    
  1113.     if ind_result == 1:
  1114.         pr_status(True,0,"Error or very new schedule >> ind result : " + str(ind_result) + "Target set to 17.654321")
  1115.         return (17.654321)
  1116.     else:
  1117.         if ind_result +1 > len(schedule.year):
  1118.             pr_status(True,0,"Error>> ind result : " + ind_result +  "Target set to 16.54321")
  1119.             return (16.54321)
  1120.         else:
  1121.             # remove # in next 4 lines for debug
  1122.             #pr_status(True,0,"Schedule Look Up Result>> index used: " + str(ind_result+1).zfill(4) + "  Date : "
  1123.             # + str(schedule.year[ind_result+1]).zfill(4) + "/" + str(schedule.month[ind_result+1]).zfill(2) + "/"
  1124.             # + str(schedule.day[ind_result+1]).zfill(2) +" Time: " + str(schedule.hour[ind_result+1]).zfill(2) +":"
  1125.             # + str(schedule.minute[ind_result+1]).zfill(2) +" Target : " + str(schedule.target_temp[ind_result+1]))
  1126.             return (float(schedule.target_temp[ind_result+1]))
  1127.                
  1128.    
  1129. def main(argv):
  1130.     global config
  1131.     global starttime
  1132.     global sensors
  1133.     global schedule
  1134.     global debug
  1135.     global target_temp
  1136.     global status_bffr
  1137.     global log_bffr
  1138.     global last_ref
  1139.    
  1140.     last_ref = -1
  1141.    
  1142.     here = "main"
  1143.     #Set things up and read in
  1144.     init(argv) # 6 tasks setting up variables etc
  1145.     # if there is a file with sensor data read it in
  1146.     if fileexists(config.sensor_info_filename):
  1147.         read_in_sensor_data(config.sensor_info_filename)
  1148.    
  1149.     # if there is a schedule file read it in
  1150.     pr_status(True,0,"Loading Schedule File Data from :  " + config.prog_path + "schedule.csv")
  1151.     if fileexists(config.prog_path + "schedule.csv"):
  1152.         schedule = class_schedule()
  1153.         read_in_schedule_data(config.prog_path + "schedule.csv")   
  1154.     else:  
  1155.         quit ( "No Schedule")
  1156.        
  1157.     if config.logging_on:
  1158.         pr(here, "Starting with Logging to : " + config.logging_filename + " at ", datetime.datetime.now())
  1159.     else:
  1160.         pr(here, "Starting with Logging Off at : ", + datetime.datetime.now())
  1161.     if config.max_scans > 0:
  1162.         pr(here, "Starting Temp Sensor Scans for : " + str(config.max_scans) + " scans starting at : ", datetime.datetime.now())
  1163.     else:
  1164.         pr(here, "Starting Continuous Temp Sensor Scans for at : ", datetime.datetime.now())
  1165.     pr(here, "With an interval of : ", config.scan_delay)
  1166.    
  1167.    
  1168.  
  1169.     #Main Loop
  1170.     change_flag = False
  1171.     config.scan_count = 1
  1172.     config.sensor_present = False
  1173.     # Scan for max_scan times or for ever if config.max_scans = 0
  1174.     while (config.scan_count <= config.max_scans) or (config.max_scans == 0):
  1175.         # check_for_new codes and find out what sensors are cobnnected
  1176.         # geting new codes if there are any
  1177.         new_codes_count = check_what_is_connected()
  1178.        
  1179.         # then if there are any new we have not seen before write to the sensor file
  1180.         if new_codes_count >0 :
  1181.             write_sensor_data(new_codes_count,len(sensors.code) == new_codes_count)
  1182.  
  1183.         # now get data from all the sensor that are connected
  1184.         for z in range(0,len(sensors.code),1):
  1185.             if sensors.connected[z]:
  1186.                 # if there has been a timeout value will be about 8 initially
  1187.                 if (sensors.delay[z] < config.delay_limit):
  1188.                     get_temperature(z)
  1189.                     pr(here,"sensor : " + str(sensors.number[z]) + " returned :", sensors.reading[z])
  1190.                 else:
  1191.                     if sensors.delay[z] >= config.delay_limit:
  1192.                         sensors.delay[z] -= config.delay_increment # so after few scans will try again
  1193.             else:
  1194.                 sensors.delay[z] = 0
  1195.             # check if this sensor has changed more than 0.25 degrees, if so set flag
  1196.             # that will trigger print out and logging
  1197.             if (abs(sensors.last_logged[z] - sensors.reading[z])) > float(config.change4log):
  1198.                 change_flag = True
  1199.                 if config.scan_count > 1 :
  1200.                     # Following two Lines helps watching changes but is not needed
  1201.                     # pr_log(True,"Change detected in sensor : " + str(sensors.number[z]) + "was : " +
  1202.                     # str(sensors.last_logged[z]) + " now : " + str(sensors.reading[z]))
  1203.                     sensors.last_logged[z] = sensors.reading[z]
  1204.             if sensors.last_logged_error_number[z] != sensors.error_number[z]:
  1205.                 change_flag = True
  1206.  
  1207. # ****************************
  1208. #  Get target temperature
  1209. #****************************  
  1210.         time_now = datetime.datetime.now()
  1211.         target_temp = get_target_temp(time_now.year,time_now.month,time_now.day,time_now.hour,time_now.minute)
  1212.  
  1213. # ****************************
  1214. # Start of smartplug Control *
  1215. # ****************************
  1216.  
  1217.         # before operating smartplugs get their status     
  1218.         #print ("get sps in log temp data before operate them")
  1219.         # NOT NEEDED    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  1220.         #get_smartplug_status
  1221.        
  1222.         #  Check if reading indictates if heaters should be turned on of off       
  1223.        
  1224.         if (len(sensors.reading) > 0) and (config.ref_sensor_index != -1 ):
  1225.             if sensors.reading[config.ref_sensor_index] > target_temp + float(config.control_hysteresis) :
  1226.                 turn_off_smartplug(0)
  1227.                 turn_off_smartplug(1)
  1228.             else:
  1229.                 if sensors.reading[config.ref_sensor_index] < target_temp - float(config.control_hysteresis) :
  1230.                     turn_on_smartplug(0)
  1231.                     turn_on_smartplug(1)
  1232.         else:
  1233.             pr_status(True,0, "Cannot control no sensors or reference sensor not defined")         
  1234.        
  1235.         # now having operated smartplugs get their status
  1236.         # print ("get sps in log temp data after operate them")
  1237.         # NOT NEEDED    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  1238.         # get_smartplug_status     
  1239.                
  1240. # ****************************
  1241. #  End  of smartplug Control *
  1242. # ****************************
  1243.  
  1244.         #for test only to get more print outs and logging
  1245.         #change_flag = True
  1246.  
  1247.        
  1248.         if change_flag or (config.scan_count == config.max_scans) or (config.scan_count == 0):
  1249.             # now if data changed do logging printing and sending
  1250.             set_status_text() # figure out the status text for the temperature sensors
  1251.             if config.logging_on:
  1252.                 log_temperature_data_to_file()
  1253.             if config.ftplog_count > config.ftplog :
  1254.            
  1255.                 ftp_result = send_by_ftp(config.ftp_credentials_log_filename, config.logging_filename)
  1256.                
  1257.                 #uncomment following two lines to get info on FTP attempts
  1258.                 # for pres_ind in range(0,len(ftp_result)):
  1259.                     #print (ftp_result[pres_ind])
  1260.                
  1261.                 try:
  1262.                     copyfile(config.logging_filename, config.local_www_log_csv)
  1263.                     # Uncoment following line for info on copying file to web site dir
  1264.                     # print ( "Sent : "+ config.logging_filename + " to : " + config.local_www_log_csv)
  1265.                 except:
  1266.                     pr_status(True,0, "Fail with send log csv to " + config.local_www_log_csv)
  1267.            
  1268.                 config.ftplog_count = 0
  1269.            
  1270.             else:
  1271.                 config.ftplog_count = config.ftplog_count +1
  1272.  
  1273. # *******************************************************************************
  1274. #  After logging done, have all info send html file to websites done every scan *
  1275. # *******************************************************************************
  1276.         send_temperature_data_using_ftp(config.ftp_credentials_filename)
  1277.  
  1278.         if (len(sensors.reading) > 0):
  1279.             #must be some sensor before we can print data
  1280.             if debug:
  1281.                 #printouts if in debug
  1282.                 pr_log(True, "\n" + make_printout_for_screen(change_flag) + "\n")
  1283.             else:
  1284.                 if change_flag:
  1285.                     #printouts if not in debug and data changed
  1286.                     pr_log(True,make_printout_for_screen(change_flag))
  1287.                 else:
  1288.                     #printouts if not in debug and no data changed
  1289.                     pr_log(False,make_printout_for_screen(change_flag))
  1290.         else:
  1291.             pr_status( True,0,0, "No sensors found yet")
  1292.             pr_log(False,"No Sensors found yet")
  1293.            
  1294. #reset change flag and operate delay before next scan
  1295.         change_flag = False
  1296.         last_ended  = make_time_text(datetime.datetime.now())  
  1297.         time.sleep(config.scan_delay)
  1298.         config.scan_count += 1
  1299.         # following line can be used
  1300.         pr_status(False,1,"Scan " + str(config.scan_count-1) + " ended at  " + last_ended + "  Now Start Scan : " + str(config.scan_count))
  1301.         pr(here, " ******** Scan Counting to " + str(config.max_scans) + "  now at scan " , config.scan_count)
  1302.     return 0
  1303.  
  1304. if __name__ == '__main__':
  1305.     main(sys.argv[1:])
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement