Added note about cgroup2 protection
[ampy.git] / run.py
1 #!/usr/bin/python3
2 """
3 PyAudio example: Record a few seconds of audio and save to a WAVE
4 file.
5 """
6
7 import json
8 import os
9 import struct
10 import time
11
12 import pyaudio
13
14 PARAMFILE = '/var/lib/ampy/params.json'
15 FORMAT = pyaudio.paInt32  # Format
16 STRUCTFORMAT = 'i'  # Format for python struct module
17 RATE = 44100
18 RECORD_SECONDS = 0.5
19
20 VOLUME_CONSTANT = 132360.98315789475
21
22 # https://www.actutem.com/valeur-crete-moyenne-et-efficace-dune-tension-ac/
23 # math.pi/math.sqrt(2)/2 = 1.1107207345395915
24
25 # Pour 30 A à 230V ( 6900 W )
26 # abs(Sndmax) = 2147483520
27 # Sndavg = 2147483520/math.pi = 683565234.8327662
28 # Sndeff = 683565234.8327662 * 1.1107207345395915 = 759250079.7391784
29
30 # https://fr.wikipedia.org/wiki/Compteur_%C3%A9lectrique
31 # Mon compteur tourne à une vitesse proportionnelle à la puissance instantanée
32
33 # https://fr.wikipedia.org/wiki/%C3%89lectricit%C3%A9_domestique#Tension
34 # En france, ERDF fourni 230V EFFICACES (=> 207.0727527161344 moyenne)
35
36 # raw average: 636207384.4738322 - min: -2147483648 - max: 2147483392
37 # average 4806.6W
38
39
40 def read_params_file():
41     with open(PARAMFILE) as f:
42         params = json.loads(f.read())
43     return params
44
45
46 def loop(optrecord, optstats):
47     try:
48         params = read_params_file()
49         alsadevice = params['alsadevice']
50     except (FileNotFoundError, json.decoder.JSONDecodeError, KeyError):
51         print("Error reading the parameters. Please run device.py")
52
53     p = pyaudio.PyAudio()
54     print("PyAudio opened")
55
56     print("Ampy Selected input device #{}: {}".format(
57         alsadevice, p.get_device_info_by_index(alsadevice)['name']))
58
59     NSAMPLE = int(RATE * RECORD_SECONDS)
60     stream = p.open(
61             format=FORMAT,
62             channels=1,  # Our ampmeter always returns 0 on the second channel
63             rate=RATE,
64             input=True,
65             input_device_index=alsadevice)
66
67     structformat = '<' + STRUCTFORMAT * NSAMPLE
68     while(True):
69         try:
70             total = 0
71             minvalue = 0
72             maxvalue = 0
73             data = stream.read(NSAMPLE, exception_on_overflow=False)
74             values = struct.unpack(structformat, data)
75             # print(values)
76             # for x in range(100):
77             #    print('**{:04x}**{}**'.format(values[x], values[x]))
78             for value in values:
79                 if value > 0:
80                     total += value
81                 else:
82                     total -= value
83                 if value > maxvalue:
84                     maxvalue = value
85                 if value < minvalue:
86                     minvalue = value
87             avg = float(total) / NSAMPLE
88             if optstats:
89                 print("raw average: {} - min: {} - max: {}".format(
90                     avg, minvalue, maxvalue))
91             watts = avg / VOLUME_CONSTANT
92             print("average {:.1f}W  ".format(watts), end='\r')
93             if optrecord:
94                 os.system(
95                         "rrdtool update --daemon /var/run/rrdcached.sock"
96                         " /var/lib/rrdcached/db/power.rrd {}:{}".format(
97                             time.time(), watts))
98         except KeyboardInterrupt:
99             print("Received KeyboardInterrupt: exiting")
100             break
101
102     stream.stop_stream()
103     stream.close()
104     p.terminate()
105
106
107 def main():
108     import argparse
109
110     parser = argparse.ArgumentParser(description='Ammeter processing')
111     parser.add_argument(
112             '--stats',
113             action='store_true',
114             default=False,
115             help='Dump recording level stats for every chunk',
116             )
117     parser.add_argument(
118             '--norecord',
119             action='store_false',
120             default=True,
121             help='Disable recording in RRD file',
122             dest='record',
123             )
124     args = parser.parse_args()
125
126     if not args.record:
127         print('Recording disabled.')
128
129     loop(optrecord=args.record, optstats=args.stats)
130
131
132 if __name__ == '__main__':
133     main()