From 49e3ce70f4dd856fa5c5ef38ecdcb03be3e8699f Mon Sep 17 00:00:00 2001 From: Sina Blattmann Date: Fri, 14 Apr 2023 07:52:37 +0200 Subject: [PATCH 1/6] add yup dependency for validation, fix small bugs --- typescript/Frontend/package-lock.json | 73 ++++++++++- typescript/Frontend/package.json | 3 +- typescript/Frontend/src/App.tsx | 8 +- typescript/Frontend/src/Login.tsx | 43 +++--- typescript/Frontend/src/ResetPassword.tsx | 122 +++++++++--------- .../Installations/Log/ScalarGraph.tsx | 18 ++- 6 files changed, 178 insertions(+), 89 deletions(-) diff --git a/typescript/Frontend/package-lock.json b/typescript/Frontend/package-lock.json index a2c709fef..c02df11a5 100644 --- a/typescript/Frontend/package-lock.json +++ b/typescript/Frontend/package-lock.json @@ -44,7 +44,8 @@ "style-loader": "^3.3.1", "testcafe": "^2.4.0", "typescript": "^4.9.5", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "yup": "^1.1.0" }, "devDependencies": { "@formatjs/cli": "^6.0.3", @@ -18173,6 +18174,11 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/property-expr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", + "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" + }, "node_modules/protocol-buffers-schema": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", @@ -21580,6 +21586,11 @@ "node": ">=0.10.0" } }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, "node_modules/tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", @@ -21691,6 +21702,11 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "node_modules/tough-cookie": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", @@ -23353,6 +23369,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yup": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.1.0.tgz", + "integrity": "sha512-CtpEHWiIMwWJBJ+zX8xWImXXdvJ10X/sKkYYTXfVocHj087e9zhP0GNkU7HlXBBI4T9BtHQxs8n2jLzmo/X8Yg==", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/yup/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zustand": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.3.tgz", @@ -36715,6 +36753,11 @@ } } }, + "property-expr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", + "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" + }, "protocol-buffers-schema": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", @@ -39376,6 +39419,11 @@ "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz", "integrity": "sha512-pqqJOi1rF5zNs/ps4vmbE4SFCrM4iR7LW+GHAsHqO/EumqbIWceioevYLM5xZRgQSH6gFgL9J/uB7EcJhQ9niQ==" }, + "tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, "tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", @@ -39472,6 +39520,11 @@ } } }, + "toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "tough-cookie": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", @@ -40789,6 +40842,24 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, + "yup": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.1.0.tgz", + "integrity": "sha512-CtpEHWiIMwWJBJ+zX8xWImXXdvJ10X/sKkYYTXfVocHj087e9zhP0GNkU7HlXBBI4T9BtHQxs8n2jLzmo/X8Yg==", + "requires": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + }, + "dependencies": { + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==" + } + } + }, "zustand": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.3.tgz", diff --git a/typescript/Frontend/package.json b/typescript/Frontend/package.json index c818ec3d8..7f6499cb8 100644 --- a/typescript/Frontend/package.json +++ b/typescript/Frontend/package.json @@ -39,7 +39,8 @@ "style-loader": "^3.3.1", "testcafe": "^2.4.0", "typescript": "^4.9.5", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "yup": "^1.1.0" }, "scripts": { "start": "react-scripts start", diff --git a/typescript/Frontend/src/App.tsx b/typescript/Frontend/src/App.tsx index d4812b767..586cf423d 100644 --- a/typescript/Frontend/src/App.tsx +++ b/typescript/Frontend/src/App.tsx @@ -13,6 +13,7 @@ import Users from "./components/Users/Users"; import NavigationButtons from "./components/Layout/NavigationButtons"; import InstallationPage from "./components/Installations/InstallationPage"; import { UserContext } from "./components/Context/UserContextProvider"; +import ResetPassword from "./ResetPassword"; const App = () => { const { token, setToken, removeToken } = useToken(); @@ -30,10 +31,9 @@ const App = () => { if (!token) { return ; } - - /* TODO create reset page if (token && currentUser?.mustResetPassword) { - return
Reset page
; - } */ + if (token && currentUser?.mustResetPassword) { + return ; + } return ( { const handleSubmit = () => { setLoading(true); - loginUser(username, password).then(({ data }) => { - // TODO change this if they return err codes from backend - if (data && data.token) { - verifyToken(data.token) - .then(() => { - setToken(data.token); - setCurrentUser(data.user); - setLoading(false); - setLanguage(data.user.language); - }) - .catch((err) => { - setError(err); - setLoading(false); - }); - } - setError(data); - setLoading(false); - }); + loginUser(username, password) + .then(({ data }) => { + // TODO change this if they return err codes from backend + if (data && data.token) { + verifyToken(data.token) + .then(() => { + setToken(data.token); + setCurrentUser(data.user); + setLoading(false); + setLanguage(data.user.language); + }) + .catch((err) => { + setError(err); + setLoading(false); + }); + } + setError(data); + setLoading(false); + }) + .catch((err) => { + setError(err); + setLoading(false); + }); }; return ( - + { - return axiosConfigWithoutToken.post("/Login", null, { - params: { username, password }, - }); -}; - -interface I_LoginProps { - setToken: (value: string) => void; - setLanguage: (value: string) => void; -} - -const ResetPassword = ({ setToken, setLanguage }: I_LoginProps) => { - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); +const ResetPassword = () => { const [loading, setLoading] = useState(false); - const [error, setError] = useState(); + const [error, setError] = useState(); const { setCurrentUser } = useContext(UserContext); - const verifyToken = async (token: string) => { - axiosConfigWithoutToken.get("/GetAllInstallations", { - params: { authToken: token }, - }); - }; + const validationSchema = Yup.object().shape({ + password: Yup.string().required("*Password is required"), + verifyPassword: Yup.string() + .oneOf([Yup.ref("password")], "Passwords must match") + .required(), + }); - const handleSubmit = () => { - setLoading(true); - loginUser(username, password).then(({ data }) => { - // TODO change this if they return err codes from backend - if (data && data.token) { - verifyToken(data.token) - .then(() => { - setToken(data.token); - setCurrentUser(data.user); - setLoading(false); - setLanguage(data.user.language); - }) - .catch((err) => { - setError(err); - setLoading(false); - }); - } - setError(data); - setLoading(false); - }); - }; + const formik = useFormik({ + initialValues: { + password: "", + verifyPassword: "", + }, + onSubmit: (formikValues) => { + setLoading(true); + axiosConfig + .put("/UpdatePassword", undefined, { + params: { newPassword: formikValues.verifyPassword }, + }) + .then((res) => { + setCurrentUser(res.data); + setLoading(false); + }) + .catch((err) => setError(err)); + }, + validationSchema, + }); return ( - - setUsername(e.target.value)} - /> - {error && Incorrect username or password} - - - Login - - - {loading && } + + + Change password + +
+ + + {formik.errors.verifyPassword && formik.touched.verifyPassword && ( + {formik.errors.verifyPassword} + )} + {error && {error.name}} + + + Submit + + + {loading && } +
); }; diff --git a/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx b/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx index 414aab9b2..7a6fa4b87 100644 --- a/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx +++ b/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx @@ -1,10 +1,5 @@ import Plot from "react-plotly.js"; -import exampleLogData, { - Datum, - TimeSeries, - timeSeries, -} from "../ExampleLogData"; -import { I_GraphData } from "../../../util/types"; +import { TimeSeries, timeSeries } from "../ExampleLogData"; const ScalarGraph = () => { const transformToGraphData = (timeStampData: TimeSeries) => { @@ -70,6 +65,17 @@ const ScalarGraph = () => { }, ]} layout={{ width: 1000, height: 500, title: path }} + config={{ + modeBarButtonsToRemove: [ + "lasso2d", + "select2d", + "pan2d", + "autoScale2d", + ], + }} + onUpdate={(figure) => { + console.log(figure); + }} /> ); }); From bf1c869cbadd675312b7a8ec9e0ad81d4e66a418 Mon Sep 17 00:00:00 2001 From: atef Date: Tue, 18 Apr 2023 10:04:08 +0200 Subject: [PATCH 2/6] Update AsciiArt.cs and Topology --- csharp/App/SaliMax/src/AsciiArt.cs | 1 + csharp/App/SaliMax/src/Topology.cs | 23 ++++++++++++----------- csharp/App/SaliMax/tunnels.sh | 12 ++++++------ 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/csharp/App/SaliMax/src/AsciiArt.cs b/csharp/App/SaliMax/src/AsciiArt.cs index 7485a1363..cff4dea90 100644 --- a/csharp/App/SaliMax/src/AsciiArt.cs +++ b/csharp/App/SaliMax/src/AsciiArt.cs @@ -1,3 +1,4 @@ +using InnovEnergy.Lib.Units; using InnovEnergy.Lib.Utils; namespace InnovEnergy.App.SaliMax; diff --git a/csharp/App/SaliMax/src/Topology.cs b/csharp/App/SaliMax/src/Topology.cs index f9db2b68b..defb0f570 100644 --- a/csharp/App/SaliMax/src/Topology.cs +++ b/csharp/App/SaliMax/src/Topology.cs @@ -1,8 +1,9 @@ #undef BatteriesAllowed using InnovEnergy.App.SaliMax.Controller; -using InnovEnergy.App.SaliMax.Log; using InnovEnergy.Lib.Utils; +using InnovEnergy.Lib.Units; + namespace InnovEnergy.App.SaliMax; @@ -16,12 +17,12 @@ public static class Topology var pwr = s.InverterStatus!.Ac.ActivePower; - var pvPower = (s.AmptStatus!.Devices[0].Dc.Voltage * s.AmptStatus.Devices[0].Dc.Current + s.AmptStatus!.Devices[1].Dc.Voltage * s.AmptStatus.Devices[1].Dc.Current).Round0(); // TODO using one Ampt - var loadPower = Utils.Round3((s.GridMeterStatus!.ActivePowerL123 + pwr)); // it's a + because the pwr is inverted + var pvPower = (s.AmptStatus!.Strings[0].Voltage * s.AmptStatus.Strings[0].Current + s.AmptStatus!.Strings[1].Voltage * s.AmptStatus.Strings[1].Current); // TODO using one Ampt + var loadPower = Utils.Round3((s.GridMeterStatus!.Ac.ActivePower + pwr)); // it's a + because the pwr is inverted - var gridSeparator = s.GridMeterStatus!.ActivePowerL123 > 0 ? chargingSeparator : dischargingSeparator; + var gridSeparator = s.GridMeterStatus!.Ac.ActivePower > 0 ? chargingSeparator : dischargingSeparator; var inverterSeparator = -pwr > 0 ? chargingSeparator : dischargingSeparator; - var dcSeparator = -s.DcDcStatus!.Dc.Power > 0 ? chargingSeparator : dischargingSeparator; + var dcSeparator = -s.DcDcStatus!.Right.Power > 0 ? chargingSeparator : dischargingSeparator; #if BatteriesAllowed var battery1Separator = s.BatteriesStatus[0]!.Power > 0 ? chargingSeparator : dischargingSeparator; var battery2Separator = s.BatteriesStatus[1]!.Power > 0 ? chargingSeparator : dischargingSeparator; @@ -36,7 +37,7 @@ public static class Topology s.GridMeterStatus.Ac.L3.Voltage.V() ).AlignCenterVertical(height); - var gridAcBusArrow = AsciiArt.CreateHorizontalArrow(s.GridMeterStatus!.ActivePowerL123.Round0(), gridSeparator) + var gridAcBusArrow = AsciiArt.CreateHorizontalArrow(s.GridMeterStatus!.Ac.ActivePower, gridSeparator) .AlignCenterVertical(height); @@ -58,7 +59,7 @@ public static class Topology var loadRect = StringUtils.AlignBottom(CreateRect(boxAcBus, boxLoad, loadPower), height); - var acBusInvertArrow = AsciiArt.CreateHorizontalArrow(-pwr.Round0(), inverterSeparator) + var acBusInvertArrow = AsciiArt.CreateHorizontalArrow(-pwr, inverterSeparator) .AlignCenterVertical(height); //////////////////// Inverter ///////////////////////// @@ -69,7 +70,7 @@ public static class Topology "" ).AlignCenterVertical(height); - var inverterArrow = AsciiArt.CreateHorizontalArrow(-pwr.Round0(), inverterSeparator) + var inverterArrow = AsciiArt.CreateHorizontalArrow(-pwr, inverterSeparator) .AlignCenterVertical(height); @@ -84,7 +85,7 @@ public static class Topology var pvBox = AsciiArt.CreateBox ( "MPPT", - ((s.AmptStatus!.Devices[0].Dc.Voltage + s.AmptStatus!.Devices[1].Dc.Voltage) / 2).Round0().V(), + ((s.AmptStatus!.Strings[0].Voltage + s.AmptStatus!.Strings[1].Voltage) / 2).V(), "" ); @@ -101,8 +102,8 @@ public static class Topology "" ).AlignCenterVertical(height); #if BatteriesAllowed - var dcArrow1 = AsciiArt.CreateHorizontalArrow(s.BatteriesStatus[0]!.Power.Round0(), battery1Separator); - var dcArrow2 = AsciiArt.CreateHorizontalArrow(s.BatteriesStatus[1]!.Power.Round0(), battery2Separator); + var dcArrow1 = AsciiArt.CreateHorizontalArrow(s.BatteriesStatus[0]!.Power, battery1Separator); + var dcArrow2 = AsciiArt.CreateHorizontalArrow(s.BatteriesStatus[1]!.Power, battery2Separator); #else var dcArrow1 =""; var dcArrow2 = ""; diff --git a/csharp/App/SaliMax/tunnels.sh b/csharp/App/SaliMax/tunnels.sh index 986fe440c..a6c06948b 100755 --- a/csharp/App/SaliMax/tunnels.sh +++ b/csharp/App/SaliMax/tunnels.sh @@ -1,6 +1,6 @@ #!/bin/bash -host=debian@10.2.1.87 +host=ie-entwicklung@10.2.3.104 tunnel() { name=$1 @@ -22,11 +22,11 @@ tunnel() { echo "" -tunnel "Trumpf Inverter (http) " 192.168.1.2 80 8001 -tunnel "Trumpf DCDC (http) " 192.168.1.3 80 8002 -tunnel "Emu Meter (http) " 192.168.1.241 80 8003 -tunnel "ADAM (http) " 192.168.1.242 80 8004 -tunnel "AMPT (http) " 192.168.1.249 8080 8005 +tunnel "Trumpf Inverter (http) " 192.168.1.2 80 7001 +tunnel "Trumpf DCDC (http) " 192.168.1.3 80 7002 +tunnel "Emu Meter (http) " 192.168.1.241 80 7003 +tunnel "ADAM (http) " 192.168.1.242 80 7004 +tunnel "AMPT (http) " 192.168.1.249 8080 7005 tunnel "Trumpf Inverter (modbus)" 192.168.1.2 502 5001 tunnel "Trumpf DCDC (modbus) " 192.168.1.3 502 5002 From 7635a8175e0923237f80b77d5b5d280f8590fb8f Mon Sep 17 00:00:00 2001 From: atef Date: Thu, 20 Apr 2023 13:19:33 +0200 Subject: [PATCH 3/6] Add EocReached and Total Current and correct naming --- .../src/Controller/AvgBatteriesStatus.cs | 5 +- csharp/App/SaliMax/src/Controller/Control.cs | 8 +-- .../App/SaliMax/src/Controller/Controller.cs | 64 +++++++++++-------- .../SaliMax/src/Controller/StatusRecord.cs | 2 +- 4 files changed, 46 insertions(+), 33 deletions(-) diff --git a/csharp/App/SaliMax/src/Controller/AvgBatteriesStatus.cs b/csharp/App/SaliMax/src/Controller/AvgBatteriesStatus.cs index 745b18187..151c0c5af 100644 --- a/csharp/App/SaliMax/src/Controller/AvgBatteriesStatus.cs +++ b/csharp/App/SaliMax/src/Controller/AvgBatteriesStatus.cs @@ -41,7 +41,10 @@ public static class AvgBatteriesStatus TemperatureState = stati.Any(b => b.TemperatureState == OperatingTemperature) // TODO: revisit when we have the overheated state ? OperatingTemperature : Cold, - + + TotalCurrent = stati.Average(b => b.TotalCurrent), + + EocReached = stati.All(b => b.EocReached), }; return new CombinedStatus diff --git a/csharp/App/SaliMax/src/Controller/Control.cs b/csharp/App/SaliMax/src/Controller/Control.cs index 08589edb5..7564a5f81 100644 --- a/csharp/App/SaliMax/src/Controller/Control.cs +++ b/csharp/App/SaliMax/src/Controller/Control.cs @@ -5,7 +5,7 @@ public static class Control public static Decimal ControlGridPower(this StatusRecord status, Decimal targetPower) { - return ControlPower(status.GridMeterStatus!.ActivePowerL123, targetPower, status.SalimaxConfig!.PConstant); + return ControlPower(status.GridMeterStatus!.Ac.ActivePower, targetPower, status.SalimaxConfig!.PConstant); } public static Decimal ControlInverterPower(this StatusRecord status, Decimal targetInverterPower) @@ -18,7 +18,7 @@ public static class Control public static Decimal ControlBatteryPower(this StatusRecord status, Decimal targetBatteryPower, UInt16 i = 0) //this will use the avg batteries { - return ControlPower(status.BatteriesStatus![i].Dc.Power, targetBatteryPower, status.SalimaxConfig!.PConstant); + return ControlPower(status.BatteriesStatus!.Combined.Dc.Power, targetBatteryPower, status.SalimaxConfig!.PConstant); } public static Decimal ControlLowBatterySoc(this StatusRecord status) @@ -29,14 +29,14 @@ public static class Control public static Decimal LowerLimit(params Decimal[] deltas) => deltas.Max(); public static Decimal UpperLimit(params Decimal[] deltas) => deltas.Min(); - private static Decimal HoldMinSocCurve(StatusRecord s, UInt16 i = 0) + private static Decimal HoldMinSocCurve(StatusRecord s) { // TODO: explain LowSOC curve var a = -2 * s.SalimaxConfig!.SelfDischargePower / s.SalimaxConfig.HoldSocZone; var b = -a * (s.SalimaxConfig.MinSoc + s.SalimaxConfig.HoldSocZone); - return s.BatteriesStatus![i].Soc * a + b; //this will use the avg batteries + return s.BatteriesStatus!.Combined.Soc * a + b; //this will use the avg batteries } private static Decimal ControlPower(Decimal measurement, Decimal target, Decimal p) diff --git a/csharp/App/SaliMax/src/Controller/Controller.cs b/csharp/App/SaliMax/src/Controller/Controller.cs index cadb47477..5638da544 100644 --- a/csharp/App/SaliMax/src/Controller/Controller.cs +++ b/csharp/App/SaliMax/src/Controller/Controller.cs @@ -5,6 +5,9 @@ using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; using InnovEnergy.Lib.Time.Unix; using InnovEnergy.Lib.Utils; + +using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Enums; + using static InnovEnergy.App.SaliMax.SaliMaxRelays.RelayState; @@ -68,69 +71,69 @@ public static class Controller //Grid-Tied 400V/50 Hz { SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open, - InverterStatus.AcDcActiveGridType: AcDcGridType.GridTied400V50Hz + InverterStatus.GridType: AcDcGridType.GridTied400V50Hz } => 9, { SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open, - InverterStatus.AcDcActiveGridType: AcDcGridType.GridTied400V50Hz + InverterStatus.GridType: AcDcGridType.GridTied400V50Hz } => 10, { SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open, - InverterStatus.AcDcActiveGridType: AcDcGridType.GridTied400V50Hz + InverterStatus.GridType: AcDcGridType.GridTied400V50Hz } => 11, { SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open, - InverterStatus.AcDcActiveGridType: AcDcGridType.GridTied400V50Hz + InverterStatus.GridType: AcDcGridType.GridTied400V50Hz } => 12, { SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed, - InverterStatus.AcDcActiveGridType: AcDcGridType.GridTied400V50Hz + InverterStatus.GridType: AcDcGridType.GridTied400V50Hz } => 13, { SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed, - InverterStatus.AcDcActiveGridType: AcDcGridType.GridTied400V50Hz + InverterStatus.GridType: AcDcGridType.GridTied400V50Hz } => 14, { SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed, - InverterStatus.AcDcActiveGridType: AcDcGridType.GridTied400V50Hz + InverterStatus.GridType: AcDcGridType.GridTied400V50Hz } => 15, { SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed, - InverterStatus.AcDcActiveGridType: AcDcGridType.GridTied400V50Hz + InverterStatus.GridType: AcDcGridType.GridTied400V50Hz } => 16, //Island 400V / 50Hz { SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open, - InverterStatus.AcDcActiveGridType: AcDcGridType.Island400V50Hz + InverterStatus.GridType: AcDcGridType.Island400V50Hz } => 17, { SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open, - InverterStatus.AcDcActiveGridType: AcDcGridType.Island400V50Hz + InverterStatus.GridType: AcDcGridType.Island400V50Hz } => 18, { SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open, - InverterStatus.AcDcActiveGridType: AcDcGridType.Island400V50Hz + InverterStatus.GridType: AcDcGridType.Island400V50Hz } => 19, { SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open, - InverterStatus.AcDcActiveGridType: AcDcGridType.Island400V50Hz + InverterStatus.GridType: AcDcGridType.Island400V50Hz } => 20, { SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed, //this is wrong - InverterStatus.AcDcActiveGridType: AcDcGridType.Island400V50Hz + InverterStatus.GridType: AcDcGridType.Island400V50Hz } => 21, { SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed, - InverterStatus.AcDcActiveGridType: AcDcGridType.Island400V50Hz + InverterStatus.GridType: AcDcGridType.Island400V50Hz } => 22, { SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed, - InverterStatus.AcDcActiveGridType: AcDcGridType.Island400V50Hz + InverterStatus.GridType: AcDcGridType.Island400V50Hz } => 23, { SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed, - InverterStatus.AcDcActiveGridType: AcDcGridType.Island400V50Hz + InverterStatus.GridType: AcDcGridType.Island400V50Hz } => 24, @@ -149,7 +152,7 @@ public static class Controller var lastEocTime = GetLastEocTime(statusRecord); var timeSinceLastEoc = UnixTime.Now - lastEocTime; - _numberOfInverters = statusRecord.InverterStatus!.NumberOfConnectedSlaves; + _numberOfInverters = (UInt16)statusRecord.InverterStatus!.NumberOfConnectedSlaves ; _mustChargeFlag = timeSinceLastEoc > MaxTimeWithoutEoc; var noGridMeter = statusRecord.GridMeterStatus == null; @@ -263,7 +266,7 @@ public static class Controller goal = "Calibration Charge"; delta = statusRecord.ControlInverterPower(statusRecord.SalimaxConfig.MaxInverterPower); } - else if (statusRecord.AvgBatteriesStatus!.Soc < statusRecord.SalimaxConfig.MinSoc) // TODO + else if (statusRecord.BatteriesStatus!.Combined.Soc < statusRecord.SalimaxConfig.MinSoc) // TODO { goal = $"reach min SOC (Min soc: {statusRecord.SalimaxConfig.MinSoc})"; delta = statusRecord.ControlInverterPower(statusRecord.SalimaxConfig @@ -285,7 +288,7 @@ public static class Controller delta = inverterAc2DcLimitPower; } - var batteryChargingLimitPower = statusRecord.ControlBatteryPower(statusRecord.BatteriesStatus[0]!.MaxChargingPower); + var batteryChargingLimitPower = statusRecord.ControlBatteryPower(statusRecord.BatteriesStatus!.Combined.MaxChargingPower); if (delta > batteryChargingLimitPower) { @@ -304,12 +307,12 @@ public static class Controller } var batteryDischargingLimitPower = - statusRecord.ControlBatteryPower(statusRecord.BatteriesStatus[0]!.MaxDischargingPower); // TODO change to avg battery + statusRecord.ControlBatteryPower(statusRecord.BatteriesStatus.Combined.MaxDischargingPower); // TODO change to avg battery if (delta < batteryDischargingLimitPower) { limitReason = - $"limited by max battery discharging power: {statusRecord.BatteriesStatus[0]!.MaxDischargingPower}";// TODO change to avg battery + $"limited by max battery discharging power: {statusRecord.BatteriesStatus.Combined.MaxDischargingPower}";// TODO change to avg battery delta = batteryDischargingLimitPower; } @@ -448,14 +451,21 @@ public static class Controller } private static UnixTime GetLastEocTime(StatusRecord statusRecord) - { - if (statusRecord.BatteriesStatus[0]!.Soc >= 100 && statusRecord.BatteriesStatus[1]!.Soc >= 100 ) + { if (statusRecord.BatteriesStatus != null) { - Console.WriteLine("battery has reached EOC"); - File.AppendAllTextAsync(Config.LogSalimaxLog, String.Join(Environment.NewLine, UnixTime.Now + "battery has reached EOC")); - return UnixTime.Now; + if (statusRecord.BatteriesStatus!.Combined.EocReached) + { + Console.WriteLine("battery has reached EOC"); + File.AppendAllTextAsync(Config.LogSalimaxLog, + String.Join(Environment.NewLine, + UnixTime.Now + "battery has reached EOC")); + return UnixTime.Now; + } + } + else + { + Console.WriteLine("No battery Detected"); } - return statusRecord.SalimaxConfig.LastEoc; } diff --git a/csharp/App/SaliMax/src/Controller/StatusRecord.cs b/csharp/App/SaliMax/src/Controller/StatusRecord.cs index 9071f5976..feebca76a 100644 --- a/csharp/App/SaliMax/src/Controller/StatusRecord.cs +++ b/csharp/App/SaliMax/src/Controller/StatusRecord.cs @@ -17,7 +17,7 @@ public record StatusRecord public EmuMeterStatus? GridMeterStatus { get; init; } public SaliMaxRelayStatus? SaliMaxRelayStatus { get; init; } - public AmptStatus? AmptStatus { get; init; } + public AmptCommunicationUnitStatus? AmptStatus { get; init; } public EmuMeterStatus? AcInToAcOutMeterStatus { get; init; } public SalimaxConfig SalimaxConfig { get; init; } = null!; } \ No newline at end of file From 8631ada267ac489373cfcd9bd6e9c8cffa915a63 Mon Sep 17 00:00:00 2001 From: atef Date: Thu, 20 Apr 2023 13:20:26 +0200 Subject: [PATCH 4/6] Delete Log and Salimax Utils(it has only Round3) function --- csharp/App/SaliMax/src/Log/Ampt.cs | 26 ----- csharp/App/SaliMax/src/Log/Battery48Tl.cs | 26 ----- csharp/App/SaliMax/src/Log/EmuMeter.cs | 46 -------- csharp/App/SaliMax/src/Log/JsonUtil.cs | 118 --------------------- csharp/App/SaliMax/src/Log/Salimax.cs | 93 ---------------- csharp/App/SaliMax/src/Log/TruConvertAc.cs | 65 ------------ csharp/App/SaliMax/src/Log/TruConvertDc.cs | 32 ------ csharp/App/SaliMax/src/Utils.cs | 11 -- 8 files changed, 417 deletions(-) delete mode 100644 csharp/App/SaliMax/src/Log/Ampt.cs delete mode 100644 csharp/App/SaliMax/src/Log/Battery48Tl.cs delete mode 100644 csharp/App/SaliMax/src/Log/EmuMeter.cs delete mode 100644 csharp/App/SaliMax/src/Log/JsonUtil.cs delete mode 100644 csharp/App/SaliMax/src/Log/Salimax.cs delete mode 100644 csharp/App/SaliMax/src/Log/TruConvertAc.cs delete mode 100644 csharp/App/SaliMax/src/Log/TruConvertDc.cs delete mode 100644 csharp/App/SaliMax/src/Utils.cs diff --git a/csharp/App/SaliMax/src/Log/Ampt.cs b/csharp/App/SaliMax/src/Log/Ampt.cs deleted file mode 100644 index efaa6e388..000000000 --- a/csharp/App/SaliMax/src/Log/Ampt.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Text.Json.Nodes; -using InnovEnergy.Lib.Devices.AMPT; -using InnovEnergy.Lib.StatusApi; - -namespace InnovEnergy.App.SaliMax.Log; - -public static class Ampt -{ - public static JsonObject? Log(this AmptStatus? s) - { - if (s is null) - return null; - - // TODO return one AMPT device to sum all the other - - return DeviceType - .PvOnDc - .CreateDevice("AMPT") - .AddProp("Current 1", s.Devices[0].Dc.Current) - .AddProp("Current 2", s.Devices[1].Dc.Current) - .AddProp("Voltage 1", s.Devices[0].Dc.Voltage) - .AddProp("Voltage 2", s.Devices[1].Dc.Voltage) - .AddProp("Power 1", s.Devices[0].Dc.Current * s.Devices[0].Dc.Voltage) - .AddProp("Power 2", s.Devices[1].Dc.Current * s.Devices[1].Dc.Voltage); - } -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Log/Battery48Tl.cs b/csharp/App/SaliMax/src/Log/Battery48Tl.cs deleted file mode 100644 index f2c85bcbf..000000000 --- a/csharp/App/SaliMax/src/Log/Battery48Tl.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Text.Json.Nodes; -using InnovEnergy.Lib.Devices.Battery48TL; -using InnovEnergy.Lib.StatusApi; - -namespace InnovEnergy.App.SaliMax.Log; - -public static class Battery48Tl -{ - public static JsonObject? Log(this Battery48TLStatus? s) - { - if (s is null) - return null; - - return DeviceType - .Battery - .CreateDevice("48TL Battery") - .AddDc48Connection(s.Dc.Current.Round3(),s.Dc.Voltage.Round3()) - .AddAlarms(s.Alarms) - .AddWarnings(s.Warnings) - .AddProp("Soc", s.Soc.Round3()) - .AddProp("HeaterOn", s.HeaterOn) - .AddProp("EocReached", s.EocReached) - .AddProp("BatteryCold", s.BatteryCold) - .AddProp("Temperature", s.Temperature); - } -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Log/EmuMeter.cs b/csharp/App/SaliMax/src/Log/EmuMeter.cs deleted file mode 100644 index bb5dbbf29..000000000 --- a/csharp/App/SaliMax/src/Log/EmuMeter.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Text.Json.Nodes; -using InnovEnergy.Lib.Devices.EmuMeter; -using InnovEnergy.Lib.StatusApi; -using InnovEnergy.Lib.Utils; -using static DecimalMath.DecimalEx; -using static InnovEnergy.App.SaliMax.Log.JsonUtil; - -namespace InnovEnergy.App.SaliMax.Log; - -public static class EmuMeter -{ - public static JsonObject? Log(this EmuMeterStatus? s, DeviceType type, String serialNb) - { - if (s is null) - return null; - - - // - - var l1 = CreateAcPhase(s.Ac.L1.Current, s.Ac.L1.Voltage, ACos(s.Ac.L1.PowerFactor)); - var l2 = CreateAcPhase(s.Ac.L2.Current, s.Ac.L2.Voltage, ACos(s.Ac.L2.PowerFactor)); - var l3 = CreateAcPhase(s.Ac.L3.Current, s.Ac.L3.Voltage, ACos(s.Ac.L3.PowerFactor)); - - var ac = new JsonObject - { - ["L1"] = l1, - ["L2"] = l2, - ["L3"] = l3, - ["Frequency"] = s.Ac.Frequency - }; - - var status = new JsonObject - { - ["Ac"] = ac, - }; - - return new JsonObject { [$"EmuMeter {serialNb}"] = status }; - } - - private static IEnumerable GetAcPhases(this EmuMeterStatus s) - { - yield return CreateAcPhase(s.Ac.L1.Current.Round3(),s.Ac.L1.Voltage.Round3(),s.Ac.L1.PowerFactor.Apply(ACos).Round3()); - yield return CreateAcPhase(s.Ac.L2.Current.Round3(),s.Ac.L2.Voltage.Round3(),s.Ac.L2.PowerFactor.Apply(ACos).Round3()); - yield return CreateAcPhase(s.Ac.L3.Current.Round3(),s.Ac.L3.Voltage.Round3(),s.Ac.L3.PowerFactor.Apply(ACos).Round3()); - } -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Log/JsonUtil.cs b/csharp/App/SaliMax/src/Log/JsonUtil.cs deleted file mode 100644 index e82f2af01..000000000 --- a/csharp/App/SaliMax/src/Log/JsonUtil.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System.Text.Json.Nodes; -using InnovEnergy.Lib.StatusApi; - -namespace InnovEnergy.App.SaliMax.Log; - -public static class JsonUtil -{ - public static JsonObject CreateDevice(this DeviceType deviceType, String name) - { - return new JsonObject - { - { "Name", name }, - { "Type", deviceType.ToString() } - }; - } - - - public static JsonObject AddAcConnection(this JsonObject json, Decimal frequency, IEnumerable acPhases) - { - return json.AddAcConnection(frequency, acPhases.ToArray()); - } - - public static JsonObject AddAcConnection(this JsonObject json, Decimal frequency, params JsonNode[] acPhases) - { - return json - .AddProp("Ac", new JsonArray(acPhases)) - .AddProp("Frequency", frequency); - } - - public static JsonObject AddAlarms(this JsonObject json, IEnumerable alarms) - { - return json.AddProp("Alarms", alarms.ToJsonArray()); - } - - public static JsonObject AddWarnings(this JsonObject json, IEnumerable warnings) - { - return json.AddProp("Warnings", warnings.ToJsonArray()); - } - - - public static JsonObject AddProp(this JsonObject json, String key, JsonNode? value) - { - json.Add(key, value); - return json; - } - - - public static JsonObject AddDcConnection(this JsonObject json, Decimal current, Decimal voltage) - { - return json.AddProp("Dc", CreateDcPhase(current, voltage)); - } - - public static JsonObject AddDc48Connection(this JsonObject json, Decimal current, Decimal voltage) - { - return json.AddProp("Dc48", CreateDcPhase(current, voltage)); - } - - public static JsonObject CreateAcPhase(Decimal current, Decimal voltage, Decimal phi) - { - return new JsonObject - { - ["Current"] = current, - ["Voltage"] = voltage, - ["Phi" ] = phi, - }; - } - - - public static JsonObject CreateDcPhase(Decimal current, Decimal voltage) - { - return new JsonObject - { - ["Current"] = current , - ["Voltage"] = voltage , - ["Power"] = current * voltage, - }; - } - - public static Decimal Round3(this Decimal val) - { - return Decimal.Round(val, 3); - } - - public static Decimal Round0(this Decimal val) - { - return Decimal.Round(val, 0); - } - - public static JsonObject CreateBus(String left, String top, String bottom, String right, String name) - { - return new JsonObject - { - ["Name"] = name, - ["Left"] = left, - ["Top"] = top, - ["Bottom"] = bottom, - ["Right"] = right - }; - } - - public static String Port(DeviceType dt, BusPort bp, Boolean display = true) - { - return $"{Enum.GetName(dt)}:{Enum.GetName(bp)}:{(display ? "show" : "hide")}"; - } - - - public static JsonArray ToJsonArray(this IEnumerable things) - { - var jsonValues = things - .Select(t => t!.ToString()) - .Select(t => JsonValue.Create(t)) - .OfType() - .ToArray(); - - return new JsonArray(jsonValues); - } - -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Log/Salimax.cs b/csharp/App/SaliMax/src/Log/Salimax.cs deleted file mode 100644 index 09a7de12f..000000000 --- a/csharp/App/SaliMax/src/Log/Salimax.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Text.Json.Nodes; -using InnovEnergy.App.SaliMax.Controller; -using InnovEnergy.Lib.StatusApi; -using InnovEnergy.Lib.Time.Unix; - -namespace InnovEnergy.App.SaliMax.Log; - -public static class Salimax -{ - public static JsonObject ToLog(this StatusRecord s, UnixTime timestamp) - { - return new JsonObject - { - { "TimeStamp", timestamp.ToString()! }, - { "Devices", CreateDevices(s) } - }; - } - - private static JsonArray CreateDevices(StatusRecord s) - { - var devices = GetDevices(s).Where(d => d != null).ToArray(); - return new JsonArray(devices); - } - - private static IEnumerable GetDevices(StatusRecord s) - { - yield return s.InverterStatus.Log(); - yield return s.DcDcStatus.Log("3214"); - yield return s.GridMeterStatus.Log(DeviceType.Grid , "123"); - yield return s.AcInToAcOutMeterStatus.Log(DeviceType.AcInToAcOut, "123"); - yield return s.AmptStatus.Log(); - yield return s.BatteriesStatus![0].Log(); - yield return s.BatteriesStatus[1].Log(); - } - - private static JsonArray CreateTopology() - { - var acInBusJson = JsonUtil.CreateBus - ( - name: "AcIn", - left: JsonUtil.Port(DeviceType.Grid, BusPort.Ac), - top: JsonUtil.Port(DeviceType.PvOnAcIn, BusPort.Ac), - bottom: JsonUtil.Port(DeviceType.Load, BusPort.Infer), - right: JsonUtil.Port(DeviceType.AcInToAcOut, BusPort.Ac, false) - ); - - var acOutBusJson = JsonUtil.CreateBus - ( - name: "AcOut", - left: JsonUtil.Port(DeviceType.AcInToAcOut, BusPort.Ac, false), - top: JsonUtil.Port(DeviceType.PvOnAcOut, BusPort.Ac), - bottom: JsonUtil.Port(DeviceType.CriticalLoad, BusPort.Infer), - right: JsonUtil.Port(DeviceType.Inverter, BusPort.Ac) - ); - - var inverterJson = JsonUtil.CreateBus - ( - name: "Inverter", - left: JsonUtil.Port(DeviceType.Inverter, BusPort.Ac), - top: JsonUtil.Port(DeviceType.None, BusPort.None), - bottom: JsonUtil.Port(DeviceType.Losses, BusPort.Infer), - right: JsonUtil.Port(DeviceType.Inverter, BusPort.Dc) - ); - - var dcBusJson = JsonUtil.CreateBus - ( - name: "Dc", - left: JsonUtil.Port(DeviceType.Inverter, BusPort.Dc), - top: JsonUtil.Port(DeviceType.PvOnDc, BusPort.Dc), - bottom: JsonUtil.Port(DeviceType.DcLoad, BusPort.Infer), - right: JsonUtil.Port(DeviceType.DcDc, BusPort.Dc) - ); - - var dcDcJson = JsonUtil.CreateBus - ( - name: "DcDc", - left: JsonUtil.Port(DeviceType.DcDc, BusPort.Dc), - top: JsonUtil.Port(DeviceType.None, BusPort.None), - bottom: JsonUtil.Port(DeviceType.Losses, BusPort.Infer), - right: JsonUtil.Port(DeviceType.Battery, BusPort.Dc) - ); - - return new JsonArray(acInBusJson, acOutBusJson, inverterJson, dcBusJson, dcDcJson); - } - - public static JsonObject TopologyToLog(UnixTime timestamp) - { - return new JsonObject - { - { "Topology", CreateTopology() } - }; - } -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Log/TruConvertAc.cs b/csharp/App/SaliMax/src/Log/TruConvertAc.cs deleted file mode 100644 index af49cbc66..000000000 --- a/csharp/App/SaliMax/src/Log/TruConvertAc.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Text.Json.Nodes; -using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; -using InnovEnergy.Lib.Utils; -using static DecimalMath.DecimalEx; -using static InnovEnergy.App.SaliMax.Log.JsonUtil; - -namespace InnovEnergy.App.SaliMax.Log; - -public static class TruConvertAc -{ - - public static JsonObject? Log(this TruConvertAcStatus? s) - { - if (s is null) - return null; - - var dcPower = s.Ac.ActivePower; - var dcVoltage = s.ActualDcLinkVoltageLowerHalfExt + s.ActualDcLinkVoltageUpperHalfExt; - var dcCurrent = dcVoltage != 0m - ? dcPower / dcVoltage - : 0m; - - - // TODO: acos quadrant - // TODO: total AC power - - var l1 = CreateAcPhase(s.Ac.L1.Current.Round3(), s.Ac.L1.Voltage.Round3(), s.Ac.L1.PowerFactor.Clamp(-1m, 1m).Apply(ACos).Round3()); - var l2 = CreateAcPhase(s.Ac.L2.Current.Round3(), s.Ac.L2.Voltage.Round3(), s.Ac.L1.PowerFactor.Clamp(-1m, 1m).Apply(ACos).Round3()); - var l3 = CreateAcPhase(s.Ac.L3.Current.Round3(), s.Ac.L3.Voltage.Round3(), s.Ac.L1.PowerFactor.Clamp(-1m, 1m).Apply(ACos).Round3()); - - var ac = new JsonObject - { - ["L1"] = l1, - ["L2"] = l2, - ["L3"] = l3, - ["Frequency"] = s.Ac.Frequency - }; - - var dc = CreateDcPhase(dcCurrent, dcVoltage); - - var status = new JsonObject - { - ["Ac"] = ac , - ["Dc"] = dc , - ["Warnings"] = s.Warnings.ToJsonArray() , - ["Alarms"] = s.Alarms.ToJsonArray() , - }; - - return new JsonObject { [$"TruConvertAc {s.SerialNumber}"] = status }; - } - - private static IEnumerable GetAcPhases(this TruConvertAcStatus s) - { - // Math.Acos return "NaN" if the cos phi < -1 or > 1 - // Decimal.Acos throw an exception - yield return JsonUtil.CreateAcPhase(s.Ac.L1.Current.Round3(), s.Ac.L1.Voltage.Round3(), - s.Ac.L1.PowerFactor.Clamp(-1m, 1m).Apply(ACos).Round3()); - - yield return JsonUtil.CreateAcPhase(s.Ac.L2.Current.Round3(), s.Ac.L2.Voltage.Round3(), - s.Ac.L2.PowerFactor.Clamp(-1m, 1m).Apply(ACos).Round3()); - - yield return JsonUtil.CreateAcPhase(s.Ac.L3.Current.Round3(), s.Ac.L3.Voltage.Round3(), - s.Ac.L3.PowerFactor.Clamp(-1m, 1m).Apply(ACos).Round3()); - } -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Log/TruConvertDc.cs b/csharp/App/SaliMax/src/Log/TruConvertDc.cs deleted file mode 100644 index 34c23942b..000000000 --- a/csharp/App/SaliMax/src/Log/TruConvertDc.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Text.Json.Nodes; -using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; -using static InnovEnergy.App.SaliMax.Log.JsonUtil; - -namespace InnovEnergy.App.SaliMax.Log; - -using JO = JsonObject; - -public static class TruConvertDc -{ - // TODO: remove serialNb arg, embed TruConvertDcStatus - public static JsonObject? Log(this TruConvertDcStatus? s, String serialNb) - { - if (s is null) - return null; - - var dcCurrent = s.Dc.Current; - - return new JO - { - { - $"TruConvertDc {serialNb}", new JO - { - { "Dc" , CreateDcPhase(dcCurrent, s.Dc.Voltage) }, - { "Dc48" , CreateDcPhase(s.BatteryCurrent, s.BatteryVoltage) }, - { "Warnings", s.Warnings.ToJsonArray() }, - { "Alarms" , s.Alarms.ToJsonArray() }, - } - } - }; - } -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Utils.cs b/csharp/App/SaliMax/src/Utils.cs deleted file mode 100644 index 1cf608f84..000000000 --- a/csharp/App/SaliMax/src/Utils.cs +++ /dev/null @@ -1,11 +0,0 @@ -using InnovEnergy.Lib.Utils; - -namespace InnovEnergy.App.SaliMax; - -public static class Utils -{ - public static Decimal Round3(this Decimal d) - { - return DecimalUtils.RoundToSignificantDigits(d, 3); - } -} \ No newline at end of file From cb5bd91e55ed11f6a9889d3e446ad28fb5aa6e65 Mon Sep 17 00:00:00 2001 From: atef Date: Thu, 20 Apr 2023 13:21:41 +0200 Subject: [PATCH 5/6] Update the Topology display and the AsciiArt --- csharp/App/SaliMax/src/AsciiArt.cs | 33 ----- csharp/App/SaliMax/src/Program.cs | 21 --- csharp/App/SaliMax/src/Topology.cs | 230 ++++++++++++++++++----------- 3 files changed, 142 insertions(+), 142 deletions(-) diff --git a/csharp/App/SaliMax/src/AsciiArt.cs b/csharp/App/SaliMax/src/AsciiArt.cs index cff4dea90..9e0b47545 100644 --- a/csharp/App/SaliMax/src/AsciiArt.cs +++ b/csharp/App/SaliMax/src/AsciiArt.cs @@ -69,39 +69,6 @@ public static class AsciiArt ); } - public static String CreateVerticalPad(String value, Int32 contentVerticalWidth, Boolean direction, String name) - { - var v = value.PadLeft(contentVerticalWidth); - var n = name.PadLeft(contentVerticalWidth); - - if (direction) // up - { - var horizontal = "".PadLeft(contentVerticalWidth - 4, ' ').V(); - - return StringUtils.JoinLines( - n, - horizontal, - horizontal, - v, - horizontal, - horizontal - ); - } - else // down - { - var horizontal = "".PadLeft(contentVerticalWidth - 4, ' ').V(); - - return StringUtils.JoinLines( - horizontal, - horizontal, - v, - horizontal, - horizontal, - n - ); - } - } - public static String CreateVerticalArrow(Decimal power, Int32 width = 0) { var flow = "V".NewLine() + "V".NewLine() + power.W().NewLine() + "V".NewLine() + "V"; diff --git a/csharp/App/SaliMax/src/Program.cs b/csharp/App/SaliMax/src/Program.cs index d4d080a11..84245bb28 100644 --- a/csharp/App/SaliMax/src/Program.cs +++ b/csharp/App/SaliMax/src/Program.cs @@ -4,7 +4,6 @@ using System.Text.Json.Nodes; using System.Text.Json.Serialization; using Flurl.Http; using InnovEnergy.App.SaliMax.Controller; -using InnovEnergy.App.SaliMax.Log; using InnovEnergy.App.SaliMax.SaliMaxRelays; using InnovEnergy.App.SaliMax.SystemConfig; using InnovEnergy.Lib.Devices.AMPT; @@ -109,7 +108,6 @@ internal static class Program { InverterStatus = inverterDevice.ReadStatus(), DcDcStatus = dcDcDevice.ReadStatus(), - BatteriesStatus = combinedBatteryStatus, AcInToAcOutMeterStatus = acInToAcOutMeterDevice.ReadStatus(), GridMeterStatus = gridMeterDevice.ReadStatus(), @@ -123,10 +121,6 @@ internal static class Program var startTime = UnixTime.Now; const Int32 delayTime = 10; - - await UploadTopology(s3Config, Salimax.TopologyToLog(startTime), startTime); - DebugWriteTopology(startTime); - Console.WriteLine("press ctrl-C to stop"); @@ -167,21 +161,6 @@ internal static class Program // WriteToFile(jsonLog, "/home/debian/DataSaliMax/" + timestamp); // this is was for beaglebone TODO } - // [Conditional("RELEASE")] - private static JsonObject ReleaseWriteTopology(UnixTime timestamp) - { - var topologyJson = Salimax.TopologyToLog(timestamp); - - // WriteToFile(topologyJson, "/home/debian/DataSaliMax/topology" + timestamp); // this is was for beaglebone - return topologyJson; - } - - [Conditional("DEBUG")] - private static void DebugWriteTopology(UnixTime timestamp) - { - var topologyJson = Salimax.TopologyToLog(timestamp); - WriteToFile(topologyJson, "/home/atef/JsonData/topology" + timestamp); - } [Conditional("DEBUG")] private static void DebugWriteLog(JsonObject jsonLog, UnixTime timestamp) diff --git a/csharp/App/SaliMax/src/Topology.cs b/csharp/App/SaliMax/src/Topology.cs index defb0f570..9966be228 100644 --- a/csharp/App/SaliMax/src/Topology.cs +++ b/csharp/App/SaliMax/src/Topology.cs @@ -1,4 +1,4 @@ -#undef BatteriesAllowed +#define BatteriesAllowed using InnovEnergy.App.SaliMax.Controller; using InnovEnergy.Lib.Utils; @@ -9,32 +9,56 @@ namespace InnovEnergy.App.SaliMax; public static class Topology { - public static void Print(StatusRecord s) + private static String Separator(Decimal power) { const String chargingSeparator = ">>>>>>>>>>"; const String dischargingSeparator = "<<<<<<<<<"; + + return power > 0 ? chargingSeparator : dischargingSeparator; + } + + public static Decimal Round3(this Decimal d) + { + return d.RoundToSignificantDigits(3); + } + + public static void Print(StatusRecord s) + { const Int32 height = 25; - - var pwr = s.InverterStatus!.Ac.ActivePower; - var pvPower = (s.AmptStatus!.Strings[0].Voltage * s.AmptStatus.Strings[0].Current + s.AmptStatus!.Strings[1].Voltage * s.AmptStatus.Strings[1].Current); // TODO using one Ampt - var loadPower = Utils.Round3((s.GridMeterStatus!.Ac.ActivePower + pwr)); // it's a + because the pwr is inverted - - var gridSeparator = s.GridMeterStatus!.Ac.ActivePower > 0 ? chargingSeparator : dischargingSeparator; - var inverterSeparator = -pwr > 0 ? chargingSeparator : dischargingSeparator; - var dcSeparator = -s.DcDcStatus!.Right.Power > 0 ? chargingSeparator : dischargingSeparator; -#if BatteriesAllowed - var battery1Separator = s.BatteriesStatus[0]!.Power > 0 ? chargingSeparator : dischargingSeparator; - var battery2Separator = s.BatteriesStatus[1]!.Power > 0 ? chargingSeparator : dischargingSeparator; + var calculatedActivePwr = - s.InverterStatus!.Ac.ActivePower; + var measuredActivePwr = (s.InverterStatus.SumActivePowerL1 + s.InverterStatus.SumActivePowerL2 + + s.InverterStatus.SumActivePowerL3) * -1; + + measuredActivePwr.WriteLine(" : measured Sum of Active Pwr "); + + var setValueCosPhi = s.InverterStatus.CosPhiSetValue; + var setValueApparentPower = s.InverterStatus.ApparentPowerSetValue; + + +#if AmptAvailable + var pvPower = (s.AmptStatus!.Devices[0].Dc.Voltage * s.AmptStatus.Devices[0].Dc.Current + s.AmptStatus!.Devices[1].Dc.Voltage * s.AmptStatus.Devices[1].Dc.Current).Round0(); // TODO using one Ampt +#else + var pvPower = 0; #endif + var criticalLoadPower = (s.AcInToAcOutMeterStatus!.Ac.ActivePower.Value).Round3(); + + var dcTotalPower = -s.DcDcStatus!.TotalDcPower; + var gridSeparator = Separator(s.GridMeterStatus!.Ac.ActivePower); + var inverterSeparator = Separator(measuredActivePwr); + var dcSeparator = Separator(dcTotalPower); + var something = measuredActivePwr + criticalLoadPower; + var gridLoadPower = (s.GridMeterStatus!.Ac.ActivePower - something).Value.Round3(); + + ////////////////// Grid ////////////////////// var boxGrid = AsciiArt.CreateBox ( "Grid", - s.GridMeterStatus.Ac.L1.Voltage.V(), - s.GridMeterStatus.Ac.L2.Voltage.V(), - s.GridMeterStatus.Ac.L3.Voltage.V() + s.GridMeterStatus.Ac.L1.Voltage.Value.V(), + s.GridMeterStatus.Ac.L2.Voltage.Value.V(), + s.GridMeterStatus.Ac.L3.Voltage.Value.V() ).AlignCenterVertical(height); var gridAcBusArrow = AsciiArt.CreateHorizontalArrow(s.GridMeterStatus!.Ac.ActivePower, gridSeparator) @@ -45,9 +69,9 @@ public static class Topology var boxAcBus = AsciiArt.CreateBox ( "AC Bus", - s.InverterStatus.Ac.L1.Voltage.V(), - s.InverterStatus.Ac.L2.Voltage.V(), - s.InverterStatus.Ac.L3.Voltage.V() + s.InverterStatus.Ac.L1.Voltage.Value.V(), + s.InverterStatus.Ac.L2.Voltage.Value.V(), + s.InverterStatus.Ac.L3.Voltage.Value.V() ); var boxLoad = AsciiArt.CreateBox @@ -57,9 +81,9 @@ public static class Topology "" ); - var loadRect = StringUtils.AlignBottom(CreateRect(boxAcBus, boxLoad, loadPower), height); + var loadRect = StringUtils.AlignBottom(CreateRect(boxAcBus, boxLoad, gridLoadPower), height); - var acBusInvertArrow = AsciiArt.CreateHorizontalArrow(-pwr, inverterSeparator) + var acBusInvertArrow = AsciiArt.CreateHorizontalArrow(measuredActivePwr, inverterSeparator) .AlignCenterVertical(height); //////////////////// Inverter ///////////////////////// @@ -70,7 +94,7 @@ public static class Topology "" ).AlignCenterVertical(height); - var inverterArrow = AsciiArt.CreateHorizontalArrow(-pwr, inverterSeparator) + var inverterArrow = AsciiArt.CreateHorizontalArrow(measuredActivePwr, inverterSeparator) .AlignCenterVertical(height); @@ -78,90 +102,113 @@ public static class Topology var dcBusBox = AsciiArt.CreateBox ( "DC Bus", - (s.InverterStatus.ActualDcLinkVoltageLowerHalfExt + s.InverterStatus.ActualDcLinkVoltageUpperHalfExt).V(), + (s.InverterStatus.ActualDcLinkVoltageLowerHalfExt.Value + s.InverterStatus.ActualDcLinkVoltageUpperHalfExt.Value).V(), "" ); var pvBox = AsciiArt.CreateBox ( "MPPT", - ((s.AmptStatus!.Strings[0].Voltage + s.AmptStatus!.Strings[1].Voltage) / 2).V(), + ((s.AmptStatus!.Devices[0].Strings[0].Voltage.Value + s.AmptStatus!.Devices[0].Strings[1].Voltage.Value) / 2).V(), "" ); var pvRect = StringUtils.AlignTop(CreateRect(pvBox, dcBusBox, pvPower), height); - var dcBusArrow = AsciiArt.CreateHorizontalArrow(-s.DcDcStatus!.Dc.Power, dcSeparator) + var dcBusArrow = AsciiArt.CreateHorizontalArrow(-s.DcDcStatus!.Left.Power, dcSeparator) .AlignCenterVertical(height); - //////////////////// Dc/Dc ///////////////////////// - var dcBox = AsciiArt.CreateBox - ( - "Dc/Dc", - s.DcDcStatus.BatteryVoltage.V(), - "" - ).AlignCenterVertical(height); -#if BatteriesAllowed - var dcArrow1 = AsciiArt.CreateHorizontalArrow(s.BatteriesStatus[0]!.Power, battery1Separator); - var dcArrow2 = AsciiArt.CreateHorizontalArrow(s.BatteriesStatus[1]!.Power, battery2Separator); -#else - var dcArrow1 =""; - var dcArrow2 = ""; - var dcArrowRect = StringUtils.AlignCenterVertical(CreateRect(dcArrow1, dcArrow2), height); -#endif - - -#if BatteriesAllowed + //////////////////// Dc/Dc ///////////////////////// - //////////////////// Batteries ///////////////////////// - var battery1Box = AsciiArt.CreateBox - ( - "Battery 1", - s.BatteriesStatus[0].Voltage.V(), - s.BatteriesStatus[0].Soc.Percent(), - s.BatteriesStatus[0].Temperature.Celsius() - ); - - var battery2Box = AsciiArt.CreateBox - ( - "Battery 2", - s.BatteriesStatus[1].Voltage.V(), - s.BatteriesStatus[1].Soc.Percent(), - s.BatteriesStatus[1].Temperature.Celsius() - ); + var dcBox = AsciiArt.CreateBox( "Dc/Dc", s.DcDcStatus.Right.Voltage.Value.V(), "").AlignCenterVertical(height); - var batteryRect = CreateRect(battery1Box, battery2Box).AlignCenterVertical(height); + var topology = ""; + + if (s.BatteriesStatus != null) + { + var numBatteries = s.BatteriesStatus.Children.Count; - var avgBatteryBox = AsciiArt.CreateBox - ( - "Batteries", - s.AvgBatteriesStatus!.Voltage.V(), - s.AvgBatteriesStatus.Soc.Percent(), - s.AvgBatteriesStatus.Temperature.Celsius() - ).AlignCenterVertical(height); + // Create an array of battery arrows using LINQ + var dcArrows = s + .BatteriesStatus.Children + .Select(b => AsciiArt.CreateHorizontalArrow(b.Dc.Power, Separator(b.Dc.Power))) + .ToArray(); + // Create a rectangle from the array of arrows and align it vertically + var dcArrowRect = CreateRect(dcArrows).AlignCenterVertical(height); + + //////////////////// Batteries ///////////////////////// + + var batteryBox = new String[numBatteries]; + + for (var i = 0; i < numBatteries; i++) + { + if (s.BatteriesStatus.Children[i] != null) + { + batteryBox[i] = AsciiArt.CreateBox + ( + "Battery " + (i+1), + s.BatteriesStatus.Children[i].Dc.Voltage .Value.V(), + s.BatteriesStatus.Children[i].Soc .Value.Percent(), + s.BatteriesStatus.Children[i].Temperature .Value.Celsius(), + s.BatteriesStatus.Children[i].Dc.Current .Value.A(), + s.BatteriesStatus.Children[i].TotalCurrent.Value.A() + ); + } + else + { + batteryBox[i] = AsciiArt.CreateBox + ( + "Battery " + (i+1), + "not detected" + ); + } + } + + var batteryRect = CreateRect(batteryBox).AlignCenterVertical(height); + + + var avgBatteryBox = ""; + + if (s.BatteriesStatus.Combined != null) + { + avgBatteryBox = AsciiArt.CreateBox + ( + "Batteries", + s.BatteriesStatus.Combined.CellsVoltage, + s.BatteriesStatus.Combined.Soc, + s.BatteriesStatus.Combined.Temperature, + s.BatteriesStatus.Combined.Dc.Current, + s.BatteriesStatus.Combined.Alarms.Count > 0 ? String.Join(Environment.NewLine, s.BatteriesStatus.Combined.Alarms) : "No Alarm" + ).AlignCenterVertical(height); + } + + + topology = boxGrid.SideBySideWith(gridAcBusArrow, "") + .SideBySideWith(loadRect, "") + .SideBySideWith(acBusInvertArrow, "") + .SideBySideWith(inverterBox, "") + .SideBySideWith(inverterArrow, "") + .SideBySideWith(pvRect, "") + .SideBySideWith(dcBusArrow, "") + .SideBySideWith(dcBox, "") + .SideBySideWith(dcArrowRect, "") + .SideBySideWith(batteryRect, "") + .SideBySideWith(avgBatteryBox, "")+ "\n"; + + } + else + { + topology = boxGrid.SideBySideWith(gridAcBusArrow, "") + .SideBySideWith(loadRect, "") + .SideBySideWith(acBusInvertArrow, "") + .SideBySideWith(inverterBox, "") + .SideBySideWith(inverterArrow, "") + .SideBySideWith(pvRect, "") + .SideBySideWith(dcBusArrow, "") + .SideBySideWith(dcBox, "") + "\n"; + } - var topology = boxGrid.SideBySideWith(gridAcBusArrow, "") - .SideBySideWith(loadRect, "") - .SideBySideWith(acBusInvertArrow, "") - .SideBySideWith(inverterBox, "") - .SideBySideWith(inverterArrow, "") - .SideBySideWith(pvRect, "") - .SideBySideWith(dcBusArrow, "") - .SideBySideWith(dcBox, "") - .SideBySideWith(dcArrowRect, "") - .SideBySideWith(batteryRect, "") - .SideBySideWith(avgBatteryBox, "")+ "\n"; -#else - var topology = boxGrid.SideBySideWith(gridAcBusArrow, "") - .SideBySideWith(loadRect, "") - .SideBySideWith(acBusInvertArrow, "") - .SideBySideWith(inverterBox, "") - .SideBySideWith(inverterArrow, "") - .SideBySideWith(pvRect, "") - .SideBySideWith(dcBusArrow, "") - .SideBySideWith(dcBox, "")+ "\n"; -#endif Console.WriteLine(topology); } @@ -183,5 +230,12 @@ public static class Topology var rect = boxes.Select(l => l.AlignCenterHorizontal(maxWidth)).JoinLines(); return rect; } + + private static String CreateRect(String[] boxes) + { + var maxWidth = boxes.Max(l => l.Width()); + var rect = boxes.Select(l => l.AlignCenterHorizontal(maxWidth)).JoinLines(); + return rect; + } } \ No newline at end of file From d8911a2a3a4e00066eb8e0c6a5712f8ed61a1861 Mon Sep 17 00:00:00 2001 From: atef Date: Thu, 20 Apr 2023 13:22:41 +0200 Subject: [PATCH 6/6] Add EocReached in the batteries and add percent to the Units --- .../Battery48TL/Battery48TLStatusRecord.cs | 3 ++ .../Lib/Devices/Battery48TL/ModbusParser.cs | 41 +++++++++++++++++-- csharp/Lib/Units/Units.cs | 1 + 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/csharp/Lib/Devices/Battery48TL/Battery48TLStatusRecord.cs b/csharp/Lib/Devices/Battery48TL/Battery48TLStatusRecord.cs index c42532fc6..da885cb32 100644 --- a/csharp/Lib/Devices/Battery48TL/Battery48TLStatusRecord.cs +++ b/csharp/Lib/Devices/Battery48TL/Battery48TLStatusRecord.cs @@ -22,9 +22,12 @@ public record Battery48TLStatus : BatteryStatus public IReadOnlyList Warnings { get; init; } = Array.Empty(); public IReadOnlyList Alarms { get; init; } = Array.Empty(); + public Boolean EocReached { get; init; } public Boolean ConnectedToDc { get; init; } public Boolean Heating { get; init; } public TemperatureState TemperatureState { get; init; } // cold | operating temperature | overheated + + public Current TotalCurrent { get; init; } diff --git a/csharp/Lib/Devices/Battery48TL/ModbusParser.cs b/csharp/Lib/Devices/Battery48TL/ModbusParser.cs index af2c3568a..6877c6a05 100644 --- a/csharp/Lib/Devices/Battery48TL/ModbusParser.cs +++ b/csharp/Lib/Devices/Battery48TL/ModbusParser.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Text; using InnovEnergy.Lib.Protocols.Modbus.Conversions; using InnovEnergy.Lib.Units.Composite; using InnovEnergy.Lib.Utils; @@ -17,9 +18,11 @@ public static class ModbusParser var soc = data.ParseSoc(); - var eoc = greenLed is On - && amberLed is Off - && blueLed is Off; + // var eoc = greenLed is On + // && amberLed is Off + // && blueLed is Off; + + var eoc = data.ParseEocReached(); var maxSoc = eoc ? 100m : 99.9m; @@ -48,6 +51,8 @@ public static class ModbusParser MaxChargingPower = data.CalcMaxChargePower(), MaxDischargingPower = data.CalcMaxDischargePower(), CellsVoltage = data.ParseDecimal(register: 1000, scaleFactor: 0.01m), + TotalCurrent = data.ReadTotalCurrent(), + EocReached = eoc }; } @@ -72,6 +77,19 @@ public static class ModbusParser return data.ParseDecimal(register: 1002, scaleFactor: 0.01m); } + internal static Decimal ReadTotalCurrent(this ModbusRegisters data) + { + try + { + return ParseDecimal(data, register: 1063, scaleFactor: 0.01m, offset: -100); + } + catch (Exception e) + { + Console.WriteLine(e + " Read Total current fail "); + throw; + } + } + internal static Boolean ParseBool(this ModbusRegisters data, Int32 baseRegister, Int16 bit) { var x = bit / 16; @@ -95,7 +113,22 @@ public static class ModbusParser (true, true) => BlinkingFast, }; } - + + internal static String ParseString(this ModbusRegisters data, Int32 register, Int16 count) + { + return Enumerable + .Range(register, count) + .Select(i => data[i]) + .Select(BitConverter.GetBytes) + .Select(Encoding.ASCII.GetString) + .Aggregate("", (a, b) => a + b[1] + b[0]); // endian swap + } + + internal static Boolean ParseEocReached(this ModbusRegisters data) + { + var s = ParseString(data, 1061, 2); + return "EOC_" == s; + } internal static Decimal ParseSoc(this ModbusRegisters data) { diff --git a/csharp/Lib/Units/Units.cs b/csharp/Lib/Units/Units.cs index d9ee297c5..a18e4292d 100644 --- a/csharp/Lib/Units/Units.cs +++ b/csharp/Lib/Units/Units.cs @@ -15,6 +15,7 @@ public static class Units public static Angle Rad (this Decimal value) => value; public static Temperature Celsius(this Decimal value) => value; public static Energy KWh (this Decimal value) => value; + public static Percent Percent(this Decimal value) => value; } public static class Prefixes