Ejemplo n.º 1
0
def run_main():
    logger.info("... Starting Raspberry Pi Power Monitor")
    logger.info("Press Ctrl-c to quit...")
    # The following empty dictionaries will hold the respective calculated values at the end of each polling cycle, which are then averaged prior to storing the value to the DB.
    solar_power_values = dict(power=[], pf=[], current=[])
    home_load_values = dict(power=[], pf=[], current=[])
    net_power_values = dict(power=[], current=[])
    ct0_dict = dict(power=[], pf=[], current=[])
    ct1_dict = dict(power=[], pf=[], current=[])
    ct2_dict = dict(power=[], pf=[], current=[])
    ct3_dict = dict(power=[], pf=[], current=[])
    ct4_dict = dict(power=[], pf=[], current=[])
    ct5_dict = dict(power=[], pf=[], current=[])
    rms_voltages = []
    i = 0  # Counter for aggregate function

    while True:
        try:
            board_voltage = get_board_voltage()
            samples = collect_data(2000)
            poll_time = samples['time']
            ct0_samples = samples['ct0']
            ct1_samples = samples['ct1']
            ct2_samples = samples['ct2']
            ct3_samples = samples['ct3']
            ct4_samples = samples['ct4']
            ct5_samples = samples['ct5']
            v_samples = samples['voltage']
            rebuilt_waves = rebuild_waves(samples, ct0_phasecal, ct1_phasecal,
                                          ct2_phasecal, ct3_phasecal,
                                          ct4_phasecal, ct5_phasecal)
            results = calculate_power(rebuilt_waves, board_voltage)

            # # RMS calculation for phase correction only - this is not needed after everything is tuned. The following code is used to compare the RMS power to the calculated real power.
            # # Ideally, you want the RMS power to equal the real power when you are measuring a purely resistive load.
            # rms_power_0 = round(results['ct0']['current'] * results['ct0']['voltage'], 2)  # AKA apparent power
            # rms_power_1 = round(results['ct1']['current'] * results['ct1']['voltage'], 2)  # AKA apparent power
            # rms_power_2 = round(results['ct2']['current'] * results['ct2']['voltage'], 2)  # AKA apparent power
            # rms_power_3 = round(results['ct3']['current'] * results['ct3']['voltage'], 2)  # AKA apparent power
            # rms_power_4 = round(results['ct4']['current'] * results['ct4']['voltage'], 2)  # AKA apparent power
            # rms_power_5 = round(results['ct5']['current'] * results['ct5']['voltage'], 2)  # AKA apparent power

            # Prepare values for database storage
            grid_0_power = results['ct0']['power']  # CT0 Real Power
            grid_1_power = results['ct1']['power']  # CT1 Real Power
            grid_2_power = results['ct2']['power']  # CT2 Real Power
            grid_3_power = results['ct3']['power']  # CT3 Real Power
            grid_4_power = results['ct4']['power']  # CT4 Real Power
            grid_5_power = results['ct5']['power']  # CT5 Real Power

            grid_0_current = results['ct0']['current']  # CT0 Current
            grid_1_current = results['ct1']['current']  # CT1 Current
            grid_2_current = results['ct2']['current']  # CT2 Current
            grid_3_current = results['ct3']['current']  # CT3 Current
            grid_4_current = results['ct4']['current']  # CT4 Current
            grid_5_current = results['ct5']['current']  # CT5 Current

            # If you are monitoring solar/generator inputs to your panel, specify which CT number(s) you are using, and uncomment the commented lines.
            solar_power = 0
            solar_current = 0
            solar_pf = 0
            # solar_power = results['ct3']['power']
            # solar_current = results['ct3']['current']
            # solar_pf = results['ct3']['pf']
            voltage = results['voltage']

            # Set solar power and current to zero if the solar power is under 20W.
            if solar_power < 20:
                solar_power = 0
                solar_current = 0
                solar_pf = 0

            # Determine if the system is net producing or net consuming right now by looking at the two panel mains.
            # Since the current measured is always positive, we need to add a negative sign to the amperage value if we're exporting power.
            if grid_0_power < 0:
                grid_0_current = grid_0_current * -1
            if grid_1_power < 0:
                grid_1_current = grid_1_current * -1
            if solar_power > 0:
                solar_current = solar_current * -1

            # Unless your specific panel setup matches mine exactly, the following four lines will likely need to be re-written:
            home_consumption_power = grid_0_power + grid_1_power + grid_2_power + grid_3_power + grid_4_power + grid_5_power + solar_power
            net_power = home_consumption_power - solar_power
            home_consumption_current = grid_0_current + grid_1_current + grid_2_current + grid_3_current + grid_4_current + grid_5_current - solar_current
            net_current = grid_0_current + grid_1_current + grid_2_current + grid_3_current + grid_4_current + grid_5_current + solar_current

            if net_power < 0:
                current_status = "Producing"
            else:
                current_status = "Consuming"

            # Average 2 readings before sending to db
            if i < 2:
                solar_power_values['power'].append(solar_power)
                solar_power_values['current'].append(solar_current)
                solar_power_values['pf'].append(solar_pf)

                home_load_values['power'].append(home_consumption_power)
                home_load_values['current'].append(home_consumption_current)
                net_power_values['power'].append(net_power)
                net_power_values['current'].append(net_current)

                ct0_dict['power'].append(results['ct0']['power'])
                ct0_dict['current'].append(results['ct0']['current'])
                ct0_dict['pf'].append(results['ct0']['pf'])
                ct1_dict['power'].append(results['ct1']['power'])
                ct1_dict['current'].append(results['ct1']['current'])
                ct1_dict['pf'].append(results['ct1']['pf'])
                ct2_dict['power'].append(results['ct2']['power'])
                ct2_dict['current'].append(results['ct2']['current'])
                ct2_dict['pf'].append(results['ct2']['pf'])
                ct3_dict['power'].append(results['ct3']['power'])
                ct3_dict['current'].append(results['ct3']['current'])
                ct3_dict['pf'].append(results['ct3']['pf'])
                ct4_dict['power'].append(results['ct4']['power'])
                ct4_dict['current'].append(results['ct4']['current'])
                ct4_dict['pf'].append(results['ct4']['pf'])
                ct5_dict['power'].append(results['ct5']['power'])
                ct5_dict['current'].append(results['ct5']['current'])
                ct5_dict['pf'].append(results['ct5']['pf'])
                rms_voltages.append(voltage)
                i += 1

            else:  # Calculate the average, send the result to InfluxDB, and reset the dictionaries for the next 2 sets of data.
                infl.write_to_influx(
                    solar_power_values,
                    home_load_values,
                    net_power_values,
                    ct0_dict,
                    ct1_dict,
                    ct2_dict,
                    ct3_dict,
                    ct4_dict,
                    ct5_dict,
                    poll_time,
                    i,
                    rms_voltages,
                )
                solar_power_values = dict(power=[], pf=[], current=[])
                home_load_values = dict(power=[], pf=[], current=[])
                net_power_values = dict(power=[], current=[])
                ct0_dict = dict(power=[], pf=[], current=[])
                ct1_dict = dict(power=[], pf=[], current=[])
                ct2_dict = dict(power=[], pf=[], current=[])
                ct3_dict = dict(power=[], pf=[], current=[])
                ct4_dict = dict(power=[], pf=[], current=[])
                ct5_dict = dict(power=[], pf=[], current=[])
                rms_voltages = []
                i = 0

                if logger.handlers[0].level == 10:
                    t = PrettyTable(
                        ['', 'CT0', 'CT1', 'CT2', 'CT3', 'CT4', 'CT5'])
                    t.add_row([
                        'Watts',
                        round(results['ct0']['power'], 3),
                        round(results['ct1']['power'], 3),
                        round(results['ct2']['power'], 3),
                        round(results['ct3']['power'], 3),
                        round(results['ct4']['power'], 3),
                        round(results['ct5']['power'], 3)
                    ])
                    t.add_row([
                        'Current',
                        round(results['ct0']['current'], 3),
                        round(results['ct1']['current'], 3),
                        round(results['ct2']['current'], 3),
                        round(results['ct3']['current'], 3),
                        round(results['ct4']['current'], 3),
                        round(results['ct5']['current'], 3)
                    ])
                    t.add_row([
                        'P.F.',
                        round(results['ct0']['pf'], 3),
                        round(results['ct1']['pf'], 3),
                        round(results['ct2']['pf'], 3),
                        round(results['ct3']['pf'], 3),
                        round(results['ct4']['pf'], 3),
                        round(results['ct5']['pf'], 3)
                    ])
                    t.add_row([
                        'Voltage',
                        round(results['voltage'], 3), '', '', '', '', ''
                    ])
                    s = t.get_string()
                    logger.debug('\n' + s)

            #sleep(0.1)

        except KeyboardInterrupt:
            infl.close_db()
            sys.exit()
def run_main():
    logger.info("Press Ctrl-c to quit...")
    # The following empty dictionaries will hold the respective calculated values at the end of each polling cycle, which are then averaged prior to storing the value to the DB.
    solar_power_values = dict(power=[], pf=[], current=[])
    home_load_values = dict(power=[], pf=[], current=[])
    net_power_values = dict(power=[], current=[])
    ct0_dict = dict(power=[], pf=[], current=[])
    ct1_dict = dict(power=[], pf=[], current=[])
    ct2_dict = dict(power=[], pf=[], current=[])
    ct3_dict = dict(power=[], pf=[], current=[])
    ct4_dict = dict(power=[], pf=[], current=[])
    ct5_dict = dict(power=[], pf=[], current=[])
    rms_voltage_values = []
    i = 0  # Counter for aggregate function

    while True:
        try:
            board_voltage = get_board_voltage()
            samples = collect_data(2000)
            poll_time = samples['time']
            ct0_samples = samples['ct0']
            ct1_samples = samples['ct1']
            ct2_samples = samples['ct2']
            ct3_samples = samples['ct3']
            ct4_samples = samples['ct4']
            ct5_samples = samples['ct5']
            v_samples = samples['voltage']
            rebuilt_waves = rebuild_waves(samples, ct0_phasecal, ct1_phasecal,
                                          ct2_phasecal, ct3_phasecal,
                                          ct4_phasecal, ct5_phasecal)
            results = calculate_power(rebuilt_waves, board_voltage)

            # # RMS calculation for phase correction only - this is not needed after everything is tuned. The following code is used to compare the RMS power to the calculated real power.
            # # Ideally, you want the RMS power to equal the real power when you are measuring a purely resistive load.
            # rms_power_0 = round(results['ct0']['current'] * results['ct0']['voltage'], 2)  # AKA apparent power
            # rms_power_1 = round(results['ct1']['current'] * results['ct1']['voltage'], 2)  # AKA apparent power
            # rms_power_2 = round(results['ct2']['current'] * results['ct2']['voltage'], 2)  # AKA apparent power
            # rms_power_3 = round(results['ct3']['current'] * results['ct3']['voltage'], 2)  # AKA apparent power
            # rms_power_4 = round(results['ct4']['current'] * results['ct4']['voltage'], 2)  # AKA apparent power
            # rms_power_5 = round(results['ct5']['current'] * results['ct5']['voltage'], 2)  # AKA apparent power
            # phase_corrected_power_0 = results['ct0']['power']
            # phase_corrected_power_1 = results['ct1']['power']
            # phase_corrected_power_2 = results['ct2']['power']
            # phase_corrected_power_3 = results['ct3']['power']
            # phase_corrected_power_4 = results['ct4']['power']
            # phase_corrected_power_5 = results['ct5']['power']

            # # diff is the difference between the real_power (phase corrected) compared to the simple rms power calculation.
            # # This is used to calibrate for the "unknown" phase error in each CT.  The phasecal value for each CT input should be adjusted so that diff comes as close to zero as possible.
            # diff_0 = phase_corrected_power_0 - rms_power_0
            # diff_1 = phase_corrected_power_1 - rms_power_1
            # diff_2 = phase_corrected_power_2 - rms_power_2
            # diff_3 = phase_corrected_power_3 - rms_power_3
            # diff_4 = phase_corrected_power_4 - rms_power_4
            # diff_5 = phase_corrected_power_5 - rms_power_5

            # Phase Corrected Results
            # logger.debug("\n")
            # logger.debug(f"CT0 Real Power: {round(results['ct0']['power'], 2):>10} W | Amps: {round(results['ct0']['current'], 2):<7} | RMS Power: {round(results['ct0']['current'] * results['ct0']['voltage'], 2):<6} W | PF: {round(results['ct0']['pf'], 5)}")
            # logger.debug(f"CT1 Real Power: {round(results['ct1']['power'], 2):>10} W | Amps: {round(results['ct1']['current'], 2):<7} | RMS Power: {round(results['ct1']['current'] * results['ct1']['voltage'], 2):<6} W | PF: {round(results['ct1']['pf'], 5)}")
            # logger.debug(f"CT2 Real Power: {round(results['ct2']['power'], 2):>10} W | Amps: {round(results['ct2']['current'], 2):<7} | RMS Power: {round(results['ct2']['current'] * results['ct2']['voltage'], 2):<6} W | PF: {round(results['ct2']['pf'], 5)}")
            # logger.debug(f"CT3 Real Power: {round(results['ct3']['power'], 2):>10} W | Amps: {round(results['ct3']['current'], 2):<7} | RMS Power: {round(results['ct3']['current'] * results['ct3']['voltage'], 2):<6} W | PF: {round(results['ct3']['pf'], 5)}")
            # logger.debug(f"CT4 Real Power: {round(results['ct4']['power'], 2):>10} W | Amps: {round(results['ct4']['current'], 2):<7} | RMS Power: {round(results['ct4']['current'] * results['ct4']['voltage'], 2):<6} W | PF: {round(results['ct4']['pf'], 5)}")
            # logger.debug(f"CT5 Real Power: {round(results['ct5']['power'], 2):>10} W | Amps: {round(results['ct5']['current'], 2):<7} | RMS Power: {round(results['ct5']['current'] * results['ct5']['voltage'], 2):<6} W | PF: {round(results['ct5']['pf'], 5)}")
            # logger.debug(f"Line Voltage: {round(results['voltage'], 2)} V")

            # Prepare values for database storage
            grid_0_power = results['ct0']['power']  # 200A Main (left)
            grid_1_power = results['ct1']['power']  # 200A Main (right)
            grid_2_power = results['ct2']['power']  # 100A Main (top)
            grid_4_power = results['ct4']['power']  # 100A Main (bottom)
            grid_5_power = results['ct5']['power']  # Unused

            grid_0_current = results['ct0']['current']
            grid_1_current = results['ct1']['current']
            grid_2_current = results['ct2']['current']
            grid_4_current = results['ct4']['current']
            grid_5_current = results['ct5']['current']

            solar_power = results['ct3']['power']
            solar_current = results['ct3']['current']
            solar_pf = results['ct3']['pf']

            # Set solar power and current to zero if the solar power is under 20W.
            if solar_power < 20:
                solar_power = 0
                solar_current = 0
                solar_pf = 0

            # Determine if the system is net producing or net consuming right now by looking at the two panel mains.
            # Since the current measured is always positive, we need to add a negative sign to the amperage value if we're exporting power.
            if grid_0_power < 0:
                grid_0_current = grid_0_current * -1
            if grid_1_power < 0:
                grid_1_current = grid_1_current * -1
            if solar_power > 0:
                solar_current = solar_current * -1

            # Unless your specific panel setup matches mine exactly, the following four lines will likely need to be re-written:
            home_consumption_power = grid_2_power + grid_4_power + grid_0_power + grid_1_power + solar_power
            net_power = home_consumption_power - solar_power
            home_consumption_current = grid_2_current + grid_4_current + grid_0_current + grid_1_current - solar_current
            net_current = grid_0_current + grid_1_current + grid_2_current + grid_4_current + solar_current

            if net_power < 0:
                current_status = "Producing"
            else:
                current_status = "Consuming"

            # Average 2 readings before sending to db
            if i < 2:
                solar_power_values['power'].append(solar_power)
                solar_power_values['current'].append(solar_current)
                solar_power_values['pf'].append(solar_pf)

                home_load_values['power'].append(home_consumption_power)
                home_load_values['current'].append(home_consumption_current)
                net_power_values['power'].append(net_power)
                net_power_values['current'].append(net_current)

                ct0_dict['power'].append(results['ct0']['power'])
                ct0_dict['current'].append(results['ct0']['current'])
                ct0_dict['pf'].append(results['ct0']['pf'])
                ct1_dict['power'].append(results['ct1']['power'])
                ct1_dict['current'].append(results['ct1']['current'])
                ct1_dict['pf'].append(results['ct1']['pf'])
                ct2_dict['power'].append(results['ct2']['power'])
                ct2_dict['current'].append(results['ct2']['current'])
                ct2_dict['pf'].append(results['ct2']['pf'])
                ct3_dict['power'].append(results['ct3']['power'])
                ct3_dict['current'].append(results['ct3']['current'])
                ct3_dict['pf'].append(results['ct3']['pf'])
                ct4_dict['power'].append(results['ct4']['power'])
                ct4_dict['current'].append(results['ct4']['current'])
                ct4_dict['pf'].append(results['ct4']['pf'])
                ct5_dict['power'].append(results['ct5']['power'])
                ct5_dict['current'].append(results['ct5']['current'])
                ct5_dict['pf'].append(results['ct5']['pf'])
                i += 1

            else:  # Calculate the average, send the result to InfluxDB, and reset the dictionaries for the next 2 sets of data.
                infl.write_to_influx(solar_power_values, home_load_values,
                                     net_power_values, ct0_dict, ct1_dict,
                                     ct2_dict, ct3_dict, ct4_dict, ct5_dict,
                                     poll_time, i)
                solar_power_values = dict(power=[], pf=[], current=[])
                home_load_values = dict(power=[], pf=[], current=[])
                net_power_values = dict(power=[], current=[])
                ct0_dict = dict(power=[], pf=[], current=[])
                ct1_dict = dict(power=[], pf=[], current=[])
                ct2_dict = dict(power=[], pf=[], current=[])
                ct3_dict = dict(power=[], pf=[], current=[])
                ct4_dict = dict(power=[], pf=[], current=[])
                ct5_dict = dict(power=[], pf=[], current=[])
                i = 0

                if logger.handlers[0].level == 10:
                    t = PrettyTable(
                        ['', 'CT0', 'CT1', 'CT2', 'CT3', 'CT4', 'CT5'])
                    t.add_row([
                        'Watts',
                        round(results['ct0']['power'], 3),
                        round(results['ct1']['power'], 3),
                        round(results['ct2']['power'], 3),
                        round(results['ct3']['power'], 3),
                        round(results['ct4']['power'], 3),
                        round(results['ct5']['power'], 3)
                    ])
                    t.add_row([
                        'Current',
                        round(results['ct0']['current'], 3),
                        round(results['ct1']['current'], 3),
                        round(results['ct2']['current'], 3),
                        round(results['ct3']['current'], 3),
                        round(results['ct4']['current'], 3),
                        round(results['ct5']['current'], 3)
                    ])
                    t.add_row([
                        'P.F.',
                        round(results['ct0']['pf'], 3),
                        round(results['ct1']['pf'], 3),
                        round(results['ct2']['pf'], 3),
                        round(results['ct3']['pf'], 3),
                        round(results['ct4']['pf'], 3),
                        round(results['ct5']['pf'], 3)
                    ])
                    t.add_row([
                        'Voltage',
                        round(results['voltage'], 3), '', '', '', '', ''
                    ])
                    s = t.get_string()
                    logger.debug('\n' + s)

            #sleep(0.1)

        except KeyboardInterrupt:
            infl.close_db()
            sys.exit()
Ejemplo n.º 3
0
            logger.info(
                dedent("""Usage:
                Start the program:                                  python3 power-monitor.py

                Collect raw data and build an interactive plot:     python3 power-monitor.py debug "chart title here" 

                Launch interactive phase correction mode:           python3 power-monitor.py phase

                Start the program like normal, but print all        python3 power-monitor.py terminal
                readings to the terminal window
                """))

        if MODE.lower() == 'debug':
            # This mode is intended to take a look at the raw CT sensor data.  It will take 2000 samples from each CT sensor, plot them to a single chart, write the chart to an HTML file located in /var/www/html/, and then terminate.
            # It also stores the samples to a file located in ./data/samples/last-debug.pkl so that the sample data can be read when this program is started in 'phase' mode.
            samples = collect_data(2000)
            logger.debug("Finished Collecting Samples")
            ct0_samples = samples['ct0']
            ct1_samples = samples['ct1']
            ct2_samples = samples['ct2']
            ct3_samples = samples['ct3']
            ct4_samples = samples['ct4']
            ct5_samples = samples['ct5']
            v_samples = samples['voltage']

            # Save samples to disk
            with open('data/samples/last-debug.pkl', 'wb') as f:
                pickle.dump(samples, f)

            if not title:
                title = input("Enter the title for this chart: ")
Ejemplo n.º 4
0
def find_phasecal(samples, ct_selection, accuracy_digits, board_voltage):
    # This controls how many times the calibration process is repeated for this particular CT.
    num_calibration_attempts = 20

    # Get Initial PF
    rebuilt_wave = rebuild_wave(samples[ct_selection], samples['voltage'], 1.0)
    results = check_phasecal(rebuilt_wave['ct'], rebuilt_wave['new_v'],
                             board_voltage)
    pf = results['pf']
    logger.debug(
        f"Please wait while I read {ct_selection} and calculate the best PHASECAL value. This can take a few minutes, so please be patient."
    )

    best_pfs = []
    previous_phasecal = 1.0
    previous_pf = pf
    trends = []

    # Base Increment settings for changing phasecal
    increment = 1.005
    decrement = 0.995
    big_increment = 1.01
    big_decrement = 0.98

    for i, _ in enumerate(range(3), start=1):
        best_pf = {
            'pf': 0,
            'cal': 0,
        }
        for _ in range(75):

            if round(pf, 4) == 1.0:
                best_pf.update({
                    'pf': pf,
                    'cal': new_phasecal,
                })
                break

            if pf < 1.0:
                # If the PF isn't better than 0.995, we can increment the phasecal by an amount twice as large, referred to as big_increment, to help speed up the process.
                if round(pf, 2) != 1.0:
                    new_phasecal = previous_phasecal * big_increment
                else:
                    new_phasecal = previous_phasecal * increment
                action = 'incremented'
            else:
                if round(pf, 2) != 1.0:
                    new_phasecal = previous_phasecal * big_decrement
                else:
                    new_phasecal = previous_phasecal * decrement
                action = 'decremented'

            # Collect a live sample and calculate PF using new_phasecal
            samples = collect_data(2000)
            rebuilt_wave = rebuild_wave(samples[ct_selection],
                                        samples['voltage'], new_phasecal)
            results = check_phasecal(rebuilt_wave['ct'], rebuilt_wave['new_v'],
                                     board_voltage)
            pf = results['pf']
            if pf > best_pf['pf']:
                best_pf.update({'pf': pf, 'cal': new_phasecal})

            #logger.debug(f"  PF: {pf} | Phasecal: {new_phasecal}")

            # Determine whether or not trend is moving away from 1.0 or towards 1.0.
            # Trend should be a moving average over two values.

            trends.append(pf - previous_pf)
            previous_phasecal = new_phasecal

            if len(trends) == 2:
                # Check to see if both values have the same sign to determine the actual trend, then empty the list
                if trends[0] < 0:
                    if trends[1] < 0:
                        trend = 'worse'
                        # If the trend is getting worse, reject the previous phasecal, and reduce cut increment/decrement by half.
                        increment = 1 + (abs(1 - increment) / 2)
                        decrement = decrement + ((1 - decrement) / 2)

                        # Apply the opposite action to the previous phasecal value to attempt to reverse the trend. If the previous phasecal
                        # was incremented, then we will decrement using the newly adjusted decrement value.
                        if action == 'increment':
                            # Decrement instead
                            new_phasecal = previous_phasecal * decrement
                        else:
                            # Increment instead
                            new_phasecal = previous_phasecal * increment

                if trends[1] > 0:
                    trend = 'better'

                trends = []
                continue  # Skip updating the previous phasecal and previous_pf since we want to attempt to reverse the trend.

            else:
                if action == 'increment':
                    # Repeat same action
                    new_phasecal = previous_phasecal * increment
                else:
                    new_phasecal = previous_phasecal * decrement

            previous_pf = pf

        logger.debug(f"Wave {i}/3 results: ")
        logger.debug(
            f" Best PF: {best_pf['pf']} using phasecal: {best_pf['cal']}")
        best_pfs.append(best_pf)

    return best_pfs