├── images
├── HVAC_cycle.mp4
├── Total_Energy.mp4
├── cycle_times.jpg
├── dehumidifier.mp4
├── wiring_to_pi.png
├── tasmota outlet.jpg
├── Node-Red_dashboard.png
├── power - rice cooker.png
├── Power - Washer zoom in.png
├── calibration_setup_01.jpg
├── hourly item - AC only.png
├── Node-Red_flow_dashboard.png
├── hour item - query details.png
├── Grafana_config_discrete_panel.jpg
├── xcel_energy_bill_compare_crop.png
├── Daily Energy - Cost july xcel date.png
├── compare_AC_to_dehumidifier_humidity.jpg
└── xcel_energy_saver switch_activation.jpg
├── circuitsetup_mqtt
├── emon_home.service
├── error_handling.py
├── atm90_e32_registers.py
├── monitor.py
└── atm90_e32_pi.py
├── LICENSE
└── README.md
/images/HVAC_cycle.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/HVAC_cycle.mp4
--------------------------------------------------------------------------------
/images/Total_Energy.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/Total_Energy.mp4
--------------------------------------------------------------------------------
/images/cycle_times.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/cycle_times.jpg
--------------------------------------------------------------------------------
/images/dehumidifier.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/dehumidifier.mp4
--------------------------------------------------------------------------------
/images/wiring_to_pi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/wiring_to_pi.png
--------------------------------------------------------------------------------
/images/tasmota outlet.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/tasmota outlet.jpg
--------------------------------------------------------------------------------
/images/Node-Red_dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/Node-Red_dashboard.png
--------------------------------------------------------------------------------
/images/power - rice cooker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/power - rice cooker.png
--------------------------------------------------------------------------------
/images/Power - Washer zoom in.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/Power - Washer zoom in.png
--------------------------------------------------------------------------------
/images/calibration_setup_01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/calibration_setup_01.jpg
--------------------------------------------------------------------------------
/images/hourly item - AC only.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/hourly item - AC only.png
--------------------------------------------------------------------------------
/images/Node-Red_flow_dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/Node-Red_flow_dashboard.png
--------------------------------------------------------------------------------
/images/hour item - query details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/hour item - query details.png
--------------------------------------------------------------------------------
/images/Grafana_config_discrete_panel.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/Grafana_config_discrete_panel.jpg
--------------------------------------------------------------------------------
/images/xcel_energy_bill_compare_crop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/xcel_energy_bill_compare_crop.png
--------------------------------------------------------------------------------
/images/Daily Energy - Cost july xcel date.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/Daily Energy - Cost july xcel date.png
--------------------------------------------------------------------------------
/images/compare_AC_to_dehumidifier_humidity.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/compare_AC_to_dehumidifier_humidity.jpg
--------------------------------------------------------------------------------
/images/xcel_energy_saver switch_activation.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsaitsai/circuitsetup_energy_to_mqtt/HEAD/images/xcel_energy_saver switch_activation.jpg
--------------------------------------------------------------------------------
/circuitsetup_mqtt/emon_home.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | # put .service file in /lib/systemd/system
3 | Description=Electrical Monitoring
4 |
5 | [Install]
6 | WantedBy=default.target
7 |
8 | [Service]
9 | # Command to execute when the service is started
10 | WorkingDirectory=/home/pi/emon
11 | User=pi
12 | ExecStart=/usr/bin/python3 /home/pi/emon/monitor.py
13 | Restart=on-failure
--------------------------------------------------------------------------------
/circuitsetup_mqtt/error_handling.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import logging
3 | logger = logging.getLogger(__name__)
4 |
5 |
6 | class Error(Exception):
7 | """ Base class for the exceptions in this module. """
8 | pass
9 |
10 |
11 | class RegistryWriteError(Error):
12 | """Exception raised when write to atm90e32 register
13 | cannot be validated.
14 |
15 | Attributes:
16 | message -- register where write verificaiton failed.
17 |
18 | """
19 |
20 | def __init__(self, message):
21 | self.message = message
22 |
23 |
24 | def handle_exception(e):
25 | """Function all modules share to handled exceptions.
26 | Currently error strings (e) are put into the log file as
27 | an exception.
28 |
29 | :param e: Error message.
30 |
31 | """
32 |
33 | logger.exception(f'Exception...{e}')
34 | sys.exit()
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Eric Tsai
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This repo has code and notes for whole home electrical power monitoring using a Raspberry Pi and [CircuitSetup modules](https://circuitsetup.us). Beyond just collecting data, it also uses Grafana to visualize electricity consumption.
2 |
3 |
4 | CircuitSetup module wiring to Raspberry Pi
5 |
6 |
7 | Node-Red dashboard
8 |
9 |
10 | Grafana Dashboard
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/circuitsetup_mqtt/atm90_e32_registers.py:
--------------------------------------------------------------------------------
1 | # REGISTERS *
2 | """Mappings of the atm90e32 to user readable strings.
3 | """
4 | # STATUS REGISTERS *#
5 | MeterEn = 0x00 # Metering Enable
6 | ChannelMapI = 0x01 # Current Channel Mapping Configuration
7 | ChannelMapU = 0x02 # Voltage Channel Mapping Configuration
8 | SagPeakDetCfg = 0x05 # Sag and Peak Detector Period Configuration
9 | OVth = 0x06 # Over Voltage Threshold
10 | ZXConfig = 0x07 # Zero-Crossing Config
11 | SagTh = 0x08 # Voltage Sag Th
12 | PhaseLossTh = 0x09 # Voltage Phase Losing Th
13 | INWarnTh = 0x0A # Neutral Current (Calculated) Warning Threshold
14 | OIth = 0x0B # Over Current Threshold
15 | FreqLoTh = 0x0C # Low Threshold for Frequency Detection
16 | FreqHiTh = 0x0D # High Threshold for Frequency Detection
17 | PMPwrCtrl = 0x0E # Partial Measurement Mode Power Control
18 | IRQ0MergeCfg = 0x0F # IRQ0 Merge Configuration
19 | ENStatus0 = 0x95 # Tells us if readings are above the threshold and being read
20 |
21 |
22 | # EMM STATUS REGISTERS *#
23 | SoftReset = 0x70 # Software Reset
24 | EMMState0 = 0x71 # EMM State 0
25 | EMMState1 = 0x72 # EMM State 1
26 | EMMIntState0 = 0x73 # EMM Interrupt Status 0
27 | EMMIntState1 = 0x74 # EMM Interrupt Status 1
28 | EMMIntEn0 = 0x75 # EMM Interrupt Enable 0
29 | EMMIntEn1 = 0x76 # EMM Interrupt Enable 1
30 | LastSPIData = 0x78 # Last Read/Write SPI Value
31 | CRCErrStatus = 0x79 # CRC Error Status
32 | CRCDigest = 0x7A # CRC Digest
33 | CfgRegAccEn = 0x7F # Configure Register Access Enable
34 |
35 | # LOW POWER MODE REGISTERS - NOT USED *#
36 | DetectCtrl = 0x10
37 | DetectTh1 = 0x11
38 | DetectTh2 = 0x12
39 | DetectTh3 = 0x13
40 | PMOffsetA = 0x14
41 | PMOffsetB = 0x15
42 | PMOffsetC = 0x16
43 | PMPGA = 0x17
44 | PMIrmsA = 0x18
45 | PMIrmsB = 0x19
46 | PMIrmsC = 0x1A
47 | PMConfig = 0x10B
48 | PMAvgSamples = 0x1C
49 | PMIrmsLSB = 0x1D
50 |
51 | # CONFIGURATION REGISTERS *#
52 | PLconstH = 0x31 # High Word of PL_Constant
53 | PLconstL = 0x32 # Low Word of PL_Constant
54 | MMode0 = 0x33 # Metering Mode Config
55 | MMode1 = 0x34 # PGA Gain Configuration for Current Channels
56 | PStartTh = 0x35 # Startup Power Th (P)
57 | QStartTh = 0x36 # Startup Power Th (Q)
58 | SStartTh = 0x37 # Startup Power Th (S)
59 | PPhaseTh = 0x38 # Startup Power Accum Th (P)
60 | QPhaseTh = 0x39 # Startup Power Accum Th (Q)
61 | SPhaseTh = 0x3A # Startup Power Accum Th (S)
62 |
63 | # CALIBRATION REGISTERS *#
64 | PoffsetA = 0x41 # A Line Power Offset (P)
65 | QoffsetA = 0x42 # A Line Power Offset (Q)
66 | PoffsetB = 0x43 # B Line Power Offset (P)
67 | QoffsetB = 0x44 # B Line Power Offset (Q)
68 | PoffsetC = 0x45 # C Line Power Offset (P)
69 | QoffsetC = 0x46 # C Line Power Offset (Q)
70 | PQGainA = 0x47 # A Line Calibration Gain
71 | PhiA = 0x48 # A Line Calibration Angle
72 | PQGainB = 0x49 # B Line Calibration Gain
73 | PhiB = 0x4A # B Line Calibration Angle
74 | PQGainC = 0x4B # C Line Calibration Gain
75 | PhiC = 0x4C # C Line Calibration Angle
76 |
77 | # FUNDAMENTAL#HARMONIC ENERGY CALIBRATION REGISTERS *#
78 | POffsetAF = 0x51 # A Fund Power Offset (P)
79 | POffsetBF = 0x52 # B Fund Power Offset (P)
80 | POffsetCF = 0x53 # C Fund Power Offset (P)
81 | PGainAF = 0x54 # A Fund Power Gain (P)
82 | PGainBF = 0x55 # B Fund Power Gain (P)
83 | PGainCF = 0x56 # C Fund Power Gain (P)
84 |
85 | # MEASUREMENT CALIBRATION REGISTERS *#
86 | UgainA = 0x61 # A Voltage RMS Gain
87 | IgainA = 0x62 # A Current RMS Gain
88 | UoffsetA = 0x63 # A Voltage Offset
89 | IoffsetA = 0x64 # A Current Offset
90 | UgainB = 0x65 # B Voltage RMS Gain
91 | IgainB = 0x66 # B Current RMS Gain
92 | UoffsetB = 0x67 # B Voltage Offset
93 | IoffsetB = 0x68 # B Current Offset
94 | UgainC = 0x69 # C Voltage RMS Gain
95 | IgainC = 0x6A # C Current RMS Gain
96 | UoffsetC = 0x6B # C Voltage Offset
97 | IoffsetC = 0x6C # C Current Offset
98 | IoffsetN = 0x6E # N Current Offset
99 |
100 | # ENERGY REGISTERS *#
101 | APenergyT = 0x80 # Total Forward Active
102 | APenergyA = 0x81 # A Forward Active
103 | APenergyB = 0x82 # B Forward Active
104 | APenergyC = 0x83 # C Forward Active
105 | ANenergyT = 0x84 # Total Reverse Active
106 | ANenergyA = 0x85 # A Reverse Active
107 | ANenergyB = 0x86 # B Reverse Active
108 | ANenergyC = 0x87 # C Reverse Active
109 | RPenergyT = 0x88 # Total Forward Reactive
110 | RPenergyA = 0x89 # A Forward Reactive
111 | RPenergyB = 0x8A # B Forward Reactive
112 | RPenergyC = 0x8B # C Forward Reactive
113 | RNenergyT = 0x8C # Total Reverse Reactive
114 | RNenergyA = 0x8D # A Reverse Reactive
115 | RNenergyB = 0x8E # B Reverse Reactive
116 | RNenergyC = 0x8F # C Reverse Reactive
117 |
118 | SAenergyT = 0x90 # Total Apparent Energy
119 | SenergyA = 0x91 # A Apparent Energy
120 | SenergyB = 0x92 # B Apparent Energy
121 | SenergyC = 0x93 # C Apparent Energy
122 |
123 |
124 | # FUNDAMENTAL # HARMONIC ENERGY REGISTERS *#
125 | APenergyTF = 0xA0 # Total Forward Fund. Energy
126 | APenergyAF = 0xA1 # A Forward Fund. Energy
127 | APenergyBF = 0xA2 # B Forward Fund. Energy
128 | APenergyCF = 0xA3 # C Forward Fund. Energy
129 | ANenergyTF = 0xA4 # Total Reverse Fund Energy
130 | ANenergyAF = 0xA5 # A Reverse Fund. Energy
131 | ANenergyBF = 0xA6 # B Reverse Fund. Energy
132 | ANenergyCF = 0xA7 # C Reverse Fund. Energy
133 | APenergyTH = 0xA8 # Total Forward Harm. Energy
134 | APenergyAH = 0xA9 # A Forward Harm. Energy
135 | APenergyBH = 0xAA # B Forward Harm. Energy
136 | APenergyCH = 0xAB # C Forward Harm. Energy
137 | ANenergyTH = 0xAC # Total Reverse Harm. Energy
138 | ANenergyAH = 0xAD # A Reverse Harm. Energy
139 | ANenergyBH = 0xAE # B Reverse Harm. Energy
140 | ANenergyCH = 0xAF # C Reverse Harm. Energy
141 |
142 | # POWER & P.F. REGISTERS *#
143 | PmeanT = 0xB0 # Total Mean Power (P)
144 | PmeanA = 0xB1 # A Mean Power (P)
145 | PmeanB = 0xB2 # B Mean Power (P)
146 | PmeanC = 0xB3 # C Mean Power (P)
147 | QmeanT = 0xB4 # Total Mean Power (Q)
148 | QmeanA = 0xB5 # A Mean Power (Q)
149 | QmeanB = 0xB6 # B Mean Power (Q)
150 | QmeanC = 0xB7 # C Mean Power (Q)
151 | SmeanT = 0xB8 # Total Mean Power (S)
152 | SmeanA = 0xB9 # A Mean Power (S)
153 | SmeanB = 0xBA # B Mean Power (S)
154 | SmeanC = 0xBB # C Mean Power (S)
155 | PFmeanT = 0xBC # Mean Power Factor
156 | PFmeanA = 0xBD # A Power Factor
157 | PFmeanB = 0xBE # B Power Factor
158 | PFmeanC = 0xBF # C Power Factor
159 |
160 | PmeanTLSB = 0xC0 # Lower Word (Tot. Act. Power)
161 | PmeanALSB = 0xC1 # Lower Word (A Act. Power)
162 | PmeanBLSB = 0xC2 # Lower Word (B Act. Power)
163 | PmeanCLSB = 0xC3 # Lower Word (C Act. Power)
164 | QmeanTLSB = 0xC4 # Lower Word (Tot. React. Power)
165 | QmeanALSB = 0xC5 # Lower Word (A React. Power)
166 | QmeanBLSB = 0xC6 # Lower Word (B React. Power)
167 | QmeanCLSB = 0xC7 # Lower Word (C React. Power)
168 | SAmeanTLSB = 0xC8 # Lower Word (Tot. App. Power)
169 | SmeanALSB = 0xC9 # Lower Word (A App. Power)
170 | SmeanBLSB = 0xCA # Lower Word (B App. Power)
171 | SmeanCLSB = 0xCB # Lower Word (C App. Power)
172 |
173 | # FUND#HARM POWER & V#I RMS REGISTERS *#
174 | PmeanTF = 0xD0 # Total Active Fund. Power
175 | PmeanAF = 0xD1 # A Active Fund. Power
176 | PmeanBF = 0xD2 # B Active Fund. Power
177 | PmeanCF = 0xD3 # C Active Fund. Power
178 | PmeanTH = 0xD4 # Total Active Harm. Power
179 | PmeanAH = 0xD5 # A Active Harm. Power
180 | PmeanBH = 0xD6 # B Active Harm. Power
181 | PmeanCH = 0xD7 # C Active Harm. Power
182 | UrmsA = 0xD9 # A RMS Voltage
183 | UrmsB = 0xDA # B RMS Voltage
184 | UrmsC = 0xDB # C RMS Voltage
185 | IrmsA = 0xDD # A RMS Current
186 | IrmsB = 0xDE # B RMS Current
187 | IrmsC = 0xDF # C RMS Current
188 | IrmsN = 0xD8 # Calculated N RMS Current
189 |
190 | PmeanTFLSB = 0xE0 # Lower Word (Tot. Act. Fund. Power)
191 | PmeanAFLSB = 0xE1 # Lower Word (A Act. Fund. Power)
192 | PmeanBFLSB = 0xE2 # Lower Word (B Act. Fund. Power)
193 | PmeanCFLSB = 0xE3 # Lower Word (C Act. Fund. Power)
194 | PmeanTHLSB = 0xE4 # Lower Word (Tot. Act. Harm. Power)
195 | PmeanAHLSB = 0xE5 # Lower Word (A Act. Harm. Power)
196 | PmeanBHLSB = 0xE6 # Lower Word (B Act. Harm. Power)
197 | PmeanCHLSB = 0xE7 # Lower Word (C Act. Harm. Power)
198 | # 0xE8 ## Reserved Register
199 | UrmsALSB = 0xE9 # Lower Word (A RMS Voltage)
200 | UrmsBLSB = 0xEA # Lower Word (B RMS Voltage)
201 | UrmsCLSB = 0xEB # Lower Word (C RMS Voltage)
202 | # 0xEC ## Reserved Register
203 | IrmsALSB = 0xED # Lower Word (A RMS Current)
204 | IrmsBLSB = 0xEE # Lower Word (B RMS Current)
205 | IrmsCLSB = 0xEF # Lower Word (C RMS Current)
206 |
207 | # THD, FREQUENCY, ANGLE & TEMPTEMP REGISTERS*#
208 | THDNUA = 0xF1 # A Voltage THD+N
209 | THDNUB = 0xF2 # B Voltage THD+N
210 | THDNUC = 0xF3 # C Voltage THD+N
211 | # 0xF4 ## Reserved Register
212 | THDNIA = 0xF5 # A Current THD+N
213 | THDNIB = 0xF6 # B Current THD+N
214 | THDNIC = 0xF7 # C Current THD+N
215 | Freq = 0xF8 # Frequency
216 | PAngleA = 0xF9 # A Mean Phase Angle
217 | PAngleB = 0xFA # B Mean Phase Angle
218 | PAngleC = 0xFB # C Mean Phase Angle
219 | Temp = 0xFC # Measured Temperature
220 | UangleA = 0xFD # A Voltage Phase Angle
221 | UangleB = 0xFE # B Voltage Phase Angle
222 | UangleC = 0xFF # C Voltage Phase Angle
223 |
--------------------------------------------------------------------------------
/circuitsetup_mqtt/monitor.py:
--------------------------------------------------------------------------------
1 | ##########################################################################
2 | # Much of this code was copied from Margaret Johnson's (@BitKnitting)
3 | # FHmonitor project https://github.com/BitKnitting/FHmonitor
4 | #
5 | # 2020-09: Eric Tsai
6 | # parameterize SPI groupings to read multiple CircuitSetup
7 | # monitoring boards and publish readings to MQTT
8 | # Not Done: THD, phase angle, publish on minimum change
9 | #########################################################################
10 |
11 | '''
12 |
13 | --start script
14 | python3 monitor.py
15 |
16 | --view logs
17 | journalctl | tail
18 |
19 | *** todo ***
20 | 1. Set MQTT broker
21 | 2. Append the right number of circuits
22 | 3. Calibrate AC adapter gain and current transformer gain
23 | 4. Set reading rate by changing sleep time
24 |
25 |
26 | '''
27 |
28 |
29 | from error_handling import handle_exception
30 | from atm90_e32_pi import ATM90e32
31 |
32 | import board
33 | import digitalio
34 | import logging
35 | logger = logging.getLogger(__name__)
36 | import paho.mqtt.client as mqtt
37 | import time
38 | from datetime import datetime
39 |
40 | logging.basicConfig(level=logging.WARNING)
41 |
42 | # Set to false if don't want regular readings, and want to send readings if > minimum change
43 | ALWAYS_PUBLISH = True
44 |
45 | class Monitor:
46 | """
47 |
48 | """
49 | def on_connect(client, userdata, flags, rc):
50 | print("Connected with result code "+str(rc))
51 | try:
52 | self.client.publish("/emon/heartbeat", "connected", qos=0)
53 | except:
54 | time.sleep(3)
55 |
56 | def on_disconnect(client, userdata,rc=0):
57 | print("disconnected with result code "+str(rc))
58 | self.client.reconnect
59 |
60 | def __init__(self, led_pin=None):
61 | #logger.setLevel(logging.warning)
62 | self.debug = False
63 | #if using ctrl_c or restarting a lot, set this to False. If run in background, set to True
64 | self.as_service = True
65 |
66 | self.db = None
67 | self.t_heartbeat = time.perf_counter()
68 |
69 | self.circuit = []
70 | self.num_circuit = 0
71 |
72 | self.energy_sensor = None
73 | self.energy_sensor2 = None
74 | self.energy_sensor3 = None
75 | self.energy_sensor4 = None
76 | if led_pin is None:
77 | led_pin = board.D18 # We always wire to GPIO 18.
78 | self.led = digitalio.DigitalInOut(board.D18)
79 | self.led.direction = digitalio.Direction.OUTPUT
80 |
81 | self.client = mqtt.Client()
82 | self.client.on_disconnect = self.on_disconnect
83 |
84 | logging.warning('started emon..')
85 | try:
86 | # *** todo ***
87 | self.client.connect("192.168.1.115",1883,60)
88 | if self.as_service:
89 | logging.warning('emon start in threaded mqtt mode')
90 | try:
91 | self.client.loop_start()
92 | except:
93 | logging.warning('emon fail to start loop')
94 | except:
95 | time.sleep(10)
96 |
97 | ####################################################
98 | # Initialize the energy sensor. The properties are
99 | # are written to atm90e32 registers during initialization.
100 | # They are specific to the Power and Current Transformers
101 | # being used. An exception occurs if the write cannot
102 | # be verified.
103 | ####################################################
104 |
105 | def init_sensor(self):
106 | """
107 | Initialize the atm90e32 by setting the calibration registry properties.
108 | """
109 | linefreq_60hz = 4485
110 | #pga gain = amps to house. 100amps = 21, >100amps = 42
111 | gain_pga_100amp = 21
112 |
113 |
114 | # *** todo ***
115 | # Replace values with the correct calibration values after performing calibration
116 | gain_voltage_ACA_01 = 7508 # AC adapter model 01, voltage gain for 1st module (circuits 0 and 1)
117 | gain_voltage_ACA_02 = 7261 # AC adapter model 02, voltage gain for 2nd module (circuits 2 and 3)
118 |
119 | # *** todo ***
120 | # current gain for different current transformers (20A, 80A, and 100A transformers)
121 | # if you buy identical 20A split core transformers, they can use the same current gain. If you ebay it and
122 | # use a bunch of different 20A current transformers, may need to calibrate individually ie "20A_00", "20A_01"
123 | gain_current_20A_00 = 5511 # emon_current / real current = x/gain_current, new_gain_current=5511
124 | gain_current_80A_00 = 20966 # emon_current / real current = x/gain_current, new_gain_current=20966
125 | gain_current_100A_00 = 13777 # emon_current / real current = x/gain_current, new_gain_current=13777
126 |
127 | try:
128 |
129 | # Hardcode each circuit's calibration parameters based on AC adapter used and voltage transformer used.
130 | # Using two 6-channel CircuitSetup modules, we actually have four circuits and 3 channels per circuit.
131 | # Each circuit needs to be instantiated with a single AC adapter voltage gain and three
132 | # current transformer current gain (one per channel).
133 | # ATM90e32(linefreq, gain_pga_100amp, AC_ADAPTER_voltage_gain, ...
134 | # ch_0_xfmr_current_gain, ch_1_xfmr_current_gain, ch_2_xfmr_current_gain, raspberry_pi_cc_pin)
135 | #
136 | #
137 | # *** todo ***
138 | #First Module: circuit 0, channel 0-2; Phase A
139 | self.circuit.append(ATM90e32(linefreq_60hz, gain_pga_100amp, gain_voltage_ACA_01, gain_current_100A_00, gain_current_80A_00, gain_current_20A_00, board.D5))
140 |
141 | #First Module: circuit 1, channel 0-2; Phase A
142 | self.circuit.append(ATM90e32(linefreq_60hz, gain_pga_100amp, gain_voltage_ACA_01, gain_current_20A_00, gain_current_20A_00, gain_current_20A_00, board.D6))
143 |
144 | #Second Module: circuit 2, channel 0-2; Phase B
145 | self.circuit.append(ATM90e32(linefreq_60hz, gain_pga_100amp, gain_voltage_ACA_02, gain_current_100A_00, gain_current_20A_00, gain_current_20A_00, board.D13))
146 |
147 | #Second Module: circuit 3, channel 0-2; Phase B
148 | self.circuit.append(ATM90e32(linefreq_60hz, gain_pga_100amp, gain_voltage_ACA_02, gain_current_20A_00, gain_current_80A_00, gain_current_20A_00, board.D19))
149 |
150 | #append more circuits as needed, or delete as needed
151 |
152 | self.num_circuit = len(self.circuit) # returns 4 for two 6-channel boards
153 |
154 | # generate unique mqtt topics based on circuit number and channel
155 | # circuit 0, channel 0
156 | # self.circuit[0].readings_ray[0]["name"] = "c0c0"
157 | # With two 6-channel circuit setup modules, we have 4 circuits with 3 channels each, like this:
158 | # c0c0, c0c1, c0c2
159 | # c1c0, c1c1, c1c2
160 | # c2c0, c2c1, c2c2
161 | # c3c0, c3c1, c3c2
162 | for i in range(len(self.circuit)):
163 | for j in range(len(self.circuit[i].readings_ray)):
164 | # c0c1 = circuit 0, channel 1
165 | # self.circuit[0].readings_ray[1]["name"] = "c0c1"
166 | self.circuit[i].readings_ray[j]["name"] = "c" + str(i) + "c" + str(j)
167 |
168 | # Option to use minimum change to send updates
169 | # intialize sane min change values so we're not reporting too much
170 | # individual: self.circuit[0].readings_min_change["line_current"] = .02
171 | for x in self.circuit:
172 | # means self.circuit[0, 1, 2, 3].get_all_readings()
173 | #newreadings = x.get_all_readings()
174 | #for y in range(len(x.readings_ray)):
175 | for y in x.readings_min_change:
176 | # means x.readings_ray[0, 1, 2], y=0, 1, 2 objects
177 | y["line_volt"] = 2.0
178 | y["line_current"] = -1
179 | y["power_active"] = -1
180 | y["power_reactive"] = -1
181 | y["power_apparent"] = -1
182 | y["power_factor"] = 1000.0
183 | y["phase_angle"] = 1
184 | y["THD_volt"] = 1
185 | y["THD_current"] = 1
186 | for z in range(len(x.update_interval)):
187 | x.update_interval[z] = 25.0
188 | # todo: add minimum time
189 | #for x.readings_min_time = 10 seconds
190 |
191 | #debug print
192 | #print("----- double check min values ----")
193 | for x in self.circuit:
194 | for y in x.readings_min_change:
195 | # means x.readings_min_change[0, 1, 2], y=0, 1, 2 objects
196 | for z in y:
197 | #print(str(z), y[z])
198 | pass
199 |
200 |
201 | logger.info('Energy meter has been initialized.')
202 | sys0 = self.circuit[0].sys_status0
203 | if (sys0 == 0xFFFF or sys0 == 0):
204 | e = 'EXCEPTION: Cannot connect to the energy meter.'
205 | handle_exception(e)
206 | logger.info('Energy meter is working.')
207 | return True
208 | except Exception as e:
209 | handle_exception(e)
210 | return False
211 |
212 |
213 | ####################################################
214 | # Take electrical reading
215 | ####################################################
216 |
217 | def take_reading(self):
218 |
219 | if not self.as_service:
220 | #use loop_start() if running as service. If testing, use this.
221 | try:
222 | self.client.loop(.1)
223 | except:
224 | time.sleep(10)
225 |
226 | if (time.perf_counter() - self.t_heartbeat > 240) or (time.perf_counter() - self.t_heartbeat < 0):
227 | self.t_heartbeat = time.perf_counter()
228 | now = datetime.now()
229 | current_time = now.strftime("%d/%m/%y %H:%M:%S")
230 | try:
231 | self.client.publish("/emon/heartbeat", self.t_heartbeat, qos=0)
232 | except:
233 | logging.warning('emon failed to publish heartbeat')
234 | time.sleep(5)
235 |
236 |
237 | # individual readings
238 | # self.circuit[?].get_all_readings()
239 | # self.circuit[X].readings_ray[Y]["metric"]
240 | # X = circuit number (0 to 3 when using two CircuitSetup 6 channel modules)
241 | # Y = channel number (0 to 2), each circuit has 3 channels
242 | # metric = power_active, power_factor, etc...
243 |
244 | for x in self.circuit:
245 | # means self.circuit[0, 1, 2, 3], x=circuit number, iterate through all circuits
246 | x.get_all_readings()
247 | for y in range(len(x.readings_ray)):
248 | # means x.readings_ray[0, 1, 2], y=channels, iterate through all channels
249 | publish_flag = False
250 | if ((x.update_interval[y] == 0.0) or (time.perf_counter() - x.previous_timestamp[y])) > x.update_interval[y]:
251 | publish_flag = True
252 | x.previous_timestamp[y] = time.perf_counter()
253 | if self.debug:
254 | print("time stamp for ", x.readings_ray[y]["name"], " @ ", time.perf_counter(), " more than ", x.update_interval[y], " seconds ago")
255 |
256 | for z in x.readings_ray[y]:
257 | # means readings_ray[y]["metric"], z=metric like line_current, power_active, etc..
258 | # the first item in list is "name" - don't publish that.
259 | if z != "name":
260 | # decide if change is great enough
261 | if ( abs(x.readings_ray[y][z] - x.readings_previous[y][z]) > x.readings_min_change[y][z]):
262 | if self.debug:
263 | print("changed! ", x.readings_ray[y]["name"], "/", str(z), " from ", x.readings_previous[y][z], " to ", x.readings_ray[y][z])
264 | x.readings_previous[y][z] = x.readings_ray[y][z]
265 | publish_flag = True
266 | else:
267 | pass
268 |
269 | # publish the data if 1) update interval exceeded 2) metric value changed enough 3) we're just supposed to always publish
270 | if publish_flag or ALWAYS_PUBLISH:
271 | mqtt_topic = "/emon/reading/" + x.readings_ray[y]["name"] + "/" + str(z)
272 | #print(mqtt_topic)
273 | try:
274 | self.client.publish(mqtt_topic, x.readings_ray[y][z])
275 | except:
276 | time.sleep(5)
277 | #print(" loop=", y["name"], " attribute", str(z), " = ", y[z])
278 | else:
279 | #no publish
280 | pass
281 |
282 | return
283 |
284 |
285 |
286 | m = Monitor()
287 |
288 | m.init_sensor()
289 |
290 | while(1):
291 |
292 | #t0= time.perf_counter()
293 | m.take_reading()
294 | #print("read duration = ", time.perf_counter()-t0)
295 | # it takes 0.25 seconds to take reading and publish on 12 channels.
296 |
297 | # *** todo ***
298 | # Set how frequently to make readings and publish data
299 | time.sleep(0.5)
300 |
301 |
302 |
303 |
304 |
--------------------------------------------------------------------------------
/circuitsetup_mqtt/atm90_e32_pi.py:
--------------------------------------------------------------------------------
1 | #####################################################################
2 | # SPI handling copied from Margaret Johnson (@BitKnitting)
3 | # atm90e32 SPI utility to read power, power factor, current, voltage, etc.
4 | # Eric Tsai - 2020-09: added power factor and group readings
5 | # todo: THD, minimum change updates
6 | #####################################################################
7 | from error_handling import RegistryWriteError
8 | import sys
9 | # Used R instead of * to accomodate Flake8.
10 | import atm90_e32_registers as R
11 |
12 |
13 | from adafruit_bus_device.spi_device import SPIDevice
14 |
15 | import board
16 | import busio
17 | import digitalio
18 | import math
19 | import time
20 | import struct
21 | import logging
22 | logger = logging.getLogger(__name__)
23 |
24 |
25 | SPI_WRITE = 0
26 | SPI_READ = 1
27 |
28 |
29 | class ATM90e32:
30 | """This code runs on a Raspberry Pi and talks to an atm90e32 chip
31 | over SPI. We use `Circuit Setup's Single Phase Energy Meter
32 | `_
33 |
34 | Initialize the atm90e32 by setting the calibration registry properties.
35 | Calibration is discussed within our
36 | `FitHome wiki `_ .
37 |
38 | The SPI pins (including the cs pin) will need to be wired between
39 | a Raspberry Pi and the atm90e32. SPI pins on the Raspberry Pi and Circuit Setup's
40 | energy meter are discussed within our `FitHome wiki
41 |
42 | """ # noqa
43 | ##########################################################################
44 |
45 | '''
46 | self.readings = {
47 | 'name': "ch_x",
48 | 'line_volt': 0.5,
49 | 'line_current': 0.5,
50 | 'power_active': 0.5,
51 | 'power_reactive': 0.5,
52 | 'power_apparent': 0.5,
53 | 'power_factor': 0.5,
54 | 'phase_angle': 0.5,
55 | 'THD_volt': 1,
56 | 'THD_current': 1
57 | }
58 | '''
59 |
60 | def __init__(self, linefreq, pgagain, ugain, igainA, igainB, igainC,
61 | cs_pin=None):
62 | pin = board.D5 if cs_pin is None else cs_pin
63 | # We'll need all the settings for the
64 | self._linefreq = linefreq
65 | self._pgagain = pgagain
66 | self._ugain = ugain
67 | self._igainA = igainA
68 | self._igainB = igainB
69 | self._igainC = igainC
70 | spi = busio.SPI(board.SCLK, board.MOSI, board.MISO)
71 |
72 | #cable select pin is same as GPIO name, i.e. "board.D18" = GPIO18
73 | #board.D5 = pin 29 / GPIO5, which is what's already connected for gray wire.
74 | #cs = digitalio.DigitalInOut(pin)
75 | cs = digitalio.DigitalInOut(pin)
76 | # How to use SPI
77 | # https://learn.adafruit.com/circuitpython-basics-i2c-and-spi/spi-devices
78 | # https://circuitpython.readthedocs.io/projects/busdevice/en/latest/_modules/adafruit_bus_device/spi_device.html
79 | self._device = SPIDevice(spi, cs, baudrate=200000, polarity=1, phase=1)
80 | self._init_config()
81 |
82 | # create holder for all readings all channels (A, B, C)
83 | # self.readings_ray = []
84 | self.readings_ray = [
85 | {
86 | 'name': "ch_x",
87 | 'line_volt': 0.5,
88 | 'line_current': 0.5,
89 | 'power_active': 0.5,
90 | 'power_reactive': 0.5,
91 | 'power_apparent': 0.5,
92 | 'power_factor': 0.5,
93 | 'phase_angle': 0.5,
94 | 'THD_volt': 1,
95 | 'THD_current': 1
96 | }
97 | for k in range(3)
98 | ]
99 |
100 | self.readings_previous =[
101 | {
102 | 'line_volt': 0.5,
103 | 'line_current': 0.5,
104 | 'power_active': 0.5,
105 | 'power_reactive': 0.5,
106 | 'power_apparent': 0.5,
107 | 'power_factor': 0.5,
108 | 'phase_angle': 0.5,
109 | 'THD_volt': 1,
110 | 'THD_current': 1
111 | }
112 | for k in range(3)
113 | ]
114 |
115 | self.readings_min_change = [
116 | {
117 | 'line_volt': 0.0,
118 | 'line_current': 0.0,
119 | 'power_active': 0.0,
120 | 'power_reactive': 0.0,
121 | 'power_apparent': 0.0,
122 | 'power_factor': 0.0,
123 | 'phase_angle': 0.0,
124 | 'THD_volt': 0.0,
125 | 'THD_current': 0.0
126 | }
127 | for k in range(3)
128 | ]
129 |
130 | self.readings_timestamp = [
131 | {
132 | 'line_volt': 0.5,
133 | 'line_current': 0.5,
134 | 'power_active': 0.5,
135 | 'power_reactive': 0.5,
136 | 'power_apparent': 0.5,
137 | 'power_factor': 0.5,
138 | 'phase_angle': 0.5,
139 | 'THD_volt': 1,
140 | 'THD_current': 1
141 | }
142 | for k in range(3)
143 | ]
144 | self.previous_timestamp = [0.0, 0.0, 0.0]
145 | self.update_interval = [0.0, 0.0, 0.0]
146 | self.readings_min_time = [
147 | {
148 | 'line_volt': 0.5,
149 | 'line_current': 0.5,
150 | 'power_active': 0.5,
151 | 'power_reactive': 0.5,
152 | 'power_apparent': 0.5,
153 | 'power_factor': 0.5,
154 | 'phase_angle': 0.5,
155 | 'THD_volt': 1,
156 | 'THD_current': 1
157 | }
158 | for k in range(3)
159 | ]
160 |
161 | self.readings_per_moodule = {
162 | 'sys_status0': 0,
163 | 'sys_tatus1': 0,
164 | 'meter_status0': 0,
165 | 'meter_status1': 0,
166 | 'freq': 0.5
167 | }
168 |
169 |
170 | def _usleep(self, x): return time.sleep(x/(1e7))
171 |
172 | def _init_config(self):
173 | # CurrentGainCT2 = 25498 #25498 - SCT-013-000 100A/50mA
174 | if (self._linefreq == 4485 or self._linefreq == 5231):
175 | # North America power frequency
176 | FreqHiThresh = 61 * 100
177 | FreqLoThresh = 59 * 100
178 | sagV = 90
179 | else:
180 | FreqHiThresh = 51 * 100
181 | FreqLoThresh = 49 * 100
182 | sagV = 190
183 |
184 | # calculation for voltage sag threshold - assumes we do not want to
185 | # go under 90v for split phase and 190v otherwise sqrt(2) = 1.41421356
186 | fvSagTh = (sagV * 100 * 1.41421356) / (2 * self._ugain / 32768)
187 | # convert to int for sending to the atm90e32.
188 | vSagTh = self._round_number(fvSagTh)
189 |
190 | self._spi_rw(SPI_WRITE, R.SoftReset, 0x789A) # Perform soft reset
191 | # enable register config access
192 | self._spi_rw(SPI_WRITE, R.CfgRegAccEn, 0x55AA)
193 | self._spi_rw(SPI_WRITE, R.MeterEn, 0x0001) # Enable Metering
194 |
195 | # Voltage sag threshold
196 | self._spi_rw(SPI_WRITE, R.SagTh, vSagTh)
197 | # High frequency threshold - 61.00Hz
198 | self._spi_rw(SPI_WRITE, R.FreqHiTh, FreqHiThresh)
199 | # Lo frequency threshold - 59.00Hz
200 | self._spi_rw(SPI_WRITE, R.FreqLoTh, FreqLoThresh)
201 | self._spi_rw(SPI_WRITE, R.EMMIntEn0, 0xB76F) # Enable interrupts
202 | self._spi_rw(SPI_WRITE, R.EMMIntEn1, 0xDDFD) # Enable interrupts
203 | # Clear interrupt flags
204 | self._spi_rw(SPI_WRITE, R.EMMIntState0, 0x0001)
205 | # Clear interrupt flags
206 | self._spi_rw(SPI_WRITE, R.EMMIntState1, 0x0001)
207 | # ZX2, ZX1, ZX0 pin config
208 | self._spi_rw(SPI_WRITE, R.ZXConfig, 0x0A55)
209 |
210 | # Set metering config values (CONFIG)
211 | # PL Constant MSB (default) - Meter Constant
212 | # = 3200 - PL Constant = 140625000
213 | self._spi_rw(SPI_WRITE, R.PLconstH, 0x0861)
214 | # PL Constant LSB (default) - this is 4C68 in the application note,
215 | # which is incorrect
216 | self._spi_rw(SPI_WRITE, R.PLconstL, 0xC468)
217 | # Mode Config (frequency set in main program)
218 | self._spi_rw(SPI_WRITE, R.MMode0, self._linefreq)
219 | # PGA Gain Configuration for Current Channels - 0x002A (x4)
220 | # # 0x0015 (x2) # 0x0000 (1x)
221 | self._spi_rw(SPI_WRITE, R.MMode1, self._pgagain)
222 | # Active Startup Power Threshold - 50% of startup current
223 | # = 0.9/0.00032 = 2812.5
224 | # self._spi_rw(SPI_WRITE, PStartTh, 0x0AFC)
225 | # Testing a little lower setting...because readings aren't
226 | # happening if power is off then turned on....
227 | # Startup Power Threshold = .4/.00032 = 1250 = 0x04E2
228 | # Just checking with 0.
229 | self._spi_rw(SPI_WRITE, R.PStartTh, 0x0000)
230 | # Reactive Startup Power Threshold
231 | # self._spi_rw(SPI_WRITE, QStartTh, 0x0AEC)
232 | self._spi_rw(SPI_WRITE, R.QStartTh, 0x0000)
233 | # Apparent Startup Power Threshold
234 | self._spi_rw(SPI_WRITE, R.SStartTh, 0x0000)
235 | # Active Phase Threshold = 10% of startup current
236 | # = 0.06/0.00032 = 187.5
237 | # self._spi_rw(SPI_WRITE, PPhaseTh, 0x00BC)
238 | self._spi_rw(SPI_WRITE, R.PPhaseTh, 0x0000)
239 | # Reactive Phase Threshold
240 | self._spi_rw(SPI_WRITE, R.QPhaseTh, 0x0000)
241 | # Apparent Phase Threshold
242 | self._spi_rw(SPI_WRITE, R.SPhaseTh, 0x0000)
243 |
244 | # Set metering calibration values (CALIBRATION)
245 | # Line calibration gain
246 | self._spi_rw(SPI_WRITE, R.PQGainA, 0x0000)
247 | # Line calibration angle
248 | self._spi_rw(SPI_WRITE, R.PhiA, 0x0000)
249 | # Line calibration gain
250 | self._spi_rw(SPI_WRITE, R.PQGainB, 0x0000)
251 | # Line calibration angle
252 | self._spi_rw(SPI_WRITE, R.PhiB, 0x0000)
253 | # Line calibration gain
254 | self._spi_rw(SPI_WRITE, R.PQGainC, 0x0000)
255 | # Line calibration angle
256 | self._spi_rw(SPI_WRITE, R.PhiC, 0x0000)
257 | # A line active power offset
258 | self._spi_rw(SPI_WRITE, R.PoffsetA, 0x0000)
259 | # A line reactive power offset
260 | self._spi_rw(SPI_WRITE, R.QoffsetA, 0x0000)
261 | # B line active power offset
262 | self._spi_rw(SPI_WRITE, R.PoffsetB, 0x0000)
263 | # B line reactive power offset
264 | self._spi_rw(SPI_WRITE, R.QoffsetB, 0x0000)
265 | # C line active power offset
266 | self._spi_rw(SPI_WRITE, R.PoffsetC, 0x0000)
267 | # C line reactive power offset
268 | self._spi_rw(SPI_WRITE, R.QoffsetC, 0x0000)
269 |
270 | # Set metering calibration values (HARMONIC)
271 | # A Fund. active power offset
272 | self._spi_rw(SPI_WRITE, R.POffsetAF, 0x0000)
273 | # B Fund. active power offset
274 | self._spi_rw(SPI_WRITE, R.POffsetBF, 0x0000)
275 | # C Fund. active power offset
276 | self._spi_rw(SPI_WRITE, R.POffsetCF, 0x0000)
277 | # A Fund. active power gain
278 | self._spi_rw(SPI_WRITE, R.PGainAF, 0x0000)
279 | # B Fund. active power gain
280 | self._spi_rw(SPI_WRITE, R.PGainBF, 0x0000)
281 | # C Fund. active power gain
282 | self._spi_rw(SPI_WRITE, R.PGainCF, 0x0000)
283 |
284 | # Set measurement calibration values (ADJUST)
285 | # A Voltage rms gain
286 | self._spi_rw(SPI_WRITE, R.UgainA, self._ugain)
287 | # A line current gain
288 | self._spi_rw(SPI_WRITE, R.IgainA, self._igainA)
289 | self._spi_rw(SPI_WRITE, R.UoffsetA, 0x0000) # A Voltage offset
290 | # A line current offset
291 | self._spi_rw(SPI_WRITE, R.IoffsetA, 0x0000)
292 | # B Voltage rms gain
293 | self._spi_rw(SPI_WRITE, R.UgainB, self._ugain)
294 | # B line current gain
295 | self._spi_rw(SPI_WRITE, R.IgainB, self._igainB)
296 | self._spi_rw(SPI_WRITE, R.UoffsetB, 0x0000) # B Voltage offset
297 | # B line current offset
298 | self._spi_rw(SPI_WRITE, R.IoffsetB, 0x0000)
299 | # C Voltage rms gain
300 | self._spi_rw(SPI_WRITE, R.UgainC, self._ugain)
301 | # C line current gain
302 | self._spi_rw(SPI_WRITE, R.IgainC, self._igainC)
303 | self._spi_rw(SPI_WRITE, R.UoffsetC, 0x0000) # C Voltage offset
304 | # C line current offset
305 | self._spi_rw(SPI_WRITE, R.IoffsetC, 0x0000)
306 | self._spi_rw(SPI_WRITE, R.CfgRegAccEn, 0x0000) # end configuration
307 |
308 | # In order to get correct results, I needed to insert
309 | # a 'significant' delay.
310 | time.sleep(1)
311 | ##########################################################################
312 | @property
313 | def lastSpiData(self):
314 | reading = self._spi_rw(SPI_READ, R.LastSPIData, 0xFFFF)
315 | return reading
316 | ##########################################################################
317 | @property
318 | def sys_status0(self):
319 | reading = self._spi_rw(SPI_READ, R.EMMIntState0, 0xFFFF)
320 | return reading
321 | ##########################################################################
322 | @property
323 | def sys_status1(self):
324 | reading = self._spi_rw(SPI_READ, R.EMMIntState1, 0xFFFF)
325 | return reading
326 | #########################################################################
327 |
328 | @property
329 | def meter_status0(self):
330 | reading = self._spi_rw(SPI_READ, R.EMMState0, 0xFFFF)
331 | return reading
332 |
333 | ##########################################################################
334 | @property
335 | def en_status0(self):
336 | reading = self._spi_rw(SPI_READ, R.ENStatus0, 0xFFFF)
337 | return reading
338 | #########################################################################
339 | @property
340 | def meter_status1(self):
341 | reading = self._spi_rw(SPI_READ, R.EMMState1, 0xFFFF)
342 | return reading
343 | #########################################################################
344 | @property
345 | def line_voltageA(self):
346 | reading = self._spi_rw(SPI_READ, R.UrmsA, 0xFFFF)
347 | return reading / 100.0
348 | #########################################################################
349 | @property
350 | def line_voltageB(self):
351 | reading = self._spi_rw(SPI_READ, R.UrmsB, 0xFFFF)
352 | return reading / 100.0
353 | #########################################################################
354 | @property
355 | def line_voltageC(self):
356 | reading = self._spi_rw(SPI_READ, R.UrmsC, 0xFFFF)
357 | return reading / 100.0
358 | #########################################################################
359 | @property
360 | def line_currentA(self):
361 | reading = self._spi_rw(SPI_READ, R.IrmsA, 0xFFFF)
362 | return reading / 1000.0
363 | #########################################################################
364 | @property
365 | def line_currentB(self):
366 | reading = self._spi_rw(SPI_READ, R.IrmsB, 0xFFFF)
367 | return reading / 1000.0
368 | #########################################################################
369 | @property
370 | def line_currentC(self):
371 | reading = self._spi_rw(SPI_READ, R.IrmsC, 0xFFFF)
372 | return reading / 1000.0
373 | #########################################################################
374 | @property
375 | def frequency(self):
376 | reading = self._spi_rw(SPI_READ, R.Freq, 0xFFFF)
377 | return reading / 100.0
378 | #########################################################################
379 | @property
380 | def total_active_power(self):
381 | reading = self._read32Register(R.PmeanT, R.PmeanTLSB)
382 | return reading * 0.00032
383 | #########################################################################
384 |
385 | @property
386 | def active_power_A(self):
387 | reading = self._read32Register(R.PmeanA, R.PmeanALSB)
388 | return round(reading * 0.00032, 3)
389 | #####################################################################
390 | #tsai: added B
391 | @property
392 | def active_power_B(self):
393 | reading = self._read32Register(R.PmeanB, R.PmeanBLSB)
394 | return round(reading * 0.00032, 3)
395 | #####################################################################
396 |
397 | @property
398 | def active_power_C(self):
399 | reading = self._read32Register(R.PmeanC, R.PmeanCLSB)
400 | return round(reading * 0.00032)
401 | #####################################################################
402 |
403 | @property
404 | def total_reactive_power(self):
405 | reading = self._read32Register(R.QmeanT, R.PmeanTLSB)
406 | return round(reading * 0.00032, 3)
407 | #########################################################################
408 |
409 | @property
410 | def reactive_power_A(self):
411 | reading = self._read32Register(R.QmeanA, R.PmeanALSB)
412 | return round(reading * 0.00032, 3)
413 | #####################################################################
414 | #tsai
415 | @property
416 | def reactive_power_B(self):
417 | reading = self._read32Register(R.QmeanB, R.PmeanBLSB)
418 | return round(reading * 0.00032, 3)
419 | #####################################################################
420 |
421 | @property
422 | def reactive_power_C(self):
423 | reading = self._read32Register(R.QmeanC, R.PmeanCLSB)
424 | return round(reading * 0.00032, 3)
425 | #####################################################################
426 |
427 | @property
428 | def apparent_power_A(self):
429 | reading = self._read32Register(R.SmeanA, R.SmeanALSB)
430 | return round(reading * 0.00032, 3)
431 |
432 | #####################################################################
433 |
434 | @property
435 | def apparent_power_B(self):
436 | reading = self._read32Register(R.SmeanB, R.SmeanBLSB)
437 | return round(reading * 0.00032, 3)
438 |
439 | #####################################################################
440 |
441 | @property
442 | def apparent_power_C(self):
443 | reading = self._read32Register(R.SmeanC, R.SmeanCLSB)
444 | return round(reading * 0.00032, 3)
445 |
446 | #####################################################################
447 |
448 | @property
449 | def power_factor_A(self):
450 | reading = self._spi_rw(SPI_READ, R.PFmeanA, 0xFFFF)
451 | return reading / 1000.0
452 |
453 | #####################################################################
454 |
455 | @property
456 | def power_factor_B(self):
457 | reading = self._spi_rw(SPI_READ, R.PFmeanB, 0xFFFF)
458 | return reading / 1000.0
459 |
460 | #####################################################################
461 |
462 | @property
463 | def power_factor_C(self):
464 | reading = self._spi_rw(SPI_READ, R.PFmeanC, 0xFFFF)
465 | return reading / 1000.0
466 |
467 |
468 | #####################################################################
469 | def get_all_readings(self):
470 |
471 | self.readings_ray[0]["line_volt"] = self.line_voltageA
472 | self.readings_ray[1]["line_volt"] = self.line_voltageB
473 | self.readings_ray[2]["line_volt"] = self.line_voltageC
474 | self.readings_ray[0]["line_current"] = self.line_currentA
475 | self.readings_ray[1]["line_current"] = self.line_currentB
476 | self.readings_ray[2]["line_current"] = self.line_currentC
477 | self.readings_ray[0]["power_active"] = self.active_power_A
478 | self.readings_ray[1]["power_active"] = self.active_power_B
479 | self.readings_ray[2]["power_active"] = self.active_power_C
480 | self.readings_ray[0]["power_reactive"] = self.reactive_power_A
481 | self.readings_ray[1]["power_reactive"] = self.reactive_power_B
482 | self.readings_ray[2]["power_reactive"] = self.reactive_power_C
483 | self.readings_ray[0]["power_apparent"] = self.apparent_power_A
484 | self.readings_ray[1]["power_apparent"] = self.apparent_power_B
485 | self.readings_ray[2]["power_apparent"] = self.apparent_power_C
486 | self.readings_ray[0]["power_factor"] = self.power_factor_A
487 | self.readings_ray[1]["power_factor"] = self.power_factor_B
488 | self.readings_ray[2]["power_factor"] = self.power_factor_C
489 |
490 | return None
491 |
492 |
493 |
494 | ######################################################
495 | # Return the value that is stored at the address.
496 | ######################################################
497 |
498 | def read(self, address):
499 | two_byte_buf = bytearray(2)
500 | results_buf = bytearray(2)
501 | # Let atm90e32 know want to read the register.
502 | address |= 1 << 15
503 | struct.pack_into('>H', two_byte_buf, 0, address)
504 | with self._device as spi:
505 | # send address w/ read request to the atm90e32
506 | spi.write(two_byte_buf)
507 | # Get the unsigned short register values sent from the atm90e32
508 | spi.readinto(results_buf)
509 | return struct.unpack('>H', results_buf)[0]
510 | ######################################################
511 | # Write the val to the address.
512 | ######################################################
513 |
514 | def write(self, address, val):
515 | # pack the address into a the bytearray. It is an unsigned short(H)
516 | # that needs to be in MSB(>)
517 |
518 | four_byte_buf = bytearray(4)
519 | struct.pack_into('>H', four_byte_buf, 0, address)
520 | struct.pack_into('>H', four_byte_buf, 2, val)
521 | with self._device as spi:
522 | spi.write(four_byte_buf)
523 | ######################################################
524 | # Verify the right.
525 | ######################################################
526 |
527 | def verify(self, address, val):
528 | if address == R.SoftReset:
529 | return True
530 | result = self.read(R.LastSPIData)
531 | return True if result == val else False
532 |
533 | ######################################################
534 | # read or write to spi.
535 | # Writes are verified. If verification shows our
536 | # write didn't work, we raise an exception.
537 | ######################################################
538 |
539 | def _spi_rw(self, rw, address, val):
540 | """read or write to spi.
541 | Writes are verified. If verification shows our
542 | write didn't work, we raise an exception.
543 |
544 | :param rw: 1 if read, 0 if write.
545 | :param address: One of the registers identified
546 | in atm90_e32_registers.py.
547 | :param val: The two byte hex value to write to the
548 | register (assumes rw = 0).
549 | """
550 |
551 | if(rw): # read
552 | return self.read(address)
553 | else:
554 | self.write(address, val)
555 | if not self.verify(address, val):
556 | e = f'EXCEPTION: Write to address 0x{address:02x} Failed.'
557 | raise RegistryWriteError(e)
558 | ######################################################
559 |
560 | def _round_number(self, f_num):
561 | if f_num - math.floor(f_num) < 0.5:
562 | return math.floor(f_num)
563 | return math.ceil(f_num)
564 |
565 | #########################################################################
566 | def _read32Register(self, regh_addr, regl_addr):
567 | val_h = self._spi_rw(SPI_READ, regh_addr, 0xFFFF)
568 | val_l = self._spi_rw(SPI_READ, regl_addr, 0xFFFF)
569 | val = val_h << 16
570 | val |= val_l # concatenate the 2 registers to make 1 32 bit number
571 | if ((val & 0x80000000) != 0): # if val is negative,
572 | val = (0xFFFFFFFF - val) + 1 # 2s compliment + 1
573 | return (val)
574 |
575 |
576 | if __name__ == "__main__":
577 | print('hello')
578 | sys.exit()
579 |
--------------------------------------------------------------------------------