Growing stuff and doing things

dstroy0

Zeroes and Ones
Doser GUI beginnings

takes a json from the doser, displays pertinent information, has direct access to doser commands and config, so not a web facing service, VNC only.

1603908799190.png

Python:
import serial
import json
from datetime import datetime
from systemd.daemon import notify, Notification
import tkinter as tk

#constants
BAUD_RATE = 500000
RAW_INPUT_STRING_SLICE_START = 12
INITIAL_DOSER_STRING = "pH 0.00 EC 0.00 temp 0.00"

#UART port
ser = serial.Serial('/dev/ttyACM2', BAUD_RATE, timeout=5) #open the serial port       
    
#figure out if the object is a valid json
def is_json(myjson):
    try:
        #leverage json.loads to tell us what's wrong with the json, if anything
        json_object = json.loads(myjson)
    except ValueError as e:     
        #useful error report
        #print('ERROR is_json() :',e)
        print(e)
        return False
    return True
        
#only pass valid json to this func
def flatten_json(nested_json):
    """
        Flatten json object with nested keys into a single level.
        Args:
            nested_json: A nested json object.
        Returns:
            The flattened json object if successful, None otherwise.
    """
    out = {}
    
    def flatten(x, name=''):
        if type(x) is dict:
            for a in x:
                flatten(x[a], name + a + '_')
        elif type(x) is list:
            i = 0
            for a in x:
                flatten(a, name + str(i) + '_')
                i += 1
        else:
            out[name[:-1]] = x

    flatten(nested_json)
    return out
    
def main():
    # Send READY=1
    notify(Notification.READY)
    
    
    #button_setup()
    root = tk.Tk()
    root.title("Doser Environmentals")
    label = tk.Label(root, fg="dark green")
    label.pack()
    label.config(text=str(INITIAL_DOSER_STRING))
    button = tk.Button(root, text='Stop', width=25, command=root.destroy)
    button.pack()
    var = 1
    keys = []
    values = []
    doser_string = INITIAL_DOSER_STRING
    while var == 1:       
        root.update()
        line = ser.readline()
        #remove byte type and carriage return
        try:
            line = line.decode('ascii')
            line = line.rstrip()
            
            #wrap object
            line = '[' + line + ']'
            
            #print(line)
        except UnicodeDecodeError as e:
            print('ERROR:',e)
        except TypeError as e:
            print('ERROR:',e)
        
        #uncomment to view raw object
        #print(line) #print 'line'
        json_dict = {}       
        #test if json in 'line' is valid json       
        if is_json(line) == True:
            try:
                #print(line)
                line = json.loads(line)
                
                flat_json = flatten_json(line)
                print(flat_json)
                #get key:value pairs
                
                for key, value in flat_json.items():       
                    #slice the first 12 char off each column name (the controller version)
                    json_dict[str(key[RAW_INPUT_STRING_SLICE_START:])] = value
                    keys.append(str(key[RAW_INPUT_STRING_SLICE_START:]))
                    values.append(str(value))
                #print(keys)
                #print(values)
                if "DOS_ENV_RES_PH" and "DOS_ENV_RES_EC" in json_dict:
                    #print(json_dict["DOS_ENV_RES_PH"])
                    #print(json_dict["DOS_ENV_RES_EC"])
                    doser_string = "pH {0} EC {1} temp {2}".format(json_dict["DOS_ENV_RES_PH"],json_dict["DOS_ENV_RES_EC"],json_dict["DOS_ENV_RES_TEMP"])
                    label.config(text=str(doser_string))
                keys.clear()
                values.clear()
            except ValueError as e:
                print('ERROR try: commit_json(line):',e)
        
    

main()
 

dstroy0

Zeroes and Ones
Doser code:

Python:
from time import sleep
from datetime import datetime,timedelta
import serial
import json
from datetime import datetime
from systemd.daemon import notify, Notification
import tkinter as tk
from tkinter import *

#constants
BAUD_RATE = 500000
RAW_INPUT_STRING_SLICE_START = 12
INITIAL_DOSER_STRING = "pH 0.00 EC 0.00 temp 0.00\n avg pH 0.00 avg EC 0.00 avg temp 0.00"
INITIAL_CONTROLLER_REPLY = "Sensor Response: "
SENSOR_LIST = ["COM_SENSOR_0_COMMAND","COM_SENSOR_1_COMMAND","COM_SENSOR_2_COMMAND","COM_SENSOR_3_COMMAND","COM_SENSOR_0_DATA","COM_SENSOR_1_DATA","COM_SENSOR_2_DATA","COM_SENSOR_3_DATA"]

#table list
table_list = [("pH","EC","temp"),
              ("","",""),
              ("avg pH","avg EC","avg temp"),
              ("","",""),
              ("Sensor:","Command:","Response:"),
              ("","",""),
              ("","",""),
              ("","",""),
              ("","",""),
              ("","",""),
              ("","",""),
              ("","",""),
              ("","","")]

print_dict_flag = 0


#UART port
ser = serial.Serial('/dev/ttyACM2', BAUD_RATE, timeout=5) #open the serial port

class Table:
      
    def __init__(self,root):
          
        # code for creating table
        for i in range(total_rows):
            for j in range(total_columns):                   
                self.e = Entry(root, width=20, fg='black',bg='white',relief="sunken",font=('Arial',16,'bold'))
                self.e.grid(row=i, column=j)
                grid_label = tk.Label(root, fg="black", bg="white", relief = "sunken", borderwidth = 2,font=('Arial',16,'bold'))
                grid_label.grid(row=i, column = j, sticky=N+S+E+W)                                       
                grid_label.config(text=str(table_list[i][j]))
                
class CreateToolTip(object):
    """
    create a tooltip for a given widget
    """
    def __init__(self, widget, text='widget info'):
        self.waittime = 500     #miliseconds
        self.wraplength = 180   #pixels
        self.widget = widget
        self.text = text
        self.widget.bind("<Enter>", self.enter)
        self.widget.bind("<Leave>", self.leave)
        self.widget.bind("<ButtonPress>", self.leave)
        self.id = None
        self.tw = None

    def enter(self, event=None):
        self.schedule()

    def leave(self, event=None):
        self.unschedule()
        self.hidetip()

    def schedule(self):
        self.unschedule()
        self.id = self.widget.after(self.waittime, self.showtip)

    def unschedule(self):
        id = self.id
        self.id = None
        if id:
            self.widget.after_cancel(id)

    def showtip(self, event=None):
        x = y = 0
        x, y, cx, cy = self.widget.bbox("insert")
        x += self.widget.winfo_rootx() + 25
        y += self.widget.winfo_rooty() + self.widget.winfo_height() + 1
        # creates a toplevel window
        self.tw = tk.Toplevel(self.widget)
        # Leaves only the label and removes the app window
        self.tw.wm_overrideredirect(True)
        self.tw.wm_geometry("+%d+%d" % (x, y))
        label = tk.Label(self.tw, text=self.text, justify='left',
                       background="#ffffff", relief='solid', borderwidth=1,
                       wraplength = self.wraplength)
        label.pack(ipadx=1)

    def hidetip(self):
        tw = self.tw
        self.tw= None
        if tw:
            tw.destroy()

    
#figure out if the object is a valid json
def is_json(myjson):
    try:
        #leverage json.loads to tell us what's wrong with the json, if anything
        json_object = json.loads(myjson)
    except ValueError as e:     
        #useful error report
        #print('ERROR is_json() :',e)
        print(e)
        return False
    return True
        
#only pass valid json to this func
def flatten_json(nested_json):
    """
        Flatten json object with nested keys into a single level.
        Args:
            nested_json: A nested json object.
        Returns:
            The flattened json object if successful, None otherwise.
    """
    out = {}
    
    def flatten(x, name=''):
        if type(x) is dict:
            for a in x:
                flatten(x[a], name + a + '_')
        elif type(x) is list:
            i = 0
            for a in x:
                flatten(a, name + str(i) + '_')
                i += 1
        else:
            out[name[:-1]] = x

    flatten(nested_json)
    return out

def dose_enable():
    command_to_write = str('dose_enable\r\n')
    for i in command_to_write:
        ser.write(i.encode('utf8'))

def calibrate_ph():
    #call write to serial sensor with calibration commands
    print("calibrating pH")
    
def print_dict():
    global print_dict_flag
    if print_dict_flag == 1:
        print_dict_flag = 0
        print('disable print json dict to shell')
    else:
        print_dict_flag = 1
        print('enable print json dict to shell')
        
#pass multiuart port and command string
def write_to_serial_sensor(port,command):
    #assemble command into string literal
    combined_command = str(port) + " " + str(command)
    command_to_write = 'atc {0} {1}\r\n'.format(str(port),str(command))
    #iterate through string literal, write to connected serial port in utf8 encoding
    for i in command_to_write:
        ser.write(i.encode('utf8'))
        
def update_grid_labels(json_dict):
    try:       
        if "COM_SENSOR_0_COMMAND" in json_dict:
            ts = str(datetime.now().strftime("%m/%d/%Y, %H:%M:%S"))
            com = str(json_dict["COM_SENSOR_0_COMMAND"])
            update = str(ts + "\n" + com)
            grid_ph_command_label.config(text=update)
        if "COM_SENSOR_0_DATA" in json_dict:
            ts = str(datetime.now().strftime("%m/%d/%Y, %H:%M:%S"))
            com = str(json_dict["COM_SENSOR_0_DATA"])
            update = str(ts + "\n" + com)
            grid_ph_data_label.config(text=update)
            if 'pH' in json_dict["COM_SENSOR_0_DATA"]:
                #print("got ph info")
                ts = str(datetime.now().strftime("%m/%d/%Y, %H:%M:%S"))
                string_start = json_dict["COM_SENSOR_0_DATA"].index("?")
                com = str(json_dict["COM_SENSOR_0_DATA"][string_start:])
                update = str(ts + "\n" + com)
                grid_ph_sensor_info_response_label.config(text=update)
        if "COM_SENSOR_1_COMMAND" in json_dict:
            ts = str(datetime.now().strftime("%m/%d/%Y, %H:%M:%S"))
            com = str(json_dict["COM_SENSOR_1_COMMAND"])
            update = str(ts + "\n" + com)
            grid_ec_command_label.config(text=update)               
        if "COM_SENSOR_1_DATA" in json_dict:
            ts = str(datetime.now().strftime("%m/%d/%Y, %H:%M:%S"))
            com = str(json_dict["COM_SENSOR_1_DATA"])
            update = str(ts + "\n" + com)
            grid_ec_data_label.config(text=update)
            if 'EC' in json_dict["COM_SENSOR_1_DATA"]:
                #print("got ec info")
                ts = str(datetime.now().strftime("%m/%d/%Y, %H:%M:%S"))
                string_start = json_dict["COM_SENSOR_1_DATA"].index("?")
                com = str(json_dict["COM_SENSOR_1_DATA"][string_start:])
                update = str(ts + "\n" + com)
                grid_ec_sensor_info_response_label.config(text=update)
        if "COM_SENSOR_2_COMMAND" in json_dict:
            ts = str(datetime.now().strftime("%m/%d/%Y, %H:%M:%S"))
            com = str(json_dict["COM_SENSOR_2_COMMAND"])
            update = str(ts + "\n" + com)
            grid_temp_command_label.config(text=update)               
        if "COM_SENSOR_2_DATA" in json_dict:
            ts = str(datetime.now().strftime("%m/%d/%Y, %H:%M:%S"))
            com = str(json_dict["COM_SENSOR_2_DATA"])
            update = str(ts + "\n" + com)
            grid_temp_data_label.config(text=update)
            if 'RTD' in json_dict['COM_SENSOR_2_DATA']:
                #print("got temp info")
                ts = str(datetime.now().strftime("%m/%d/%Y, %H:%M:%S"))
                string_start = json_dict["COM_SENSOR_2_DATA"].index("?")
                com = str(json_dict["COM_SENSOR_2_DATA"][string_start:])
                update = str(ts + "\n" + com)
                grid_temp_sensor_info_response_label.config(text=update)
        if "COM_SENSOR_3_COMMAND" in json_dict:
            ts = str(datetime.now().strftime("%m/%d/%Y, %H:%M:%S"))
            com = str(json_dict["COM_SENSOR_3_COMMAND"])
            update = str(ts + "\n" + com)
            grid_flow_command_label.config(text=update)               
        if "COM_SENSOR_3_DATA" in json_dict:
            ts = str(datetime.now().strftime("%m/%d/%Y, %H:%M:%S"))
            com = str(json_dict["COM_SENSOR_3_DATA"])
            update = str(ts + "\n" + com)
            grid_flow_data_label.config(text=update)
            if 'FLO' in json_dict['COM_SENSOR_3_DATA']:
                #print("got flow info")
                ts = str(datetime.now().strftime("%m/%d/%Y, %H:%M:%S"))
                string_start = json_dict["COM_SENSOR_3_DATA"].index("?")
                com = str(json_dict["COM_SENSOR_3_DATA"][string_start:])
                
                update = str(ts + "\n" + com)
                grid_flow_sensor_info_response_label.config(text=update)
                    
        if "DOS_ENV_RES_PH" and "DOS_ENV_RES_EC" in json_dict:                   
            grid_ph_label.config(text=str(json_dict["DOS_ENV_RES_PH"]))
            grid_ec_label.config(text=str(json_dict["DOS_ENV_RES_EC"]))
            grid_temp_label.config(text=str(json_dict["DOS_ENV_RES_TEMP"]))                   
                    
            grid_ph_avg_label.config(text=str(json_dict["DOS_ENV_RES_AVG_PH"]))
            grid_ec_avg_label.config(text=str(json_dict["DOS_ENV_RES_AVG_EC"]))
            grid_temp_avg_label.config(text=str(json_dict["DOS_ENV_RES_AVG_TEMP"]))
                        
    except KeyError as e:
        print('ERROR keyerror',e)
    
def main():
    global print_dict_flag,root
    # Send READY=1
    notify(Notification.READY)
          
    var = 1
    get_sensor_info_timer = datetime.now()
    get_sensor_info_timer = get_sensor_info_timer + timedelta(seconds=5)
    get_sensor_info = 1
    doser_string = INITIAL_DOSER_STRING
    
    while var == 1:
        #get sensor info one time after boot
        curr_time = datetime.now()
        
        if get_sensor_info == 1 and curr_time >= get_sensor_info_timer:
            write_to_serial_sensor(0,"i")
            sleep(0.05)
            write_to_serial_sensor(1,"i")
            sleep(0.05)
            write_to_serial_sensor(2,"i")
            sleep(0.05)
            write_to_serial_sensor(3,"i")
            get_sensor_info = 0
            
        root.update()
        
        line = ser.readline()
        
        try:
            #remove byte type and carriage return
            line = line.decode('ascii')
            line = line.rstrip()           
            #wrap object
            line = '[' + line + ']'           
            #print(line)
        except UnicodeDecodeError as e:
            print('ERROR:',e)
        except TypeError as e:
            print('ERROR:',e)
        
        #uncomment to view raw object
        #print(line) #print 'line'
        json_dict = {}       
        #test if json in 'line' is valid json       
        if is_json(line) == True:
            try:
                line = json.loads(line)               
                flat_json = flatten_json(line)

                #get key:value pairs               
                for key, value in flat_json.items():       
                    #slice the first 12 char off each column name (the controller version)
                    json_dict[str(key[RAW_INPUT_STRING_SLICE_START:])] = value
                
                if print_dict_flag == 1:
                    print(json_dict)
                result_list = []
                for i in SENSOR_LIST:
                    if i in json_dict:                       
                        #print(i,json_dict[i])                       
                        result_list.append(json_dict[i])
                    
                    if '*OK' in str(result_list):                       
                        #print(result_list)
                        #print(i)               
                        #print("affirmative")
                        result_list = []
                                                                                                                  
                    update_grid_labels(json_dict)
                  
            except ValueError as e:
                print('ERROR',e)


main()
 

dstroy0

Zeroes and Ones
Tkinter setup, goes right before main() :

Python:
#tkinter setup

#button_setup()
root = tk.Tk()
root.title("Doser Environmentals")
# find total number of rows and
# columns in list
total_rows = len(table_list)
total_columns = len(table_list[0])
t = Table(root)
grid_ph_sensor_label = tk.Label(root, fg="black", bg="white", relief = "sunken", borderwidth = 2,font=('Arial',12,'bold'))
grid_ph_sensor_label.grid(row=5, column = 0, rowspan=1, sticky=N+S+E+W)
grid_ph_sensor_label.config(text=str("0-pH"))

grid_ph_sensor_info_response_label = tk.Label(root, fg="black", bg="white", relief = "sunken", borderwidth = 2,font=('Arial',12))
grid_ph_sensor_info_response_label.grid(row=6, column = 0, rowspan=1, sticky=N+S+E+W)
grid_ph_sensor_info_response_label.config(text=str(" "))
grid_ph_sensor_info_response_label_ttp = CreateToolTip(grid_ph_sensor_info_response_label, \
"This field contains sensor type and sensor firmware version")

grid_ec_sensor_label = tk.Label(root, fg="black", bg="white", relief = "sunken", borderwidth = 2,font=('Arial',12,'bold'))
grid_ec_sensor_label.grid(row=7, column = 0, rowspan=1, sticky=N+S+E+W)
grid_ec_sensor_label.config(text=str("1-EC"))

grid_ec_sensor_info_response_label = tk.Label(root, fg="black", bg="white", relief = "sunken", borderwidth = 2,font=('Arial',12))
grid_ec_sensor_info_response_label.grid(row=8, column = 0, rowspan=1, sticky=N+S+E+W)
grid_ec_sensor_info_response_label.config(text=str(" "))
grid_ec_sensor_info_response_label_ttp = CreateToolTip(grid_ec_sensor_info_response_label, \
"This field contains sensor type and sensor firmware version")   

grid_temp_sensor_label = tk.Label(root, fg="black", bg="white", relief = "sunken", borderwidth = 2,font=('Arial',12,'bold'))
grid_temp_sensor_label.grid(row=9, column = 0, rowspan=1, sticky=N+S+E+W)
grid_temp_sensor_label.config(text=str("2-temp"))    

grid_temp_sensor_info_response_label = tk.Label(root, fg="black", bg="white", relief = "sunken", borderwidth = 2,font=('Arial',12))
grid_temp_sensor_info_response_label.grid(row=10, column = 0, rowspan=1, sticky=N+S+E+W)
grid_temp_sensor_info_response_label.config(text=str(" "))
grid_temp_sensor_info_response_label_ttp = CreateToolTip(grid_temp_sensor_info_response_label, \
"This field contains sensor type and sensor firmware version")      

grid_flow_sensor_label = tk.Label(root, fg="black", bg="white", relief = "sunken", borderwidth = 2,font=('Arial',12,'bold'))
grid_flow_sensor_label.grid(row=11, column = 0, rowspan=1, sticky=N+S+E+W)
grid_flow_sensor_label.config(text=str("3-flow"))   

grid_flow_sensor_info_response_label = tk.Label(root, fg="black", bg="white", relief = "sunken", borderwidth = 2,font=('Arial',12))
grid_flow_sensor_info_response_label.grid(row=12, column = 0, rowspan=1, sticky=N+S+E+W)
grid_flow_sensor_info_response_label.config(text=str(" "))
grid_flow_sensor_info_response_label_ttp = CreateToolTip(grid_flow_sensor_info_response_label, \
"This field contains sensor type and sensor firmware version")   

grid_ph_label = tk.Label(root, fg="black", bg="white")
grid_ph_label.grid(row=1, column = 0)
grid_ph_label.config(text=str(0.00))
grid_ec_label = tk.Label(root, fg="black", bg="white")
grid_ec_label.grid(row=1, column = 1)
grid_ec_label.config(text=str(0.00))
grid_temp_label = tk.Label(root, fg="black", bg="white")
grid_temp_label.grid(row=1, column = 2)
grid_temp_label.config(text=str(0.00))   

grid_ph_avg_label = tk.Label(root, fg="black", bg="white")
grid_ph_avg_label.grid(row=3, column = 0)
grid_ph_avg_label.config(text=str(0.00))

grid_ec_avg_label = tk.Label(root, fg="black", bg="white")
grid_ec_avg_label.grid(row=3, column = 1)
grid_ec_avg_label.config(text=str(0.00))

grid_temp_avg_label = tk.Label(root, fg="black", bg="white")
grid_temp_avg_label.grid(row=3, column = 2)
grid_temp_avg_label.config(text=str(0.00))

grid_ph_command_label = tk.Label(root, fg="black", bg="white", relief = "sunken", borderwidth = 2)
grid_ph_command_label.grid(row=5, column = 1, rowspan=2, sticky=N+S+E+W)
grid_ph_command_label.config(text=str("boot"))
grid_ph_command_label_ttp = CreateToolTip(grid_ph_command_label, \
"This was the last command sent, if it says boot here then no commands have been sent ")

grid_ph_data_label = tk.Label(root, fg="black", bg="white", relief = "sunken", borderwidth = 2)
grid_ph_data_label.grid(row=5, column = 2, rowspan=2, sticky=N+S+E+W)
grid_ph_data_label.config(text=str(" "))
grid_ph_data_label_ttp = CreateToolTip(grid_ph_data_label, \
"This was the last response received from the sensor, if this is blank there has been no response received ")

grid_ec_command_label = tk.Label(root, fg="black", bg="white", relief = "sunken", borderwidth = 2)
grid_ec_command_label.grid(row=7, column = 1, rowspan=2, sticky=N+S+E+W)
grid_ec_command_label.config(text=str("boot"))
grid_ec_command_label_ttp = CreateToolTip(grid_ec_command_label, \
"This was the last command sent, if it says boot here then no commands have been sent ")
grid_ec_data_label = tk.Label(root, fg="black", bg="white", relief = "sunken", borderwidth = 2)
grid_ec_data_label.grid(row=7, column = 2, rowspan=2, sticky=N+S+E+W)
grid_ec_data_label.config(text=str(" "))
grid_ec_data_label_ttp = CreateToolTip(grid_ec_data_label, \
"This was the last response received from the sensor, if this is blank there has been no response received ")

grid_temp_command_label = tk.Label(root, fg="black", bg="white", relief = "sunken", borderwidth = 2)
grid_temp_command_label.grid(row=9, column = 1, rowspan=2, sticky=N+S+E+W)
grid_temp_command_label.config(text=str("boot"))
grid_temp_command_label_ttp = CreateToolTip(grid_temp_command_label, \
"This was the last command sent, if it says boot here then no commands have been sent ")

grid_temp_data_label = tk.Label(root, fg="black", bg="white", relief = "sunken", borderwidth = 2)
grid_temp_data_label.grid(row=9, column = 2, rowspan=2, sticky=N+S+E+W)
grid_temp_data_label.config(text=str(" "))
grid_temp_data_label_ttp = CreateToolTip(grid_temp_data_label, \
"This was the last response received from the sensor, if this is blank there has been no response received ")
    
grid_flow_command_label = tk.Label(root, fg="black", bg="white", relief = "sunken", borderwidth = 2)
grid_flow_command_label.grid(row=11, column = 1, rowspan=2, sticky=N+S+E+W)
grid_flow_command_label.config(text=str("boot"))
grid_flow_command_label_ttp = CreateToolTip(grid_flow_command_label, \
"This was the last command sent, if it says boot here then no commands have been sent ")

grid_flow_data_label = tk.Label(root, fg="black", bg="white", relief = "sunken", borderwidth = 2)
grid_flow_data_label.grid(row=11, column = 2, rowspan=2, sticky=N+S+E+W)
grid_flow_data_label.config(text=str(" "))
grid_flow_data_label_ttp = CreateToolTip(grid_flow_data_label, \
"This was the last response received from the sensor, if this is blank there has been no response received ")

    
print_dict_button = tk.Button(root, text='Print dict', width=25, command=print_dict)
print_dict_button.grid(column=1)    

dose_enable_button = tk.Button(root, text='Auto-dose enable/disable', width=25, command=dose_enable)
dose_enable_button.grid(column=1)

        
cal_button = tk.Button(root, text='Calibrate pH', width=25, command=calibrate_ph)
cal_button.grid(column=1)

stop_button = tk.Button(root, text='Stop', width=25, command=root.destroy)
stop_button.grid(column=1)
 
Top Bottom