Skip to content
Snippets Groups Projects
Commit 4395dc33 authored by Florian Oppermann's avatar Florian Oppermann
Browse files

add current verion of powercalc


Signed-off-by: default avatarflorian.oppermann <florian.oppermann@eonerc.rwth-aachen.de>
parent 7060daa8
No related branches found
No related tags found
1 merge request!2increment version
......@@ -31,7 +31,7 @@ $ kubectl --namespace dsotp get pods
Login to the pod
```bashh
```bash
# kubectl --namespace dsotp exec -i -t [pod name] /bin/sh
```
......
......@@ -6,5 +6,6 @@ require github.com/eclipse/paho.mqtt.golang v1.3.5
require (
github.com/gorilla/websocket v1.4.2 // indirect
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 // indirect
)
......@@ -3,6 +3,8 @@ github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+J
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg=
golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U=
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
......
......@@ -6,10 +6,14 @@ import (
"log"
"math"
"os"
"os/signal"
"syscall"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
_ "embed"
mqtt "github.com/eclipse/paho.mqtt.golang"
)
......@@ -30,6 +34,7 @@ type reading struct {
Measurand string `json:"measurand"`
Phase string `json:"phase"`
Data float64 `json:"data"`
// Unit string `json:unit` // maybe later
}
type PmuReading struct {
......@@ -39,51 +44,141 @@ type PmuReading struct {
}
/*
{
"device": "device1",
"timestamp": "TIMESTAMP",
"readings": [
{
"component": "BUS1",
"measurand": "voltmagnitude",
"phase": "A",
"data": "RANDOM",
"RND-base": 230.0,
"RND-dev": 1.0,
"RND-digits": 2
},
{
"component": "BUS1",
"measurand": "voltmagnitude",
"phase": "B",
"data": "RANDOM"
},
{
"component": "BUS1",
"measurand": "voltmagnitude",
"phase": "C",
"data": "RANDOM"
}
]
}
{
"device": "pmu_avacon1",
"readings": [
{
"component": "IL3",
"data": 54.804229736328125,
"measurand": "magnitude",
"phase": "P3"
},
{
"component": "IL3",
"data": -0.5466897487640381,
"measurand": "angle",
"phase": "P3"
},
{
"component": "IL3",
"data": 49.97727966308594,
"measurand": "frequency",
"phase": "P3"
},
{
"component": "UL3",
"data": 226.3511962890625,
"measurand": "magnitude",
"phase": "P3"
},
{
"component": "UL3",
"data": -0.7485408782958984,
"measurand": "angle",
"phase": "P3"
},
{
"component": "UL3",
"data": 49.99614715576172,
"measurand": "frequency",
"phase": "P3"
},
{
"component": "IL2",
"data": 84.67153930664063,
"measurand": "magnitude",
"phase": "P2"
},
{
"component": "IL2",
"data": -2.883809804916382,
"measurand": "angle",
"phase": "P2"
},
{
"component": "IL2",
"data": 50.0085334777832,
"measurand": "frequency",
"phase": "P2"
},
{
"component": "UL2",
"data": 226.46560668945313,
"measurand": "magnitude",
"phase": "P2"
},
{
"component": "UL2",
"data": -2.8399910926818848,
"measurand": "angle",
"phase": "P2"
},
{
"component": "UL2",
"data": 49.99687576293945,
"measurand": "frequency",
"phase": "P2"
},
{
"component": "IL1",
"data": 51.397918701171875,
"measurand": "magnitude",
"phase": "P1"
},
{
"component": "IL1",
"data": 1.2730059623718262,
"measurand": "angle",
"phase": "P1"
},
{
"component": "IL1",
"data": 50.02635192871094,
"measurand": "frequency",
"phase": "P1"
},
{
"component": "UL1",
"data": 226.29930114746094,
"measurand": "magnitude",
"phase": "P1"
},
{
"component": "UL1",
"data": 1.3454995155334473,
"measurand": "angle",
"phase": "P1"
},
{
"component": "UL1",
"data": 49.99594497680664,
"measurand": "frequency",
"phase": "P1"
}
],
"timestamp": "2022-12-19T14:38:59.739923+00:00"
}
*/
const unit_mulitplier = 0.001 //Transform from W to KW -> devide by 1000
func calcPower(volt float64, curr float64) float64 {
return volt * curr
}
func ActivePowerSinglePhase(pp *PhasePower) int {
activePower := pp.Current.Magnitude * pp.Voltage.Magnitude * math.Cos(pp.Current.Angle-pp.Voltage.Angle)
func ActivePowerSinglePhase(pp *PhasePower, currentMagnitudeMultiplier float64, currentAngleShift float64) int {
activePower := unit_mulitplier * currentMagnitudeMultiplier * pp.Current.Magnitude * pp.Voltage.Magnitude * math.Cos(pp.Current.Angle-pp.Voltage.Angle+currentAngleShift)
pp.ActivePower = activePower
return 0
}
func ReactivePowerSinglePhase(pp *PhasePower) int {
reactivePower := pp.Current.Magnitude * pp.Voltage.Magnitude * math.Sin(pp.Current.Angle-pp.Voltage.Angle)
func ReactivePowerSinglePhase(pp *PhasePower, currentMagnitudeMultiplier float64, currentAngleShift float64) int {
reactivePower := unit_mulitplier * currentMagnitudeMultiplier * pp.Current.Magnitude * pp.Voltage.Magnitude * math.Sin(pp.Current.Angle-pp.Voltage.Angle+currentAngleShift)
pp.ReactivePower = reactivePower
return 0
}
func ApparentPowerSinglePhase(pp *PhasePower, currentMagnitudeMultiplier float64) int {
apparentPower := unit_mulitplier * currentMagnitudeMultiplier * pp.Current.Magnitude * pp.Voltage.Magnitude
pp.ApparentPower = apparentPower
return 0
}
type Phasor struct {
Magnitude float64
Angle float64
......@@ -96,25 +191,64 @@ type PhasePower struct {
Current Phasor
ActivePower float64
ReactivePower float64
ApparentPower float64
}
func appendPowerReadings(phase PhasePower, pmuData *PmuReading) {
var ret reading
ret.Measurand = "activepower"
ret.Data = phase.ActivePower
ret.Phase = phase.Phase
ret.Component = phase.Component
var ret2 reading
ret2.Measurand = "reactivepower"
ret2.Data = phase.ReactivePower
ret2.Phase = phase.Phase
ret2.Component = phase.Component
(*pmuData).Readings = append((*pmuData).Readings, ret, ret2)
var active_power reading
active_power.Measurand = "magnitude"
active_power.Data = phase.ActivePower
active_power.Phase = phase.Phase
active_power.Component = "P" + phase.Component
var reactive_power reading
reactive_power.Measurand = "magnitude"
reactive_power.Data = phase.ReactivePower
reactive_power.Phase = phase.Phase
reactive_power.Component = "Q" + phase.Component
var apparent_power reading
apparent_power.Measurand = "magnitude"
apparent_power.Data = math.Sqrt(phase.ReactivePower*phase.ReactivePower + phase.ActivePower*phase.ActivePower)
apparent_power.Phase = phase.Phase
apparent_power.Component = "S" + phase.Component
(*pmuData).Readings = append((*pmuData).Readings, active_power, reactive_power, apparent_power)
// bytes, _ := json.MarshalIndent(*pmuData, "", "\t")
// fmt.Println(string(bytes))
}
// type reading struct {
// Component string `json:"component"`
// Measurand string `json:"measurand"`
// Phase string `json:"phase"`
// Data float64 `json:"data"`
// }
func appendTotalReadings(phases []PhasePower, pmuData *PmuReading) {
var total_voltage float64 = 0.0
var total_current float64 = 0.0
var total_active_power float64 = 0.0
var total_reactive_power float64 = 0.0
var total_apparent_power float64 = 0.0
for _, phase := range phases {
total_voltage += phase.Voltage.Magnitude
total_current += phase.Current.Magnitude
total_active_power += phase.ActivePower
total_reactive_power += phase.ReactivePower
total_apparent_power += phase.ApparentPower
}
var total_voltage_reading = reading{"ULT", "magnitude", "total", total_voltage}
var total_current_reading = reading{"ILT", "magnitude", "total", total_current}
var total_active_power_reading = reading{"PT", "magnitude", "total", total_active_power}
var total_reactive_power_reading = reading{"QT", "magnitude", "total", total_reactive_power}
var total_apparent_power_reading = reading{"ST", "magnitude", "total", total_apparent_power}
(*pmuData).Readings = append((*pmuData).Readings, total_voltage_reading, total_current_reading, total_active_power_reading, total_reactive_power_reading, total_apparent_power_reading)
// bytes, _ := json.MarshalIndent(*pmuData, "", "\t")
// fmt.Println(string(bytes))
}
func extractPhase(data PmuReading, phName string) PhasePower {
......@@ -122,17 +256,25 @@ func extractPhase(data PmuReading, phName string) PhasePower {
for _, r := range data.Readings {
if r.Phase == phName {
switch r.Measurand {
case "voltmagnitude":
phaseB.Voltage.Magnitude = r.Data
case "currentmagnitude":
phaseB.Current.Magnitude = r.Data
case "voltangle":
phaseB.Voltage.Angle = r.Data
case "currentangle":
phaseB.Current.Angle = r.Data
// name Component after phase, real syntax has different component names for U and I
var comp = strings.Replace(phName, "P", "", 1)
if strings.Contains(r.Component, "I") {
switch r.Measurand {
case "magnitude":
phaseB.Current.Magnitude = r.Data
case "angle":
phaseB.Current.Angle = r.Data
}
} else if strings.Contains(r.Component, "U") {
switch r.Measurand {
case "magnitude":
phaseB.Voltage.Magnitude = r.Data
case "angle":
phaseB.Voltage.Angle = r.Data
}
}
phaseB.Component = r.Component
phaseB.Component = comp
phaseB.Phase = r.Phase
//fmt.Println(phaseB)
}
......@@ -141,97 +283,124 @@ func extractPhase(data PmuReading, phName string) PhasePower {
return phaseB
}
func publishPower(client mqtt.Client, pmuData PmuReading) {
func publishPower(client mqtt.Client, pmuData PmuReading, outTopicSuffix string) {
data, err := json.MarshalIndent(pmuData, "", " ")
if err != nil {
panic(err)
}
token := client.Publish(outTopic, 0, false, data)
token := client.Publish(outTopic+outTopicSuffix, 0, false, data)
token.Wait()
}
func (data *PmuReading) AddPowerAndTotal() {
var phases = []string{"P1", "P2", "P3"}
var phasePowerArray [3]PhasePower
for idx, elem := range phases {
phasePowerArray[idx] = extractPhase(*data, elem)
ActivePowerSinglePhase(&phasePowerArray[idx], currentMagnitudeMultiplier, currentAngleShift)
ReactivePowerSinglePhase(&phasePowerArray[idx], currentMagnitudeMultiplier, currentAngleShift)
ApparentPowerSinglePhase(&phasePowerArray[idx], currentMagnitudeMultiplier)
appendPowerReadings(phasePowerArray[idx], data)
}
appendTotalReadings(phasePowerArray[:], data)
}
var pmuData mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
//fmt.Printf("#########Received message: %s from topic: %s\n", msg.Payload(), msg.Topic())
outTopicPostfix := strings.TrimPrefix(msg.Topic(), inTopic)
start := time.Now()
var data PmuReading
err := json.Unmarshal(msg.Payload(), &data)
if err != nil {
panic(err)
}
var phases = []string{"A", "B", "C"}
var phasePowerArray [3]PhasePower
for idx, elem := range phases {
phasePowerArray[idx] = extractPhase(data, elem)
ActivePowerSinglePhase(&phasePowerArray[idx])
ReactivePowerSinglePhase(&phasePowerArray[idx])
appendPowerReadings(phasePowerArray[idx], &data)
}
publishPower(client, data)
data.AddPowerAndTotal()
publishPower(client, data, outTopicPostfix)
timeElapsed := time.Since(start)
fmt.Printf("The processing took %s\n", timeElapsed)
}
func mqttConnect() mqtt.Client {
var broker = getEnv("MQTT_BROKER", "192.168.0.5")
var broker = getEnv("MQTT_BROKER", "localhost")
var port = getEnv("MQTT_PORT", "1883")
var podIP = getEnv("KUBERNETES_POD_IP", "127.0.0.1")
opts := mqtt.NewClientOptions()
opts.AddBroker(fmt.Sprintf("mqtt://%s:%s", broker, port))
opts.SetClientID("go_mqtt_client")
opts.SetUsername(getEnv("MQTT_USER","admin"))
opts.SetPassword(getEnv("MQTT_PSSWD","admin"))
opts.SetClientID("powercalc_" + strings.ReplaceAll(podIP, ".", "_"))
opts.SetUsername(getEnv("MQTT_USER", "admin"))
opts.SetPassword(getEnv("MQTT_PSSWD", "admin"))
opts.SetDefaultPublishHandler(messagePubHandler)
opts.OnConnect = connectHandler
opts.OnConnectionLost = connectLostHandler
opts.SetAutoReconnect(true)
opts.SetMaxReconnectInterval(10 * time.Second)
opts.SetReconnectingHandler(func(c mqtt.Client, options *mqtt.ClientOptions) {
fmt.Println("...... mqtt reconnecting ......")
})
client := mqtt.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
return client
}
// extended getEnv that also takes a default value if env is not set
func getEnv(key, defaultValue string) string {
value, exists := os.LookupEnv(key)
if !exists {
value = defaultValue
}
return value
value, exists := os.LookupEnv(key)
if !exists {
value = defaultValue
}
return value
}
var outTopic = "foo"
var inTopic = getEnv("MQTT_INPUT_TOPIC", "/dev/pmu")
var outTopic = getEnv("MQTT_OUTPUT_TOPIC", "/dev/power")
var currentMagnitudeMultiplier, errCMM = strconv.ParseFloat(getEnv("CURRENT_MAGNITUDE_MULTIPLIER", "1.059"), 64)
var currentAngleShift, errCAS = strconv.ParseFloat(getEnv("CURRENT_ANGLE_SHIFT", "-0.078"), 64)
//go:generate bash get_version.sh
//go:embed version.txt
var version string
func main() {
// very simple info flag for kubernetes probes
if len(os.Args) > 1 && os.Args[1] == "info" {
fmt.Println("powercalc version", version)
return
}
mqtt.ERROR = log.New(os.Stdout, "[ERROR] ", 0)
// mqtt.CRITICAL = log.New(os.Stdout, "[CRIT] ", 0)
// mqtt.WARN = log.New(os.Stdout, "[WARN] ", 0)
// mqtt.DEBUG = log.New(os.Stdout, "[DEBUG] ", 0)
inTopic := getEnv("MQTT_INPUT_TOPIC", "/dev/pmu")
outTopic = getEnv("MQTT_OUTPUT_TOPIC", "/dev/power")
// inTopic := getEnv("MQTT_INPUT_TOPIC", "/dev/pmu")
// outTopic = getEnv("MQTT_OUTPUT_TOPIC", "/dev/power")
if errCMM != nil {
panic(errCMM)
}
if errCAS != nil {
panic(errCAS)
}
client := mqttConnect()
client.AddRoute(inTopic, pmuData)
client.AddRoute(inTopic+"/#", pmuData)
token := client.Subscribe(inTopic, 1, nil)
token := client.Subscribe(inTopic+"/#", 1, nil)
token.Wait()
fmt.Printf("Subscribed to input topic %s\n", inTopic)
fmt.Printf("Publishing to output topic %s\n", outTopic)
// wait for graceful termination
sigChan := make(chan os.Signal, 1)
// wait for graceful termination
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
<-sigChan
client.Disconnect(250)
fmt.Println("Sucessfully terminated...")
fmt.Println("Sucessfully terminated...")
}
package main
import (
"encoding/json"
"fmt"
"testing"
"golang.org/x/exp/slices"
)
func getPmuData() PmuReading {
var jsonData = "{\"device\":\"pmu_avacon1\",\"readings\":[{\"component\":\"IL3\",\"data\":54.804229736328125,\"measurand\":\"magnitude\",\"phase\":\"P3\"},{\"component\":\"IL3\",\"data\":-0.5466897487640381,\"measurand\":\"angle\",\"phase\":\"P3\"},{\"component\":\"IL3\",\"data\":49.97727966308594,\"measurand\":\"frequency\",\"phase\":\"P3\"},{\"component\":\"UL3\",\"data\":226.3511962890625,\"measurand\":\"magnitude\",\"phase\":\"P3\"},{\"component\":\"UL3\",\"data\":-0.7485408782958984,\"measurand\":\"angle\",\"phase\":\"P3\"},{\"component\":\"UL3\",\"data\":49.99614715576172,\"measurand\":\"frequency\",\"phase\":\"P3\"},{\"component\":\"IL2\",\"data\":84.67153930664063,\"measurand\":\"magnitude\",\"phase\":\"P2\"},{\"component\":\"IL2\",\"data\":-2.883809804916382,\"measurand\":\"angle\",\"phase\":\"P2\"},{\"component\":\"IL2\",\"data\":50.0085334777832,\"measurand\":\"frequency\",\"phase\":\"P2\"},{\"component\":\"UL2\",\"data\":226.46560668945313,\"measurand\":\"magnitude\",\"phase\":\"P2\"},{\"component\":\"UL2\",\"data\":-2.8399910926818848,\"measurand\":\"angle\",\"phase\":\"P2\"},{\"component\":\"UL2\",\"data\":49.99687576293945,\"measurand\":\"frequency\",\"phase\":\"P2\"},{\"component\":\"IL1\",\"data\":51.397918701171875,\"measurand\":\"magnitude\",\"phase\":\"P1\"},{\"component\":\"IL1\",\"data\":1.2730059623718262,\"measurand\":\"angle\",\"phase\":\"P1\"},{\"component\":\"IL1\",\"data\":50.02635192871094,\"measurand\":\"frequency\",\"phase\":\"P1\"},{\"component\":\"UL1\",\"data\":226.29930114746094,\"measurand\":\"magnitude\",\"phase\":\"P1\"},{\"component\":\"UL1\",\"data\":1.3454995155334473,\"measurand\":\"angle\",\"phase\":\"P1\"},{\"component\":\"UL1\",\"data\":49.99594497680664,\"measurand\":\"frequency\",\"phase\":\"P1\"}],\"timestamp\":\"2022-12-19T14:38:59.739923+00:00\"}"
var data PmuReading
err := json.Unmarshal([]byte(jsonData), &data)
if err != nil {
panic(err)
}
return data
}
func TestExtractPhase(t *testing.T) {
var data = getPmuData()
var phasePower = extractPhase(data, "P1")
if phasePower.Current.Magnitude != 51.397918701171875 {
t.Errorf("incorrect current magnitude epected 51.397918701171875 but read %v", phasePower.Current.Magnitude)
}
if phasePower.Current.Angle != 1.2730059623718262 {
t.Errorf("incorrect current angle epected 1.2730059623718262 but read %v", phasePower.Current.Angle)
}
if phasePower.Voltage.Magnitude != 226.29930114746094 {
t.Errorf("incorrect voltage magnitude epected 226.29930114746094 but read %v", phasePower.Voltage.Magnitude)
}
if phasePower.Voltage.Angle != 1.3454995155334473 {
t.Errorf("incorrect current angle epected 1.3454995155334473 but read %v", phasePower.Voltage.Angle)
}
}
func TestAppendPowerReadings(t *testing.T) {
var data = getPmuData()
var phase = "P1"
var phasePower PhasePower
phasePower = extractPhase(data, phase)
ActivePowerSinglePhase(&phasePower, currentMagnitudeMultiplier, currentAngleShift)
ReactivePowerSinglePhase(&phasePower, currentMagnitudeMultiplier, currentAngleShift)
ApparentPowerSinglePhase(&phasePower, currentMagnitudeMultiplier)
appendPowerReadings(phasePower, &data)
fmt.Printf("%+v\n", data.Readings)
var expected_apparent_power = reading{Component: "S1", Measurand: "magnitude", Phase: "P1", Data: 12.317560554377252}
var expected_reactive_power = reading{Component: "Q1", Measurand: "magnitude", Phase: "P1", Data: -1.84672412761097}
var expected_active_power = reading{Component: "P1", Measurand: "magnitude", Phase: "P1", Data: 12.178337653688615}
if !slices.Contains(data.Readings, expected_apparent_power) {
t.Error("final Readings did not contain apparent power or apparent power was invalid.")
}
if !slices.Contains(data.Readings, expected_reactive_power) {
t.Error("final Readings did not contain reactive power or reactive power was invalid.")
}
if !slices.Contains(data.Readings, expected_active_power) {
t.Error("final Readings did not contain active power or active power was invalid.")
}
}
func TestAppendTotals(t *testing.T) {
var data = getPmuData()
data.AddPowerAndTotal()
fmt.Printf("%+v\n", data.Readings)
var expected_voltage = reading{Component: "ULT", Measurand: "magnitude", Phase: "total", Data: 679.1161041259766}
var expected_current = reading{Component: "ILT", Measurand: "magnitude", Phase: "total", Data: 190.87368774414062}
var expected_apparent_power = reading{Component: "ST", Measurand: "magnitude", Phase: "total", Data: 45.76098650967866}
var expected_reactive_power = reading{Component: "QT", Measurand: "magnitude", Phase: "total", Data: -2.6914621371904106}
var expected_active_power = reading{Component: "PT", Measurand: "magnitude", Phase: "total", Data: 45.370652065630495}
if !slices.Contains(data.Readings, expected_voltage) {
t.Error("final Readings did not contain total voltage or total voltage was invalid.")
}
if !slices.Contains(data.Readings, expected_current) {
t.Error("final Readings did not contain total current or total current was invalid.")
}
if !slices.Contains(data.Readings, expected_apparent_power) {
t.Error("final Readings did not contain total apparent power or total apparent power was invalid.")
}
if !slices.Contains(data.Readings, expected_reactive_power) {
t.Error("final Readings did not contain total reactive power or total reactive power was invalid.")
}
if !slices.Contains(data.Readings, expected_active_power) {
t.Error("final Readings did not contain total reactive power or total active power was invalid.")
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment