Pierwszy commit - inicjalizacja projektu
This commit is contained in:
17
.claude/settings.local.json
Normal file
17
.claude/settings.local.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(bash:*)",
|
||||||
|
"Bash(./modborno3:*)",
|
||||||
|
"Bash(timeout 10 python3:*)",
|
||||||
|
"Bash(timeout 8 python3:*)",
|
||||||
|
"Bash(for i in 1 2 3)",
|
||||||
|
"Bash(do echo \"=== Test $i ===\")",
|
||||||
|
"Bash(done)",
|
||||||
|
"Bash(1 echo \"Test wykonany. Analiza wyników:\" echo \"\" echo \"✓ POMYŚLNIE ODCZYTANE REJESTRY:\" grep \"OK.*elapsed\" /tmp/sun2k_test.log)",
|
||||||
|
"Read(//tmp/**)",
|
||||||
|
"Bash(for i in 1 2)",
|
||||||
|
"Bash(ls:*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
611
CONFIG.html
Normal file
611
CONFIG.html
Normal file
@@ -0,0 +1,611 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>CONFIG.md</title>
|
||||||
|
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* https://github.com/microsoft/vscode/blob/master/extensions/markdown-language-features/media/markdown.css */
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--vscode-markdown-font-family, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif);
|
||||||
|
font-size: var(--vscode-markdown-font-size, 14px);
|
||||||
|
padding: 0 26px;
|
||||||
|
line-height: var(--vscode-markdown-line-height, 22px);
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
#code-csp-warning {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
color: white;
|
||||||
|
margin: 16px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
background-color:#444444;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 6px;
|
||||||
|
box-shadow: 1px 1px 1px rgba(0,0,0,.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
#code-csp-warning:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
background-color:#007acc;
|
||||||
|
box-shadow: 2px 2px 2px rgba(0,0,0,.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.scrollBeyondLastLine {
|
||||||
|
margin-bottom: calc(100vh - 22px);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.showEditorSelection .code-line {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.showEditorSelection .code-active-line:before,
|
||||||
|
body.showEditorSelection .code-line:hover:before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -12px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.showEditorSelection li.code-active-line:before,
|
||||||
|
body.showEditorSelection li.code-line:hover:before {
|
||||||
|
left: -30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-light.showEditorSelection .code-active-line:before {
|
||||||
|
border-left: 3px solid rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-light.showEditorSelection .code-line:hover:before {
|
||||||
|
border-left: 3px solid rgba(0, 0, 0, 0.40);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-light.showEditorSelection .code-line .code-line:hover:before {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-dark.showEditorSelection .code-active-line:before {
|
||||||
|
border-left: 3px solid rgba(255, 255, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-dark.showEditorSelection .code-line:hover:before {
|
||||||
|
border-left: 3px solid rgba(255, 255, 255, 0.60);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-dark.showEditorSelection .code-line .code-line:hover:before {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-high-contrast.showEditorSelection .code-active-line:before {
|
||||||
|
border-left: 3px solid rgba(255, 160, 0, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-high-contrast.showEditorSelection .code-line:hover:before {
|
||||||
|
border-left: 3px solid rgba(255, 160, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-high-contrast.showEditorSelection .code-line .code-line:hover:before {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:focus,
|
||||||
|
input:focus,
|
||||||
|
select:focus,
|
||||||
|
textarea:focus {
|
||||||
|
outline: 1px solid -webkit-focus-ring-color;
|
||||||
|
outline-offset: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 0;
|
||||||
|
height: 2px;
|
||||||
|
border-bottom: 2px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
padding-bottom: 0.3em;
|
||||||
|
line-height: 1.2;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3 {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
table > thead > tr > th {
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
table > thead > tr > th,
|
||||||
|
table > thead > tr > td,
|
||||||
|
table > tbody > tr > th,
|
||||||
|
table > tbody > tr > td {
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table > tbody > tr + tr > td {
|
||||||
|
border-top: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 0 7px 0 5px;
|
||||||
|
padding: 0 16px 0 10px;
|
||||||
|
border-left-width: 5px;
|
||||||
|
border-left-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1.357em;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.wordWrap pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre:not(.hljs),
|
||||||
|
pre.hljs code > div {
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
color: var(--vscode-editor-foreground);
|
||||||
|
tab-size: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Theming */
|
||||||
|
|
||||||
|
.vscode-light pre {
|
||||||
|
background-color: rgba(220, 220, 220, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-dark pre {
|
||||||
|
background-color: rgba(10, 10, 10, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-high-contrast pre {
|
||||||
|
background-color: rgb(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-high-contrast h1 {
|
||||||
|
border-color: rgb(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-light table > thead > tr > th {
|
||||||
|
border-color: rgba(0, 0, 0, 0.69);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-dark table > thead > tr > th {
|
||||||
|
border-color: rgba(255, 255, 255, 0.69);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-light h1,
|
||||||
|
.vscode-light hr,
|
||||||
|
.vscode-light table > tbody > tr + tr > td {
|
||||||
|
border-color: rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-dark h1,
|
||||||
|
.vscode-dark hr,
|
||||||
|
.vscode-dark table > tbody > tr + tr > td {
|
||||||
|
border-color: rgba(255, 255, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Tomorrow Theme */
|
||||||
|
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
|
||||||
|
/* Original theme - https://github.com/chriskempson/tomorrow-theme */
|
||||||
|
|
||||||
|
/* Tomorrow Comment */
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-quote {
|
||||||
|
color: #8e908c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Red */
|
||||||
|
.hljs-variable,
|
||||||
|
.hljs-template-variable,
|
||||||
|
.hljs-tag,
|
||||||
|
.hljs-name,
|
||||||
|
.hljs-selector-id,
|
||||||
|
.hljs-selector-class,
|
||||||
|
.hljs-regexp,
|
||||||
|
.hljs-deletion {
|
||||||
|
color: #c82829;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Orange */
|
||||||
|
.hljs-number,
|
||||||
|
.hljs-built_in,
|
||||||
|
.hljs-builtin-name,
|
||||||
|
.hljs-literal,
|
||||||
|
.hljs-type,
|
||||||
|
.hljs-params,
|
||||||
|
.hljs-meta,
|
||||||
|
.hljs-link {
|
||||||
|
color: #f5871f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Yellow */
|
||||||
|
.hljs-attribute {
|
||||||
|
color: #eab700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Green */
|
||||||
|
.hljs-string,
|
||||||
|
.hljs-symbol,
|
||||||
|
.hljs-bullet,
|
||||||
|
.hljs-addition {
|
||||||
|
color: #718c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Blue */
|
||||||
|
.hljs-title,
|
||||||
|
.hljs-section {
|
||||||
|
color: #4271ae;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Purple */
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-selector-tag {
|
||||||
|
color: #8959a8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
color: #4d4d4c;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-emphasis {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/*
|
||||||
|
* Markdown PDF CSS
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif, "Meiryo";
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre:not(.hljs) {
|
||||||
|
padding: 23px;
|
||||||
|
line-height: 19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
background: rgba(127, 127, 127, 0.1);
|
||||||
|
border-color: rgba(0, 122, 204, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji {
|
||||||
|
height: 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* for inline code */
|
||||||
|
:not(pre):not(.hljs) > code {
|
||||||
|
color: #C9AE75; /* Change the old color so it seems less like an error */
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page Break : use <div class="page"/> to insert page break
|
||||||
|
-------------------------------------------------------- */
|
||||||
|
.page {
|
||||||
|
page-break-after: always;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/mermaid/dist/mermaid.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
mermaid.initialize({
|
||||||
|
startOnLoad: true,
|
||||||
|
theme: document.body.classList.contains('vscode-dark') || document.body.classList.contains('vscode-high-contrast')
|
||||||
|
? 'dark'
|
||||||
|
: 'default'
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<h1 id="konfiguracja-programu-modborno3">Konfiguracja programu modb_orno3</h1>
|
||||||
|
<h2 id="parametry-konfiguracyjne">Parametry konfiguracyjne</h2>
|
||||||
|
<p>Wszystkie parametry są zdefiniowane na początku pliku <code>modb_orno3.c</code> (linie 11-40):</p>
|
||||||
|
<pre class="hljs"><code><div><span class="hljs-comment">/* KONFIGURACJA PROGRAMU - EDYTUJ TU */</span>
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* Port szeregowy */</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> USB_DEV_DEFAULT <span class="hljs-meta-string">"/dev/ttyUSB0"</span></span>
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* Adresy urządzeń Modbus RTU */</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> ORNO_SLAVE_ADR 2</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> SUN2000_SLAVE_ADR 3</span>
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* Parametry timing dla ORNO (mikrosekund) */</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> ORNO_RTS_DELAY 5000 <span class="hljs-comment">/* RTS delay: 5ms */</span></span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> ORNO_BYTE_TIMEOUT 2500 <span class="hljs-comment">/* Byte timeout: 2.5ms */</span></span>
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* Parametry timing dla SUN2K (mikrosekund) */</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> SUN2K_RTS_DELAY 5000 <span class="hljs-comment">/* RTS delay: 5ms */</span></span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> SUN2K_BYTE_TIMEOUT 2500 <span class="hljs-comment">/* Byte timeout: 2.5ms */</span></span>
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* Przerwa między ORNO a SUN2K (mikrosekund) */</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> DELAY_BETWEEN_DEVICES 100000 <span class="hljs-comment">/* 100ms */</span></span>
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* MQTT - ustaw 1 aby wyłączyć MQTT */</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> DISABLE_MQTT 0 <span class="hljs-comment">/* 0 = MQTT włączony, 1 = wyłączony */</span></span>
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* Broker MQTT */</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> MQTT_BROKER <span class="hljs-meta-string">"10.1.1.1"</span></span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> MQTT_PORT 1883</span>
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* Tryb pracy */</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> READ_LOOP false <span class="hljs-comment">/* false = jeden odczyt, true = pętla co 5s */</span></span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> READ_ORNO true <span class="hljs-comment">/* Czy odczytywać ORNO */</span></span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> READ_SUN2K true <span class="hljs-comment">/* Czy odczytywać SUN2K */</span></span>
|
||||||
|
</div></code></pre>
|
||||||
|
<h2 id="uruchomienie">Uruchomienie</h2>
|
||||||
|
<h3 id="kompilacja">Kompilacja:</h3>
|
||||||
|
<pre class="hljs"><code><div>bash cc-modb_orno3.sh
|
||||||
|
</div></code></pre>
|
||||||
|
<h3 id="uruchomienie">Uruchomienie:</h3>
|
||||||
|
<pre class="hljs"><code><div>./modborno3
|
||||||
|
</div></code></pre>
|
||||||
|
<p>Program uruchamia się <strong>bez parametrów</strong> i używa konfiguracji z #define.</p>
|
||||||
|
<h2 id="sekwencja-dzia%C5%82ania">Sekwencja działania</h2>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<p><strong>Odczyt ORNO</strong> (adres Modbus: 2)</p>
|
||||||
|
<ul>
|
||||||
|
<li>Napięcia L1, L2, L3</li>
|
||||||
|
<li>Prądy L1, L2, L3</li>
|
||||||
|
<li>Moc Total, L1, L2, L3</li>
|
||||||
|
<li>Energia Total, L1, L2, L3</li>
|
||||||
|
<li>Częstotliwość</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p><strong>Przerwa 100ms</strong></p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p><strong>Odczyt SUN2K</strong> (adres Modbus: 3)</p>
|
||||||
|
<ul>
|
||||||
|
<li>Wszystkie rejestry falownika</li>
|
||||||
|
<li>Czasy start/shutdown</li>
|
||||||
|
<li>Statusy</li>
|
||||||
|
<li>Moce</li>
|
||||||
|
<li>Energia dzienna i całkowita</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<h2 id="tematy-mqtt">Tematy MQTT</h2>
|
||||||
|
<h3 id="orno">ORNO:</h3>
|
||||||
|
<ul>
|
||||||
|
<li><code>/energy/orno/U</code> - napięcia (JSON: U1, U2, U3)</li>
|
||||||
|
<li><code>/energy/orno/I</code> - prądy (JSON: I1, I2, I3)</li>
|
||||||
|
<li><code>/energy/orno/P</code> - moc (JSON: P_Tot, P1, P2, P3)</li>
|
||||||
|
<li><code>/energy/orno/W</code> - energia (JSON: W_Tot, W1, W2, W3)</li>
|
||||||
|
<li><code>/energy/orno/Hz</code> - częstotliwość (wartość)</li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="sun2k">SUN2K:</h3>
|
||||||
|
<ul>
|
||||||
|
<li><code>/energy/SUN2K</code> - wszystkie parametry (JSON dla każdego rejestru)</li>
|
||||||
|
</ul>
|
||||||
|
<h2 id="modyfikacja-konfiguracji">Modyfikacja konfiguracji</h2>
|
||||||
|
<ol>
|
||||||
|
<li>Edytuj plik <code>modb_orno3.c</code></li>
|
||||||
|
<li>Zmień wartości #define</li>
|
||||||
|
<li>Przekompiluj: <code>bash cc-modb_orno3.sh</code></li>
|
||||||
|
<li>Uruchom: <code>./modborno3</code></li>
|
||||||
|
</ol>
|
||||||
|
<h2 id="przyk%C5%82ady-modyfikacji">Przykłady modyfikacji</h2>
|
||||||
|
<h3 id="wy%C5%82%C4%85czenie-mqtt">Wyłączenie MQTT:</h3>
|
||||||
|
<pre class="hljs"><code><div><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> DISABLE_MQTT 1</span>
|
||||||
|
</div></code></pre>
|
||||||
|
<h3 id="zmiana-adresu-brokera">Zmiana adresu brokera:</h3>
|
||||||
|
<pre class="hljs"><code><div><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> MQTT_BROKER <span class="hljs-meta-string">"192.168.1.100"</span></span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> MQTT_PORT 1883</span>
|
||||||
|
</div></code></pre>
|
||||||
|
<h3 id="tryb-ci%C4%85g%C5%82y-p%C4%99tla-co-5s">Tryb ciągły (pętla co 5s):</h3>
|
||||||
|
<pre class="hljs"><code><div><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> READ_LOOP true</span>
|
||||||
|
</div></code></pre>
|
||||||
|
<h3 id="tylko-orno-bez-sun2k">Tylko ORNO (bez SUN2K):</h3>
|
||||||
|
<pre class="hljs"><code><div><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> READ_SUN2K false</span>
|
||||||
|
</div></code></pre>
|
||||||
|
<h3 id="zmiana-przerwy-mi%C4%99dzy-odczytami-200ms">Zmiana przerwy między odczytami (200ms):</h3>
|
||||||
|
<pre class="hljs"><code><div><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> DELAY_BETWEEN_DEVICES 200000</span>
|
||||||
|
</div></code></pre>
|
||||||
|
<h2 id="walidacja-danych-i-zabezpieczenia-mqtt">Walidacja danych i zabezpieczenia MQTT</h2>
|
||||||
|
<h3 id="automatyczna-walidacja-warto%C5%9Bci">Automatyczna walidacja wartości</h3>
|
||||||
|
<p>Program <strong>automatycznie waliduje</strong> wszystkie odczytane wartości przed wysłaniem do MQTT.
|
||||||
|
Dane są wysyłane do MQTT <strong>tylko</strong> gdy:</p>
|
||||||
|
<ol>
|
||||||
|
<li>✅ Odczyt Modbus zakończył się sukcesem (prawidłowa liczba rejestrów)</li>
|
||||||
|
<li>✅ Wartość nie jest <strong>NaN</strong> (Not a Number)</li>
|
||||||
|
<li>✅ Wartość nie jest <strong>Inf</strong> (Infinity)</li>
|
||||||
|
<li>✅ Wartość mieści się w <strong>rozsądnym zakresie</strong></li>
|
||||||
|
</ol>
|
||||||
|
<h3 id="zakresy-walidacji-dla-orno">Zakresy walidacji dla ORNO</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Parametr</th>
|
||||||
|
<th>Zakres</th>
|
||||||
|
<th>Jednostka</th>
|
||||||
|
<th>Powód</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Napięcia L1/L2/L3</strong></td>
|
||||||
|
<td>150 - 280</td>
|
||||||
|
<td>V</td>
|
||||||
|
<td>Typowy zakres sieci UE (230V ±20%)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Prądy L1/L2/L3</strong></td>
|
||||||
|
<td>0 - 100</td>
|
||||||
|
<td>A</td>
|
||||||
|
<td>Zabezpieczenie instalacji domowej</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Moc Total/L1/L2/L3</strong></td>
|
||||||
|
<td>-25000 - +25000</td>
|
||||||
|
<td>W</td>
|
||||||
|
<td>Pozwala na generację (wartości ujemne)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Energia Total/L1/L2/L3</strong></td>
|
||||||
|
<td>0 - 1000000</td>
|
||||||
|
<td>kWh</td>
|
||||||
|
<td>Licznik nie może mieć ujemnej energii</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Częstotliwość</strong></td>
|
||||||
|
<td>47 - 53</td>
|
||||||
|
<td>Hz</td>
|
||||||
|
<td>Typowy zakres sieci UE (50Hz) / US (60Hz)</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h3 id="logi-walidacji">Logi walidacji</h3>
|
||||||
|
<p>Program wyświetla szczegółowe informacje o walidacji:</p>
|
||||||
|
<p><strong>Gdy dane są prawidłowe:</strong></p>
|
||||||
|
<pre class="hljs"><code><div>Voltages: L1=225.8 V, L2=221.3 V, L3=229.8 V
|
||||||
|
MQTT: Published voltages
|
||||||
|
</div></code></pre>
|
||||||
|
<p><strong>Gdy odczyt się nie powiódł:</strong></p>
|
||||||
|
<pre class="hljs"><code><div>Failed to read currents: Resource temporarily unavailable
|
||||||
|
(brak komunikatu MQTT - dane nie są wysyłane)
|
||||||
|
</div></code></pre>
|
||||||
|
<p><strong>Gdy wartość jest poza zakresem:</strong></p>
|
||||||
|
<pre class="hljs"><code><div>WARNING: Value 350.5 out of range [150.0, 280.0]
|
||||||
|
MQTT: Skipping voltages - invalid values
|
||||||
|
</div></code></pre>
|
||||||
|
<p><strong>Gdy wartość jest NaN lub Inf:</strong></p>
|
||||||
|
<pre class="hljs"><code><div>WARNING: Value is NaN
|
||||||
|
MQTT: Skipping power - invalid values
|
||||||
|
</div></code></pre>
|
||||||
|
<h3 id="zabezpieczenia-dla-sun2k">Zabezpieczenia dla SUN2K</h3>
|
||||||
|
<p>Dla urządzenia SUN2K program:</p>
|
||||||
|
<ul>
|
||||||
|
<li>❌ <strong>NIE wysyła</strong> danych gdy odczyt się nie powiódł</li>
|
||||||
|
<li>❌ <strong>NIE używa</strong> starych wartości (<code>buf_last_val</code>) po błędzie</li>
|
||||||
|
<li>✅ <strong>Tylko</strong> aktualne, poprawnie odczytane wartości są publikowane</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Przykład:</strong></p>
|
||||||
|
<pre class="hljs"><code><div>modbus_read_registers addr=0x7D55 nb=1 -> ERR (Connection timed out)
|
||||||
|
Frequency: Failed to 11 read: Connection timed out
|
||||||
|
MQTT: Skipping Frequency - no valid data
|
||||||
|
</div></code></pre>
|
||||||
|
<h3 id="dlaczego-to-wa%C5%BCne">Dlaczego to ważne?</h3>
|
||||||
|
<p><strong>Bez walidacji:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>Błędne odczyty (CRC errors) mogły wysyłać śmieciowe dane do MQTT</li>
|
||||||
|
<li>Wartości NaN/Inf mogły powodować błędy w systemach monitoringu</li>
|
||||||
|
<li>Niemożliwe wartości (np. 10000V) mogły wywoływać fałszywe alarmy</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Z walidacją:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>✅ Tylko <strong>poprawne</strong> wartości trafiają do MQTT</li>
|
||||||
|
<li>✅ System monitoringu dostaje <strong>wiarygodne</strong> dane</li>
|
||||||
|
<li>✅ Brak <strong>fałszywych alarmów</strong> z powodu błędnych odczytów</li>
|
||||||
|
<li>✅ <strong>Logi</strong> pokazują dokładnie co poszło nie tak</li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="kod-walidacji">Kod walidacji</h3>
|
||||||
|
<p>Funkcja walidacji znajduje się w <a href="modb_orno3.c#L237-L250">modb_orno3.c:237-250</a>:</p>
|
||||||
|
<pre class="hljs"><code><div><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">is_valid_float</span><span class="hljs-params">(<span class="hljs-keyword">float</span> value, <span class="hljs-keyword">float</span> min_val, <span class="hljs-keyword">float</span> max_val)</span>
|
||||||
|
</span>{
|
||||||
|
<span class="hljs-keyword">if</span> (isnan(value)) {
|
||||||
|
<span class="hljs-built_in">printf</span>(<span class="hljs-string">"WARNING: Value is NaN\n"</span>);
|
||||||
|
<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
|
||||||
|
}
|
||||||
|
<span class="hljs-keyword">if</span> (isinf(value)) {
|
||||||
|
<span class="hljs-built_in">printf</span>(<span class="hljs-string">"WARNING: Value is Inf\n"</span>);
|
||||||
|
<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
|
||||||
|
}
|
||||||
|
<span class="hljs-keyword">if</span> (value < min_val || value > max_val) {
|
||||||
|
<span class="hljs-built_in">printf</span>(<span class="hljs-string">"WARNING: Value %.2f out of range [%.2f, %.2f]\n"</span>,
|
||||||
|
value, min_val, max_val);
|
||||||
|
<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
|
||||||
|
}
|
||||||
|
<span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;
|
||||||
|
}
|
||||||
|
</div></code></pre>
|
||||||
|
<p>Walidacja jest wywoływana dla każdego pomiaru przed wysłaniem do MQTT.</p>
|
||||||
|
<h3 id="modyfikacja-zakres%C3%B3w-walidacji">Modyfikacja zakresów walidacji</h3>
|
||||||
|
<p>Jeśli chcesz zmienić zakresy walidacji (np. dla instalacji przemysłowej):</p>
|
||||||
|
<ol>
|
||||||
|
<li>Otwórz plik <code>modb_orno3.c</code></li>
|
||||||
|
<li>Znajdź sekcję z walidacją (np. dla napięć około linii 370)</li>
|
||||||
|
<li>Zmień wartości w <code>is_valid_float()</code>:</li>
|
||||||
|
</ol>
|
||||||
|
<p><strong>Przykład - zwiększenie zakresu prądów do 200A:</strong></p>
|
||||||
|
<pre class="hljs"><code><div><span class="hljs-comment">/* Przed: */</span>
|
||||||
|
<span class="hljs-keyword">if</span> (is_valid_float(I1, <span class="hljs-number">0.0</span>, <span class="hljs-number">100.0</span>) && ...
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* Po: */</span>
|
||||||
|
<span class="hljs-keyword">if</span> (is_valid_float(I1, <span class="hljs-number">0.0</span>, <span class="hljs-number">200.0</span>) && ...
|
||||||
|
</div></code></pre>
|
||||||
|
<ol start="4">
|
||||||
|
<li>Przekompiluj: <code>bash cc-modb_orno3.sh</code></li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
241
CONFIG.md
Normal file
241
CONFIG.md
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
# Konfiguracja programu modb_orno3
|
||||||
|
|
||||||
|
## Parametry konfiguracyjne
|
||||||
|
|
||||||
|
Wszystkie parametry są zdefiniowane na początku pliku `modb_orno3.c` (linie 11-40):
|
||||||
|
|
||||||
|
```c
|
||||||
|
/* KONFIGURACJA PROGRAMU - EDYTUJ TU */
|
||||||
|
|
||||||
|
/* Port szeregowy */
|
||||||
|
#define USB_DEV_DEFAULT "/dev/ttyUSB0"
|
||||||
|
|
||||||
|
/* Adresy urządzeń Modbus RTU */
|
||||||
|
#define ORNO_SLAVE_ADR 2
|
||||||
|
#define SUN2000_SLAVE_ADR 3
|
||||||
|
|
||||||
|
/* Parametry timing dla ORNO (mikrosekund) */
|
||||||
|
#define ORNO_RTS_DELAY 5000 /* RTS delay: 5ms */
|
||||||
|
#define ORNO_BYTE_TIMEOUT 2500 /* Byte timeout: 2.5ms */
|
||||||
|
|
||||||
|
/* Parametry timing dla SUN2K (mikrosekund) */
|
||||||
|
#define SUN2K_RTS_DELAY 5000 /* RTS delay: 5ms */
|
||||||
|
#define SUN2K_BYTE_TIMEOUT 2500 /* Byte timeout: 2.5ms */
|
||||||
|
|
||||||
|
/* Przerwa między ORNO a SUN2K (mikrosekund) */
|
||||||
|
#define DELAY_BETWEEN_DEVICES 100000 /* 100ms */
|
||||||
|
|
||||||
|
/* MQTT - ustaw 1 aby wyłączyć MQTT */
|
||||||
|
#define DISABLE_MQTT 0 /* 0 = MQTT włączony, 1 = wyłączony */
|
||||||
|
|
||||||
|
/* Broker MQTT */
|
||||||
|
#define MQTT_BROKER "10.1.1.1"
|
||||||
|
#define MQTT_PORT 1883
|
||||||
|
|
||||||
|
/* Tryb pracy */
|
||||||
|
#define READ_LOOP false /* false = jeden odczyt, true = pętla co 5s */
|
||||||
|
#define READ_ORNO true /* Czy odczytywać ORNO */
|
||||||
|
#define READ_SUN2K true /* Czy odczytywać SUN2K */
|
||||||
|
```
|
||||||
|
|
||||||
|
## Uruchomienie
|
||||||
|
|
||||||
|
### Kompilacja:
|
||||||
|
```bash
|
||||||
|
bash cc-modb_orno3.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Uruchomienie:
|
||||||
|
```bash
|
||||||
|
./modborno3
|
||||||
|
```
|
||||||
|
|
||||||
|
Program uruchamia się **bez parametrów** i używa konfiguracji z #define.
|
||||||
|
|
||||||
|
## Sekwencja działania
|
||||||
|
|
||||||
|
1. **Odczyt ORNO** (adres Modbus: 2)
|
||||||
|
- Napięcia L1, L2, L3
|
||||||
|
- Prądy L1, L2, L3
|
||||||
|
- Moc Total, L1, L2, L3
|
||||||
|
- Energia Total, L1, L2, L3
|
||||||
|
- Częstotliwość
|
||||||
|
|
||||||
|
2. **Przerwa 100ms**
|
||||||
|
|
||||||
|
3. **Odczyt SUN2K** (adres Modbus: 3)
|
||||||
|
- Wszystkie rejestry falownika
|
||||||
|
- Czasy start/shutdown
|
||||||
|
- Statusy
|
||||||
|
- Moce
|
||||||
|
- Energia dzienna i całkowita
|
||||||
|
|
||||||
|
## Tematy MQTT
|
||||||
|
|
||||||
|
### ORNO:
|
||||||
|
- `/energy/orno/U` - napięcia (JSON: U1, U2, U3)
|
||||||
|
- `/energy/orno/I` - prądy (JSON: I1, I2, I3)
|
||||||
|
- `/energy/orno/P` - moc (JSON: P_Tot, P1, P2, P3)
|
||||||
|
- `/energy/orno/W` - energia (JSON: W_Tot, W1, W2, W3)
|
||||||
|
- `/energy/orno/Hz` - częstotliwość (wartość)
|
||||||
|
|
||||||
|
### SUN2K:
|
||||||
|
- `/energy/SUN2K` - wszystkie parametry (JSON dla każdego rejestru)
|
||||||
|
|
||||||
|
## Modyfikacja konfiguracji
|
||||||
|
|
||||||
|
1. Edytuj plik `modb_orno3.c`
|
||||||
|
2. Zmień wartości #define
|
||||||
|
3. Przekompiluj: `bash cc-modb_orno3.sh`
|
||||||
|
4. Uruchom: `./modborno3`
|
||||||
|
|
||||||
|
## Przykłady modyfikacji
|
||||||
|
|
||||||
|
### Wyłączenie MQTT:
|
||||||
|
```c
|
||||||
|
#define DISABLE_MQTT 1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Zmiana adresu brokera:
|
||||||
|
```c
|
||||||
|
#define MQTT_BROKER "192.168.1.100"
|
||||||
|
#define MQTT_PORT 1883
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tryb ciągły (pętla co 5s):
|
||||||
|
```c
|
||||||
|
#define READ_LOOP true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tylko ORNO (bez SUN2K):
|
||||||
|
```c
|
||||||
|
#define READ_SUN2K false
|
||||||
|
```
|
||||||
|
|
||||||
|
### Zmiana przerwy między odczytami (200ms):
|
||||||
|
```c
|
||||||
|
#define DELAY_BETWEEN_DEVICES 200000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Walidacja danych i zabezpieczenia MQTT
|
||||||
|
|
||||||
|
### Automatyczna walidacja wartości
|
||||||
|
|
||||||
|
Program **automatycznie waliduje** wszystkie odczytane wartości przed wysłaniem do MQTT.
|
||||||
|
Dane są wysyłane do MQTT **tylko** gdy:
|
||||||
|
|
||||||
|
1. ✅ Odczyt Modbus zakończył się sukcesem (prawidłowa liczba rejestrów)
|
||||||
|
2. ✅ Wartość nie jest **NaN** (Not a Number)
|
||||||
|
3. ✅ Wartość nie jest **Inf** (Infinity)
|
||||||
|
4. ✅ Wartość mieści się w **rozsądnym zakresie**
|
||||||
|
|
||||||
|
### Zakresy walidacji dla ORNO
|
||||||
|
|
||||||
|
| Parametr | Zakres | Jednostka | Powód |
|
||||||
|
|----------|--------|-----------|-------|
|
||||||
|
| **Napięcia L1/L2/L3** | 150 - 280 | V | Typowy zakres sieci UE (230V ±20%) |
|
||||||
|
| **Prądy L1/L2/L3** | 0 - 100 | A | Zabezpieczenie instalacji domowej |
|
||||||
|
| **Moc Total/L1/L2/L3** | -25000 - +25000 | W | Pozwala na generację (wartości ujemne) |
|
||||||
|
| **Energia Total/L1/L2/L3** | 0 - 1000000 | kWh | Licznik nie może mieć ujemnej energii |
|
||||||
|
| **Częstotliwość** | 47 - 53 | Hz | Typowy zakres sieci UE (50Hz) / US (60Hz) |
|
||||||
|
|
||||||
|
### Logi walidacji
|
||||||
|
|
||||||
|
Program wyświetla szczegółowe informacje o walidacji:
|
||||||
|
|
||||||
|
**Gdy dane są prawidłowe:**
|
||||||
|
```
|
||||||
|
Voltages: L1=225.8 V, L2=221.3 V, L3=229.8 V
|
||||||
|
MQTT: Published voltages
|
||||||
|
```
|
||||||
|
|
||||||
|
**Gdy odczyt się nie powiódł:**
|
||||||
|
```
|
||||||
|
Failed to read currents: Resource temporarily unavailable
|
||||||
|
(brak komunikatu MQTT - dane nie są wysyłane)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Gdy wartość jest poza zakresem:**
|
||||||
|
```
|
||||||
|
WARNING: Value 350.5 out of range [150.0, 280.0]
|
||||||
|
MQTT: Skipping voltages - invalid values
|
||||||
|
```
|
||||||
|
|
||||||
|
**Gdy wartość jest NaN lub Inf:**
|
||||||
|
```
|
||||||
|
WARNING: Value is NaN
|
||||||
|
MQTT: Skipping power - invalid values
|
||||||
|
```
|
||||||
|
|
||||||
|
### Zabezpieczenia dla SUN2K
|
||||||
|
|
||||||
|
Dla urządzenia SUN2K program:
|
||||||
|
|
||||||
|
- ❌ **NIE wysyła** danych gdy odczyt się nie powiódł
|
||||||
|
- ❌ **NIE używa** starych wartości (`buf_last_val`) po błędzie
|
||||||
|
- ✅ **Tylko** aktualne, poprawnie odczytane wartości są publikowane
|
||||||
|
|
||||||
|
**Przykład:**
|
||||||
|
```
|
||||||
|
modbus_read_registers addr=0x7D55 nb=1 -> ERR (Connection timed out)
|
||||||
|
Frequency: Failed to 11 read: Connection timed out
|
||||||
|
MQTT: Skipping Frequency - no valid data
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dlaczego to ważne?
|
||||||
|
|
||||||
|
**Bez walidacji:**
|
||||||
|
- Błędne odczyty (CRC errors) mogły wysyłać śmieciowe dane do MQTT
|
||||||
|
- Wartości NaN/Inf mogły powodować błędy w systemach monitoringu
|
||||||
|
- Niemożliwe wartości (np. 10000V) mogły wywoływać fałszywe alarmy
|
||||||
|
|
||||||
|
**Z walidacją:**
|
||||||
|
- ✅ Tylko **poprawne** wartości trafiają do MQTT
|
||||||
|
- ✅ System monitoringu dostaje **wiarygodne** dane
|
||||||
|
- ✅ Brak **fałszywych alarmów** z powodu błędnych odczytów
|
||||||
|
- ✅ **Logi** pokazują dokładnie co poszło nie tak
|
||||||
|
|
||||||
|
### Kod walidacji
|
||||||
|
|
||||||
|
Funkcja walidacji znajduje się w [modb_orno3.c:237-250](modb_orno3.c#L237-L250):
|
||||||
|
|
||||||
|
```c
|
||||||
|
int is_valid_float(float value, float min_val, float max_val)
|
||||||
|
{
|
||||||
|
if (isnan(value)) {
|
||||||
|
printf("WARNING: Value is NaN\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (isinf(value)) {
|
||||||
|
printf("WARNING: Value is Inf\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (value < min_val || value > max_val) {
|
||||||
|
printf("WARNING: Value %.2f out of range [%.2f, %.2f]\n",
|
||||||
|
value, min_val, max_val);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Walidacja jest wywoływana dla każdego pomiaru przed wysłaniem do MQTT.
|
||||||
|
|
||||||
|
### Modyfikacja zakresów walidacji
|
||||||
|
|
||||||
|
Jeśli chcesz zmienić zakresy walidacji (np. dla instalacji przemysłowej):
|
||||||
|
|
||||||
|
1. Otwórz plik `modb_orno3.c`
|
||||||
|
2. Znajdź sekcję z walidacją (np. dla napięć około linii 370)
|
||||||
|
3. Zmień wartości w `is_valid_float()`:
|
||||||
|
|
||||||
|
**Przykład - zwiększenie zakresu prądów do 200A:**
|
||||||
|
```c
|
||||||
|
/* Przed: */
|
||||||
|
if (is_valid_float(I1, 0.0, 100.0) && ...
|
||||||
|
|
||||||
|
/* Po: */
|
||||||
|
if (is_valid_float(I1, 0.0, 200.0) && ...
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Przekompiluj: `bash cc-modb_orno3.sh`
|
||||||
611
CONFIG_tmp.html
Normal file
611
CONFIG_tmp.html
Normal file
@@ -0,0 +1,611 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>CONFIG.md</title>
|
||||||
|
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* https://github.com/microsoft/vscode/blob/master/extensions/markdown-language-features/media/markdown.css */
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--vscode-markdown-font-family, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif);
|
||||||
|
font-size: var(--vscode-markdown-font-size, 14px);
|
||||||
|
padding: 0 26px;
|
||||||
|
line-height: var(--vscode-markdown-line-height, 22px);
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
#code-csp-warning {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
color: white;
|
||||||
|
margin: 16px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
background-color:#444444;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 6px;
|
||||||
|
box-shadow: 1px 1px 1px rgba(0,0,0,.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
#code-csp-warning:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
background-color:#007acc;
|
||||||
|
box-shadow: 2px 2px 2px rgba(0,0,0,.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.scrollBeyondLastLine {
|
||||||
|
margin-bottom: calc(100vh - 22px);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.showEditorSelection .code-line {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.showEditorSelection .code-active-line:before,
|
||||||
|
body.showEditorSelection .code-line:hover:before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -12px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.showEditorSelection li.code-active-line:before,
|
||||||
|
body.showEditorSelection li.code-line:hover:before {
|
||||||
|
left: -30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-light.showEditorSelection .code-active-line:before {
|
||||||
|
border-left: 3px solid rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-light.showEditorSelection .code-line:hover:before {
|
||||||
|
border-left: 3px solid rgba(0, 0, 0, 0.40);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-light.showEditorSelection .code-line .code-line:hover:before {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-dark.showEditorSelection .code-active-line:before {
|
||||||
|
border-left: 3px solid rgba(255, 255, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-dark.showEditorSelection .code-line:hover:before {
|
||||||
|
border-left: 3px solid rgba(255, 255, 255, 0.60);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-dark.showEditorSelection .code-line .code-line:hover:before {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-high-contrast.showEditorSelection .code-active-line:before {
|
||||||
|
border-left: 3px solid rgba(255, 160, 0, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-high-contrast.showEditorSelection .code-line:hover:before {
|
||||||
|
border-left: 3px solid rgba(255, 160, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-high-contrast.showEditorSelection .code-line .code-line:hover:before {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:focus,
|
||||||
|
input:focus,
|
||||||
|
select:focus,
|
||||||
|
textarea:focus {
|
||||||
|
outline: 1px solid -webkit-focus-ring-color;
|
||||||
|
outline-offset: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 0;
|
||||||
|
height: 2px;
|
||||||
|
border-bottom: 2px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
padding-bottom: 0.3em;
|
||||||
|
line-height: 1.2;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3 {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
table > thead > tr > th {
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
table > thead > tr > th,
|
||||||
|
table > thead > tr > td,
|
||||||
|
table > tbody > tr > th,
|
||||||
|
table > tbody > tr > td {
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table > tbody > tr + tr > td {
|
||||||
|
border-top: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 0 7px 0 5px;
|
||||||
|
padding: 0 16px 0 10px;
|
||||||
|
border-left-width: 5px;
|
||||||
|
border-left-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1.357em;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.wordWrap pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre:not(.hljs),
|
||||||
|
pre.hljs code > div {
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
color: var(--vscode-editor-foreground);
|
||||||
|
tab-size: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Theming */
|
||||||
|
|
||||||
|
.vscode-light pre {
|
||||||
|
background-color: rgba(220, 220, 220, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-dark pre {
|
||||||
|
background-color: rgba(10, 10, 10, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-high-contrast pre {
|
||||||
|
background-color: rgb(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-high-contrast h1 {
|
||||||
|
border-color: rgb(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-light table > thead > tr > th {
|
||||||
|
border-color: rgba(0, 0, 0, 0.69);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-dark table > thead > tr > th {
|
||||||
|
border-color: rgba(255, 255, 255, 0.69);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-light h1,
|
||||||
|
.vscode-light hr,
|
||||||
|
.vscode-light table > tbody > tr + tr > td {
|
||||||
|
border-color: rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-dark h1,
|
||||||
|
.vscode-dark hr,
|
||||||
|
.vscode-dark table > tbody > tr + tr > td {
|
||||||
|
border-color: rgba(255, 255, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Tomorrow Theme */
|
||||||
|
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
|
||||||
|
/* Original theme - https://github.com/chriskempson/tomorrow-theme */
|
||||||
|
|
||||||
|
/* Tomorrow Comment */
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-quote {
|
||||||
|
color: #8e908c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Red */
|
||||||
|
.hljs-variable,
|
||||||
|
.hljs-template-variable,
|
||||||
|
.hljs-tag,
|
||||||
|
.hljs-name,
|
||||||
|
.hljs-selector-id,
|
||||||
|
.hljs-selector-class,
|
||||||
|
.hljs-regexp,
|
||||||
|
.hljs-deletion {
|
||||||
|
color: #c82829;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Orange */
|
||||||
|
.hljs-number,
|
||||||
|
.hljs-built_in,
|
||||||
|
.hljs-builtin-name,
|
||||||
|
.hljs-literal,
|
||||||
|
.hljs-type,
|
||||||
|
.hljs-params,
|
||||||
|
.hljs-meta,
|
||||||
|
.hljs-link {
|
||||||
|
color: #f5871f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Yellow */
|
||||||
|
.hljs-attribute {
|
||||||
|
color: #eab700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Green */
|
||||||
|
.hljs-string,
|
||||||
|
.hljs-symbol,
|
||||||
|
.hljs-bullet,
|
||||||
|
.hljs-addition {
|
||||||
|
color: #718c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Blue */
|
||||||
|
.hljs-title,
|
||||||
|
.hljs-section {
|
||||||
|
color: #4271ae;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Purple */
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-selector-tag {
|
||||||
|
color: #8959a8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
color: #4d4d4c;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-emphasis {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/*
|
||||||
|
* Markdown PDF CSS
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif, "Meiryo";
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre:not(.hljs) {
|
||||||
|
padding: 23px;
|
||||||
|
line-height: 19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
background: rgba(127, 127, 127, 0.1);
|
||||||
|
border-color: rgba(0, 122, 204, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji {
|
||||||
|
height: 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* for inline code */
|
||||||
|
:not(pre):not(.hljs) > code {
|
||||||
|
color: #C9AE75; /* Change the old color so it seems less like an error */
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page Break : use <div class="page"/> to insert page break
|
||||||
|
-------------------------------------------------------- */
|
||||||
|
.page {
|
||||||
|
page-break-after: always;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/mermaid/dist/mermaid.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
mermaid.initialize({
|
||||||
|
startOnLoad: true,
|
||||||
|
theme: document.body.classList.contains('vscode-dark') || document.body.classList.contains('vscode-high-contrast')
|
||||||
|
? 'dark'
|
||||||
|
: 'default'
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<h1 id="konfiguracja-programu-modborno3">Konfiguracja programu modb_orno3</h1>
|
||||||
|
<h2 id="parametry-konfiguracyjne">Parametry konfiguracyjne</h2>
|
||||||
|
<p>Wszystkie parametry są zdefiniowane na początku pliku <code>modb_orno3.c</code> (linie 11-40):</p>
|
||||||
|
<pre class="hljs"><code><div><span class="hljs-comment">/* KONFIGURACJA PROGRAMU - EDYTUJ TU */</span>
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* Port szeregowy */</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> USB_DEV_DEFAULT <span class="hljs-meta-string">"/dev/ttyUSB0"</span></span>
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* Adresy urządzeń Modbus RTU */</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> ORNO_SLAVE_ADR 2</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> SUN2000_SLAVE_ADR 3</span>
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* Parametry timing dla ORNO (mikrosekund) */</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> ORNO_RTS_DELAY 5000 <span class="hljs-comment">/* RTS delay: 5ms */</span></span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> ORNO_BYTE_TIMEOUT 2500 <span class="hljs-comment">/* Byte timeout: 2.5ms */</span></span>
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* Parametry timing dla SUN2K (mikrosekund) */</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> SUN2K_RTS_DELAY 5000 <span class="hljs-comment">/* RTS delay: 5ms */</span></span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> SUN2K_BYTE_TIMEOUT 2500 <span class="hljs-comment">/* Byte timeout: 2.5ms */</span></span>
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* Przerwa między ORNO a SUN2K (mikrosekund) */</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> DELAY_BETWEEN_DEVICES 100000 <span class="hljs-comment">/* 100ms */</span></span>
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* MQTT - ustaw 1 aby wyłączyć MQTT */</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> DISABLE_MQTT 0 <span class="hljs-comment">/* 0 = MQTT włączony, 1 = wyłączony */</span></span>
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* Broker MQTT */</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> MQTT_BROKER <span class="hljs-meta-string">"10.1.1.1"</span></span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> MQTT_PORT 1883</span>
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* Tryb pracy */</span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> READ_LOOP false <span class="hljs-comment">/* false = jeden odczyt, true = pętla co 5s */</span></span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> READ_ORNO true <span class="hljs-comment">/* Czy odczytywać ORNO */</span></span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> READ_SUN2K true <span class="hljs-comment">/* Czy odczytywać SUN2K */</span></span>
|
||||||
|
</div></code></pre>
|
||||||
|
<h2 id="uruchomienie">Uruchomienie</h2>
|
||||||
|
<h3 id="kompilacja">Kompilacja:</h3>
|
||||||
|
<pre class="hljs"><code><div>bash cc-modb_orno3.sh
|
||||||
|
</div></code></pre>
|
||||||
|
<h3 id="uruchomienie">Uruchomienie:</h3>
|
||||||
|
<pre class="hljs"><code><div>./modborno3
|
||||||
|
</div></code></pre>
|
||||||
|
<p>Program uruchamia się <strong>bez parametrów</strong> i używa konfiguracji z #define.</p>
|
||||||
|
<h2 id="sekwencja-dzia%C5%82ania">Sekwencja działania</h2>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<p><strong>Odczyt ORNO</strong> (adres Modbus: 2)</p>
|
||||||
|
<ul>
|
||||||
|
<li>Napięcia L1, L2, L3</li>
|
||||||
|
<li>Prądy L1, L2, L3</li>
|
||||||
|
<li>Moc Total, L1, L2, L3</li>
|
||||||
|
<li>Energia Total, L1, L2, L3</li>
|
||||||
|
<li>Częstotliwość</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p><strong>Przerwa 100ms</strong></p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p><strong>Odczyt SUN2K</strong> (adres Modbus: 3)</p>
|
||||||
|
<ul>
|
||||||
|
<li>Wszystkie rejestry falownika</li>
|
||||||
|
<li>Czasy start/shutdown</li>
|
||||||
|
<li>Statusy</li>
|
||||||
|
<li>Moce</li>
|
||||||
|
<li>Energia dzienna i całkowita</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<h2 id="tematy-mqtt">Tematy MQTT</h2>
|
||||||
|
<h3 id="orno">ORNO:</h3>
|
||||||
|
<ul>
|
||||||
|
<li><code>/energy/orno/U</code> - napięcia (JSON: U1, U2, U3)</li>
|
||||||
|
<li><code>/energy/orno/I</code> - prądy (JSON: I1, I2, I3)</li>
|
||||||
|
<li><code>/energy/orno/P</code> - moc (JSON: P_Tot, P1, P2, P3)</li>
|
||||||
|
<li><code>/energy/orno/W</code> - energia (JSON: W_Tot, W1, W2, W3)</li>
|
||||||
|
<li><code>/energy/orno/Hz</code> - częstotliwość (wartość)</li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="sun2k">SUN2K:</h3>
|
||||||
|
<ul>
|
||||||
|
<li><code>/energy/SUN2K</code> - wszystkie parametry (JSON dla każdego rejestru)</li>
|
||||||
|
</ul>
|
||||||
|
<h2 id="modyfikacja-konfiguracji">Modyfikacja konfiguracji</h2>
|
||||||
|
<ol>
|
||||||
|
<li>Edytuj plik <code>modb_orno3.c</code></li>
|
||||||
|
<li>Zmień wartości #define</li>
|
||||||
|
<li>Przekompiluj: <code>bash cc-modb_orno3.sh</code></li>
|
||||||
|
<li>Uruchom: <code>./modborno3</code></li>
|
||||||
|
</ol>
|
||||||
|
<h2 id="przyk%C5%82ady-modyfikacji">Przykłady modyfikacji</h2>
|
||||||
|
<h3 id="wy%C5%82%C4%85czenie-mqtt">Wyłączenie MQTT:</h3>
|
||||||
|
<pre class="hljs"><code><div><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> DISABLE_MQTT 1</span>
|
||||||
|
</div></code></pre>
|
||||||
|
<h3 id="zmiana-adresu-brokera">Zmiana adresu brokera:</h3>
|
||||||
|
<pre class="hljs"><code><div><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> MQTT_BROKER <span class="hljs-meta-string">"192.168.1.100"</span></span>
|
||||||
|
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> MQTT_PORT 1883</span>
|
||||||
|
</div></code></pre>
|
||||||
|
<h3 id="tryb-ci%C4%85g%C5%82y-p%C4%99tla-co-5s">Tryb ciągły (pętla co 5s):</h3>
|
||||||
|
<pre class="hljs"><code><div><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> READ_LOOP true</span>
|
||||||
|
</div></code></pre>
|
||||||
|
<h3 id="tylko-orno-bez-sun2k">Tylko ORNO (bez SUN2K):</h3>
|
||||||
|
<pre class="hljs"><code><div><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> READ_SUN2K false</span>
|
||||||
|
</div></code></pre>
|
||||||
|
<h3 id="zmiana-przerwy-mi%C4%99dzy-odczytami-200ms">Zmiana przerwy między odczytami (200ms):</h3>
|
||||||
|
<pre class="hljs"><code><div><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> DELAY_BETWEEN_DEVICES 200000</span>
|
||||||
|
</div></code></pre>
|
||||||
|
<h2 id="walidacja-danych-i-zabezpieczenia-mqtt">Walidacja danych i zabezpieczenia MQTT</h2>
|
||||||
|
<h3 id="automatyczna-walidacja-warto%C5%9Bci">Automatyczna walidacja wartości</h3>
|
||||||
|
<p>Program <strong>automatycznie waliduje</strong> wszystkie odczytane wartości przed wysłaniem do MQTT.
|
||||||
|
Dane są wysyłane do MQTT <strong>tylko</strong> gdy:</p>
|
||||||
|
<ol>
|
||||||
|
<li>✅ Odczyt Modbus zakończył się sukcesem (prawidłowa liczba rejestrów)</li>
|
||||||
|
<li>✅ Wartość nie jest <strong>NaN</strong> (Not a Number)</li>
|
||||||
|
<li>✅ Wartość nie jest <strong>Inf</strong> (Infinity)</li>
|
||||||
|
<li>✅ Wartość mieści się w <strong>rozsądnym zakresie</strong></li>
|
||||||
|
</ol>
|
||||||
|
<h3 id="zakresy-walidacji-dla-orno">Zakresy walidacji dla ORNO</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Parametr</th>
|
||||||
|
<th>Zakres</th>
|
||||||
|
<th>Jednostka</th>
|
||||||
|
<th>Powód</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Napięcia L1/L2/L3</strong></td>
|
||||||
|
<td>150 - 280</td>
|
||||||
|
<td>V</td>
|
||||||
|
<td>Typowy zakres sieci UE (230V ±20%)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Prądy L1/L2/L3</strong></td>
|
||||||
|
<td>0 - 100</td>
|
||||||
|
<td>A</td>
|
||||||
|
<td>Zabezpieczenie instalacji domowej</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Moc Total/L1/L2/L3</strong></td>
|
||||||
|
<td>-25000 - +25000</td>
|
||||||
|
<td>W</td>
|
||||||
|
<td>Pozwala na generację (wartości ujemne)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Energia Total/L1/L2/L3</strong></td>
|
||||||
|
<td>0 - 1000000</td>
|
||||||
|
<td>kWh</td>
|
||||||
|
<td>Licznik nie może mieć ujemnej energii</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Częstotliwość</strong></td>
|
||||||
|
<td>47 - 53</td>
|
||||||
|
<td>Hz</td>
|
||||||
|
<td>Typowy zakres sieci UE (50Hz) / US (60Hz)</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h3 id="logi-walidacji">Logi walidacji</h3>
|
||||||
|
<p>Program wyświetla szczegółowe informacje o walidacji:</p>
|
||||||
|
<p><strong>Gdy dane są prawidłowe:</strong></p>
|
||||||
|
<pre class="hljs"><code><div>Voltages: L1=225.8 V, L2=221.3 V, L3=229.8 V
|
||||||
|
MQTT: Published voltages
|
||||||
|
</div></code></pre>
|
||||||
|
<p><strong>Gdy odczyt się nie powiódł:</strong></p>
|
||||||
|
<pre class="hljs"><code><div>Failed to read currents: Resource temporarily unavailable
|
||||||
|
(brak komunikatu MQTT - dane nie są wysyłane)
|
||||||
|
</div></code></pre>
|
||||||
|
<p><strong>Gdy wartość jest poza zakresem:</strong></p>
|
||||||
|
<pre class="hljs"><code><div>WARNING: Value 350.5 out of range [150.0, 280.0]
|
||||||
|
MQTT: Skipping voltages - invalid values
|
||||||
|
</div></code></pre>
|
||||||
|
<p><strong>Gdy wartość jest NaN lub Inf:</strong></p>
|
||||||
|
<pre class="hljs"><code><div>WARNING: Value is NaN
|
||||||
|
MQTT: Skipping power - invalid values
|
||||||
|
</div></code></pre>
|
||||||
|
<h3 id="zabezpieczenia-dla-sun2k">Zabezpieczenia dla SUN2K</h3>
|
||||||
|
<p>Dla urządzenia SUN2K program:</p>
|
||||||
|
<ul>
|
||||||
|
<li>❌ <strong>NIE wysyła</strong> danych gdy odczyt się nie powiódł</li>
|
||||||
|
<li>❌ <strong>NIE używa</strong> starych wartości (<code>buf_last_val</code>) po błędzie</li>
|
||||||
|
<li>✅ <strong>Tylko</strong> aktualne, poprawnie odczytane wartości są publikowane</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Przykład:</strong></p>
|
||||||
|
<pre class="hljs"><code><div>modbus_read_registers addr=0x7D55 nb=1 -> ERR (Connection timed out)
|
||||||
|
Frequency: Failed to 11 read: Connection timed out
|
||||||
|
MQTT: Skipping Frequency - no valid data
|
||||||
|
</div></code></pre>
|
||||||
|
<h3 id="dlaczego-to-wa%C5%BCne">Dlaczego to ważne?</h3>
|
||||||
|
<p><strong>Bez walidacji:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>Błędne odczyty (CRC errors) mogły wysyłać śmieciowe dane do MQTT</li>
|
||||||
|
<li>Wartości NaN/Inf mogły powodować błędy w systemach monitoringu</li>
|
||||||
|
<li>Niemożliwe wartości (np. 10000V) mogły wywoływać fałszywe alarmy</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Z walidacją:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>✅ Tylko <strong>poprawne</strong> wartości trafiają do MQTT</li>
|
||||||
|
<li>✅ System monitoringu dostaje <strong>wiarygodne</strong> dane</li>
|
||||||
|
<li>✅ Brak <strong>fałszywych alarmów</strong> z powodu błędnych odczytów</li>
|
||||||
|
<li>✅ <strong>Logi</strong> pokazują dokładnie co poszło nie tak</li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="kod-walidacji">Kod walidacji</h3>
|
||||||
|
<p>Funkcja walidacji znajduje się w <a href="modb_orno3.c#L237-L250">modb_orno3.c:237-250</a>:</p>
|
||||||
|
<pre class="hljs"><code><div><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">is_valid_float</span><span class="hljs-params">(<span class="hljs-keyword">float</span> value, <span class="hljs-keyword">float</span> min_val, <span class="hljs-keyword">float</span> max_val)</span>
|
||||||
|
</span>{
|
||||||
|
<span class="hljs-keyword">if</span> (isnan(value)) {
|
||||||
|
<span class="hljs-built_in">printf</span>(<span class="hljs-string">"WARNING: Value is NaN\n"</span>);
|
||||||
|
<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
|
||||||
|
}
|
||||||
|
<span class="hljs-keyword">if</span> (isinf(value)) {
|
||||||
|
<span class="hljs-built_in">printf</span>(<span class="hljs-string">"WARNING: Value is Inf\n"</span>);
|
||||||
|
<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
|
||||||
|
}
|
||||||
|
<span class="hljs-keyword">if</span> (value < min_val || value > max_val) {
|
||||||
|
<span class="hljs-built_in">printf</span>(<span class="hljs-string">"WARNING: Value %.2f out of range [%.2f, %.2f]\n"</span>,
|
||||||
|
value, min_val, max_val);
|
||||||
|
<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
|
||||||
|
}
|
||||||
|
<span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;
|
||||||
|
}
|
||||||
|
</div></code></pre>
|
||||||
|
<p>Walidacja jest wywoływana dla każdego pomiaru przed wysłaniem do MQTT.</p>
|
||||||
|
<h3 id="modyfikacja-zakres%C3%B3w-walidacji">Modyfikacja zakresów walidacji</h3>
|
||||||
|
<p>Jeśli chcesz zmienić zakresy walidacji (np. dla instalacji przemysłowej):</p>
|
||||||
|
<ol>
|
||||||
|
<li>Otwórz plik <code>modb_orno3.c</code></li>
|
||||||
|
<li>Znajdź sekcję z walidacją (np. dla napięć około linii 370)</li>
|
||||||
|
<li>Zmień wartości w <code>is_valid_float()</code>:</li>
|
||||||
|
</ol>
|
||||||
|
<p><strong>Przykład - zwiększenie zakresu prądów do 200A:</strong></p>
|
||||||
|
<pre class="hljs"><code><div><span class="hljs-comment">/* Przed: */</span>
|
||||||
|
<span class="hljs-keyword">if</span> (is_valid_float(I1, <span class="hljs-number">0.0</span>, <span class="hljs-number">100.0</span>) && ...
|
||||||
|
|
||||||
|
<span class="hljs-comment">/* Po: */</span>
|
||||||
|
<span class="hljs-keyword">if</span> (is_valid_float(I1, <span class="hljs-number">0.0</span>, <span class="hljs-number">200.0</span>) && ...
|
||||||
|
</div></code></pre>
|
||||||
|
<ol start="4">
|
||||||
|
<li>Przekompiluj: <code>bash cc-modb_orno3.sh</code></li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
2708
Claude_conversation.md
Normal file
2708
Claude_conversation.md
Normal file
File diff suppressed because it is too large
Load Diff
1103
Claude_conversation_full.html
Normal file
1103
Claude_conversation_full.html
Normal file
File diff suppressed because it is too large
Load Diff
753
Claude_conversation_full.md
Normal file
753
Claude_conversation_full.md
Normal file
@@ -0,0 +1,753 @@
|
|||||||
|
---
|
||||||
|
title: "Claude Code Conversation - modb_orno3"
|
||||||
|
author: "Claude & User"
|
||||||
|
date: "25 stycznia 2026"
|
||||||
|
geometry: margin=2cm
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Przeanalizuję program modb_orno3.c i porównam go z ornowe51x_copilo.py. Zacznę od odczytania obu plików.
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Nie znalazłem pliku ornowe51x_copilo.py. Pozwól, że go poszukam w katalogu.
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
## Analiza programu modb_orno3.c
|
||||||
|
|
||||||
|
Przeanalizowałem program C oraz odpowiadający mu program Python. Oto szczegółowe zestawienie:
|
||||||
|
|
||||||
|
### **Konfiguracja parametrów przy uruchomieniu `/dev/ttyUSB0 2 3 orno`:**
|
||||||
|
|
||||||
|
Z linii [modb_orno3.c:245-260](modb_orno3.c#L245-L260):
|
||||||
|
- USB_DEV = `/dev/ttyUSB0`
|
||||||
|
- ORNO_SLAVE = `2`
|
||||||
|
- SUN2000_SLAVE = `3`
|
||||||
|
- mode = `"orno"` → ustawia `do_orno=1, do_sun2k=0`
|
||||||
|
|
||||||
|
### **Konfiguracja portu szeregowego:**
|
||||||
|
|
||||||
|
**Program C** ([modb_orno3.c:347](modb_orno3.c#L347)):
|
||||||
|
```c
|
||||||
|
modbus_new_rtu(USB_DEV, 9600, 'E', 8, 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Program Python** ([mbusclient.py:258](mbusclient.py#L258)):
|
||||||
|
```python
|
||||||
|
serial.Serial(device, baudrate=4800, parity=PARITY_EVEN, stopbits=STOPBITS_ONE)
|
||||||
|
```
|
||||||
|
ale potem ustawia `baudrate=brate` (parametr z createClient → 9600)
|
||||||
|
|
||||||
|
✅ **Zgodne: 9600 baud, Even parity, 8 data bits, 1 stop bit**
|
||||||
|
|
||||||
|
### **Timeout:**
|
||||||
|
|
||||||
|
**Program C** ([modb_orno3.c:366-368](modb_orno3.c#L366-L368)):
|
||||||
|
- RTS delay: 1000 µs (1ms)
|
||||||
|
- Response timeout: 900000 µs (0.9s)
|
||||||
|
- Byte timeout: 2500 µs (2.5ms)
|
||||||
|
|
||||||
|
**Program Python** ([mbusclient.py:255](mbusclient.py#L255)):
|
||||||
|
- mainTout = 0.9s
|
||||||
|
- charTout = 12 * 1/9600 = 0.00125s = 1.25ms
|
||||||
|
|
||||||
|
✅ **Prawidłowe**
|
||||||
|
|
||||||
|
### **Odczytywane rejestry:**
|
||||||
|
|
||||||
|
| Adres | Ilość | Python | C (program) |
|
||||||
|
|-------|-------|--------|-------------|
|
||||||
|
| 0x0E | 6 | ✅ Napięcia L1,L2,L3 | ✅ [linia 413](modb_orno3.c#L413) |
|
||||||
|
| 0x16 | 6 | ✅ Prądy L1,L2,L3 | ❌ **BRAK!** |
|
||||||
|
| 0x1C | 8 | ✅ Moc Tot,L1,L2,L3 | ✅ [linia 437](modb_orno3.c#L437) |
|
||||||
|
| 0x100 | 8 | ✅ Energia Tot,L1,L2,L3 | ✅ [linia 463](modb_orno3.c#L463) |
|
||||||
|
| 0x14 | 2 | ✅ Częstotliwość | ❌ **BRAK!** |
|
||||||
|
|
||||||
|
### **🔴 Główne problemy:**
|
||||||
|
|
||||||
|
1. **Program C NIE odczytuje prądów (0x16) i częstotliwości (0x14)** - które Python odczytuje
|
||||||
|
|
||||||
|
2. **Błąd w definicji mbReg_Orno[]** ([linia 84](modb_orno3.c#L84)):
|
||||||
|
```c
|
||||||
|
{"L3 Volt", 0x0012, 2, "L3 Volt", 10, rgU32, "V", "info", "hold", ""}
|
||||||
|
```
|
||||||
|
L3 ma `scale=10`, a powinno być `scale=1` (jak L1 i L2)
|
||||||
|
|
||||||
|
3. **Debug jest włączony** ([linia 364](modb_orno3.c#L364)):
|
||||||
|
```c
|
||||||
|
modbus_set_debug(ctx, TRUE);
|
||||||
|
```
|
||||||
|
To powoduje wypisywanie surowych ramek Modbus na stdout
|
||||||
|
|
||||||
|
4. **Niepotrzebne opóźnienia** ([linia 434-435](modb_orno3.c#L434-L435)):
|
||||||
|
```c
|
||||||
|
usleep(10000); // 10ms
|
||||||
|
usleep(100000); // 100ms - to jest zbędne podwójne opóźnienie
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Konwersja danych:**
|
||||||
|
|
||||||
|
**Python** używa:
|
||||||
|
```python
|
||||||
|
def mem2float(reg1, reg2):
|
||||||
|
raw = struct.pack('>HH', reg1, reg2)
|
||||||
|
return struct.unpack('>f', raw)[0]
|
||||||
|
```
|
||||||
|
|
||||||
|
**C** używa:
|
||||||
|
```c
|
||||||
|
modbus_get_float_dcba(®[0])
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Obie metody są zgodne** (big-endian float, DCBA order)
|
||||||
|
|
||||||
|
### **Podsumowanie:**
|
||||||
|
|
||||||
|
Program powinien działać dla podstawowych odczytów (napięcia, moc, energia), ale:
|
||||||
|
- Nie odczytuje prądów i częstotliwości
|
||||||
|
- Ma błąd w skali dla L3 Volt
|
||||||
|
- Debug mode może powodować zaśmiecenie outputu
|
||||||
|
|
||||||
|
Czy chcesz, żebym naprawił te problemy?
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Zbuduję i uruchomię program. Najpierw znajdę skrypt budowania.
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Widzę problem! Program otrzymuje błędne CRC, co wskazuje na problemy z komunikacją. Odpowiedzi są zbyt krótkie i nieprawidłowe. Spróbuję użyć trybu autotest, aby znaleźć optymalne parametry RTS delay i byte timeout.
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Świetnie! Autotest pokazuje, że domyślne parametry **nie działają**. Znalazłem optymalne ustawienia:
|
||||||
|
|
||||||
|
**Działające kombinacje:**
|
||||||
|
- ✅ RTS=5000us, byte_timeout=2500us (najszybsze: 62-70ms)
|
||||||
|
- ✅ RTS=10000us, byte_timeout=2500us (stabilne: 64-69ms)
|
||||||
|
|
||||||
|
Teraz uruchomię program z optymalnymi parametrami:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Widzę problem! Program ignoruje parametry RTS i byte_timeout w normalnym trybie. Parametry są odczytywane ([linia 272-277](modb_orno3.c#L272-L277)), ale używane tylko w trybie autotest. W normalnym trybie wartości są sztywno ustawione w [liniach 366-368](modb_orno3.c#L366-L368).
|
||||||
|
|
||||||
|
Sprawdzę kod dokładniej:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Problem jest jasny! Kod **ignoruje** parametry przekazane z linii komend. Muszę naprawić kod, aby używał parametrów `rts_delay_arg` i `byte_timeout_arg`. Ale najpierw sprawdzę jeszcze jedną rzecz - może problem jest w baud rate? W kodzie Python widziałem `BIT_RATE = 4800`. Sprawdzę:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Muszę naprawić kod. Zobaczę jak to działa w trybie autotest i dodam wsparcie dla parametrów w normalnym trybie:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Widzę problem! W trybie autotest kod prawidłowo ustawia parametry, ale w normalnym trybie są one ignorowane. Naprawię kod:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Teraz usunę zbędne podwójne ustawienia:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Teraz przekompiluję i uruchomię z poprawnymi parametrami:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Widzę że parametry są teraz używane (RTS Delay 5000 us), ale nadal są błędy CRC. Zauważyłem błędy CRC **na początku** outputu - to są pozostałości w buforze z poprzednich testów. Wyłączę debug mode i dodam flush bufora przed odczytem:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Teraz dodam flush bufora i poczekam przed pierwszym odczytem:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Świetnie! Komunikacja działa (wszystkie odczyty OK), ale wartości są błędne. Problem jest w formacie float! Obecnie używa `modbus_get_float_dcba`, ale to daje niepoprawne wartości.
|
||||||
|
|
||||||
|
Przetestuję wszystkie dostępne formaty float aby znaleźć właściwy:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Doskonale! **Format ABCD** jest prawidłowy! Wartości napięć wyglądają poprawnie:
|
||||||
|
- L1: 222.4 V
|
||||||
|
- L2: 215.0 V
|
||||||
|
- L3: 225.0 V
|
||||||
|
|
||||||
|
Teraz zmienię wszystkie formaty float na ABCD:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Usunę debug output:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Poprawię również output dla pozostałych odczytów:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Teraz przekompiluję i uruchomię końcową wersję:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
## ✅ DOSKONALE! Program działa poprawnie!
|
||||||
|
|
||||||
|
### **Wyniki transmisji:**
|
||||||
|
|
||||||
|
**Napięcia (0x0E):**
|
||||||
|
- L1: 223.1 V
|
||||||
|
- L2: 217.6 V
|
||||||
|
- L3: 226.1 V
|
||||||
|
- Czas: 62ms ✓
|
||||||
|
|
||||||
|
**Moc (0x1C):**
|
||||||
|
- Total: 0.595 W
|
||||||
|
- L1: 0.220 W
|
||||||
|
- L2: 0.256 W
|
||||||
|
- L3: 0.117 W
|
||||||
|
- Czas: 131ms ✓
|
||||||
|
|
||||||
|
**Energia (0x100):**
|
||||||
|
- Total: 27197.779 kWh
|
||||||
|
- L1: 10579.180 kWh
|
||||||
|
- L2: 8609.990 kWh
|
||||||
|
- L3: 8008.610 kWh
|
||||||
|
- Czas: 68ms ✓
|
||||||
|
|
||||||
|
### **Podsumowanie poprawek:**
|
||||||
|
|
||||||
|
1. ✅ **Naprawiono parametry RTS/byte timeout** - teraz program używa parametrów z linii komend (domyślnie: RTS=5000µs, byte=2500µs)
|
||||||
|
|
||||||
|
2. ✅ **Naprawiono format float** - zmieniono z `DCBA` na `ABCD` (big-endian)
|
||||||
|
|
||||||
|
3. ✅ **Wyłączono debug mode** - eliminuje problemy z timingiem
|
||||||
|
|
||||||
|
4. ✅ **Dodano flush bufora** - czyści port przed pierwszym odczytem
|
||||||
|
|
||||||
|
5. ✅ **Poprawiono output** - czytelny format wyników
|
||||||
|
|
||||||
|
Porównajmy teraz z programem Python:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Python program nie generuje outputu (prawdopodobnie problem z MQTT). Ale program C działa prawidłowo! Teraz sprawdzę czy wartości są stabilne:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
## ✅ WERYFIKACJA ZAKOŃCZONA SUKCESEM!
|
||||||
|
|
||||||
|
### **Test stabilności (3 próby):**
|
||||||
|
|
||||||
|
| Parametr | Test 1 | Test 2 | Test 3 | Stabilność |
|
||||||
|
|----------|--------|--------|--------|------------|
|
||||||
|
| **Napięcie L1** | 221.0 V | 219.4 V | 217.4 V | ✓ Wahania ±2V (normalne) |
|
||||||
|
| **Napięcie L2** | 221.7 V | 217.5 V | 218.3 V | ✓ Wahania ±2V (normalne) |
|
||||||
|
| **Napięcie L3** | 228.6 V | 228.4 V | 226.9 V | ✓ Stabilne |
|
||||||
|
| **Moc Total** | 0.614 W | 0.609 W | 0.609 W | ✓ Bardzo stabilna |
|
||||||
|
| **Energia Total** | 27197.801 | 27197.801 | 27197.811 | ✓ Powolny wzrost (normalne) |
|
||||||
|
|
||||||
|
### **Kluczowe naprawy w programie C:**
|
||||||
|
|
||||||
|
1. **Format float**: `DCBA` → `ABCD` (big-endian)
|
||||||
|
2. **Parametry timing**: Domyślnie RTS=5000µs, byte_timeout=2500µs (zamiast 1000/2500)
|
||||||
|
3. **Wsparcie parametrów CLI**: Program teraz używa argumentów z linii komend
|
||||||
|
4. **Flush bufora**: Dodano czyszczenie portu przed pierwszym odczytem
|
||||||
|
5. **Wyłączono debug**: Eliminuje problemy z timingiem komunikacji
|
||||||
|
|
||||||
|
### **Stan programu:**
|
||||||
|
- ✅ Komunikacja Modbus RTU działa poprawnie (9600 baud, Even parity)
|
||||||
|
- ✅ Odczyty napięć (0x0E) - OK
|
||||||
|
- ✅ Odczyty mocy (0x1C) - OK
|
||||||
|
- ✅ Odczyty energii (0x100) - OK
|
||||||
|
- ❌ **Brak odczytów prądów (0x16) i częstotliwości (0x14)** - te funkcje nie są zaimplementowane w programie C (są w Python)
|
||||||
|
|
||||||
|
Program działa zgodnie z oczekiwaniami dla parametrów `/dev/ttyUSB0 2 3 orno 5000 2500`!
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Dodam odczyt prądów (0x16) i częstotliwości (0x14) do programu C, tak jak są w programie Python.
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Najpierw dodam funkcje MQTT dla prądów i częstotliwości:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Teraz dodam zmienne dla prądów i częstotliwości:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Teraz dodam odczyt prądów (0x16) po odczycie napięć:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Teraz dodam odczyt częstotliwości (0x14) na końcu, po odczycie energii:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Teraz przekompiluję i przetestuję program z nowymi odczytami:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Doskonale! Wszystkie odczyty działają. Teraz wykonam test stabilności:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Świetnie! Wykonam jeszcze jeden pełny test z dokładnym timerem:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Doskonale! Porównajmy teraz z programem Python aby zweryfikować zgodność:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Teraz stworzę podsumowanie zmian:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Wykonam ostateczny test pokazujący wszystkie pomiary:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Wystąpił błąd CRC - prawdopodobnie port był zajęty. Poczekam chwilę i wykonam test ponownie:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
## ✅ SUKCES! Dodano odczyt prądów i częstotliwości
|
||||||
|
|
||||||
|
### **Kompletny odczyt z miernika ORNO:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Napięcia (0x0E): L1=215.4 V, L2=212.1 V, L3=226.0 V [64ms]
|
||||||
|
Prądy (0x16): L1=1.47 A, L2=1.83 A, L3=0.91 A [63ms] ← NOWE
|
||||||
|
Moc (0x1C): Total=0.607 W, L1=0.220 W, L2=0.275 W, L3=0.112 W [266ms]
|
||||||
|
Energia (0x100): Total=27197.840 kWh, L1=10579.200 kWh, L2=8610.020 kWh, L3=8008.620 kWh [69ms]
|
||||||
|
Częstotliwość (0x14): 49.99 Hz [54ms] ← NOWE
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Dodane do programu C:**
|
||||||
|
|
||||||
|
**1. Nowe funkcje MQTT:**
|
||||||
|
- `mqtt_send_I(I1, I2, I3)` → publikuje do `/energy/orno/I`
|
||||||
|
- `mqtt_send_Hz(Hz)` → publikuje do `/energy/orno/Hz`
|
||||||
|
|
||||||
|
**2. Nowe zmienne:**
|
||||||
|
- `float I1, I2, I3` - prądy dla faz L1, L2, L3
|
||||||
|
- `float Freq` - częstotliwość sieci
|
||||||
|
|
||||||
|
**3. Nowe odczyty Modbus:**
|
||||||
|
- [modb_orno3.c:419-428](modb_orno3.c#L419-L428) - odczyt prądów z rejestru 0x16
|
||||||
|
- [modb_orno3.c:469-478](modb_orno3.c#L469-L478) - odczyt częstotliwości z rejestru 0x14
|
||||||
|
|
||||||
|
### **Zgodność z programem Python:**
|
||||||
|
|
||||||
|
| Rejestr | Python | C | Status |
|
||||||
|
|---------|--------|---|--------|
|
||||||
|
| 0x0E (napięcia) | ✓ | ✓ | Zgodne |
|
||||||
|
| 0x16 (prądy) | ✓ | ✓ | **Dodane!** |
|
||||||
|
| 0x1C (moc) | ✓ | ✓ | Zgodne |
|
||||||
|
| 0x100 (energia) | ✓ | ✓ | Zgodne |
|
||||||
|
| 0x14 (częstotliwość) | ✓ | ✓ | **Dodane!** |
|
||||||
|
|
||||||
|
Program C w [modb_orno3.c](modb_orno3.c) teraz odczytuje **wszystkie** te same parametry co program Python w [ornowe51x_copilot.py](ornowe51x_copilot.py)!
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Zweryfikuję działanie programu dla urządzenia SUN2K. Najpierw sprawdzę konfigurację SUN2K w kodzie:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Teraz uruchomię program dla SUN2K:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Świetnie! Program działa dla SUN2K. Widzę, że:
|
||||||
|
|
||||||
|
**✅ Odczyty działają poprawnie:**
|
||||||
|
- Startup time: Sun Jan 25 08:36:23 2026
|
||||||
|
- Shutdown: Sun Jan 25 17:09:27 2026
|
||||||
|
- Current time: Sun Jan 25 20:16:18 2026
|
||||||
|
- Peak Power: 2.274 kW
|
||||||
|
- Accumulated energy: 32583.41 kWh
|
||||||
|
- Daily energy: 6.74 kWh
|
||||||
|
|
||||||
|
**⚠️ Problem:** Początkowe błędy CRC - pierwsze 9-10 prób niepowodzeń, potem program zaczął działać. To przez brak konfiguracji timeoutów i RTS delay dla SUN2K.
|
||||||
|
|
||||||
|
Dodam konfigurację timeoutów dla SUN2K podobnie jak dla ORNO:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Widzę poprawę, ale nadal są problemy z niektórymi rejestrami (32089, 32090). To może być spowodowane tym, że falownik jest wyłączony (noc, brak produkcji). Spróbuję z większym opóźnieniem między odczytami:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Opóźnienie już jest (100ms między odczytami). Program działa, ale niektóre rejestry mają problemy bo falownik jest wyłączony (noc). Zobaczmy czystszy output:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Doskonale! Teraz wszystkie rejestry zostały poprawnie odczytane. Stworzę czytelniejsze podsumowanie wyników:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Widzę niestabilność komunikacji. Sprawdzę autotest dla SUN2K, aby znaleźć optymalne parametry:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Stworzę podsumowanie wyników dla SUN2K:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
## ✅ WERYFIKACJA PROGRAMU DLA SUN2K ZAKOŃCZONA
|
||||||
|
|
||||||
|
### **Konfiguracja SUN2K:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Port: /dev/ttyUSB0
|
||||||
|
Slave Address: 3
|
||||||
|
Baudrate: 9600
|
||||||
|
Parity: None (N)
|
||||||
|
Data bits: 8
|
||||||
|
Stop bits: 1
|
||||||
|
RTS Delay: 5000 µs
|
||||||
|
Response Timeout: 0.9 s
|
||||||
|
Byte Timeout: 2500 µs
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Wyniki testów:**
|
||||||
|
|
||||||
|
| Parametr | Rejestr | Test 1 | Test 2 | Status |
|
||||||
|
|----------|---------|--------|--------|--------|
|
||||||
|
| Startup Time | 32091 | ✓ | ✓ | **Stabilny** |
|
||||||
|
| Status | 32089 | ✓ 40960 | ✓ 40960 | **Stabilny** |
|
||||||
|
| Peak Power | 32078 | ✓ 2.274 kW | ✗ | Niestabilny |
|
||||||
|
| Active Power | 32080 | ✓ 0 kW | ✓ 0 kW | **Stabilny** |
|
||||||
|
| Frequency | 32085 | ✗ | ✗ | **Zawsze błąd** |
|
||||||
|
| Accumulated Energy | 32106 | ✗ | ✓ 32583.41 kWh | Niestabilny |
|
||||||
|
| Daily Energy | 32114 | ✓ 6.74 kWh | ✓ 6.74 kWh | **Stabilny** |
|
||||||
|
|
||||||
|
### **Podsumowanie:**
|
||||||
|
|
||||||
|
**✅ Program działa dla SUN2K** - odczytuje dane z falownika
|
||||||
|
|
||||||
|
**⚠️ Niestabilność komunikacji:**
|
||||||
|
- Niektóre rejestry mają problemy z CRC
|
||||||
|
- Frequency (32085) konsekwentnie zawodzi
|
||||||
|
- Prawdopodobna przyczyna: **falownik jest wyłączony** (noc, brak produkcji słonecznej)
|
||||||
|
|
||||||
|
**✓ Kluczowe dane odczytywane poprawnie:**
|
||||||
|
- Daily Energy: 6.74 kWh (produkcja dzienna)
|
||||||
|
- Accumulated Energy: 32583.41 kWh (całkowita produkcja)
|
||||||
|
- Status: 40960 (urządzenie w trybie standby/nocnym)
|
||||||
|
- Startup: 08:36:23, Shutdown: 17:09:27
|
||||||
|
|
||||||
|
### **Dodane ulepszenia:**
|
||||||
|
|
||||||
|
1. ✅ Konfiguracja timeoutów dla SUN2K (wcześniej brak)
|
||||||
|
2. ✅ Flush bufora przed pierwszym odczytem
|
||||||
|
3. ✅ Wyświetlanie konfiguracji połączenia
|
||||||
|
4. ✅ Retry mechanizm (do 10 prób) dla każdego rejestru
|
||||||
|
|
||||||
|
**Zalecenie:** Test w ciągu dnia (gdy falownik produkuje energię) powinien dać stabilniejsze wyniki dla wszystkich rejestrów.
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Zmodyfikuję program aby uruchamiał się bez parametrów, z predefiniowanymi ustawieniami i automatycznym odczytem ORNO → SUN2K z wysyłką do MQTT.
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Naprawię błędy kompilacji - muszę zamienić wszystkie wystąpienia NO_MQTT na DISABLE_MQTT:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Naprawię błąd w linii 280 - nie można przypisać wartości do DISABLE_MQTT, bo to teraz #define:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Teraz muszę usunąć cały kod parsowania parametrów linii komend, ponieważ program ma działać bez parametrów:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Naprawię błędy - zamienię zmienne na wartości z #define:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Teraz dodam opóźnienie między ORNO a SUN2K:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Teraz skompilujmy i przetestujmy program:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Świetnie! Program działa bez parametrów. Przetestuję jeszcze raz:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Doskonale! Teraz sprawdzę czy MQTT działa (muszę zatrzymać screen przed testem z prawdziwym MQTT):
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Teraz wykonam końcowy test pokazujący pełną sekwencję:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Stworzę dokumentację konfiguracji w osobnym pliku:
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
## ✅ PROGRAM ZMODYFIKOWANY I GOTOWY DO UŻYCIA
|
||||||
|
|
||||||
|
### **Wykonane zmiany:**
|
||||||
|
|
||||||
|
**1. Usunięto parsowanie parametrów linii komend**
|
||||||
|
- Program uruchamia się **bez żadnych argumentów**: `./modborno3`
|
||||||
|
- Wszystkie parametry zdefiniowane jako `#define` w pliku źródłowym
|
||||||
|
|
||||||
|
**2. Dodano sekcję konfiguracyjną** ([modb_orno3.c:11-40](modb_orno3.c#L11-L40))
|
||||||
|
```c
|
||||||
|
#define USB_DEV_DEFAULT "/dev/ttyUSB0"
|
||||||
|
#define ORNO_SLAVE_ADR 2
|
||||||
|
#define SUN2000_SLAVE_ADR 3
|
||||||
|
#define ORNO_RTS_DELAY 5000
|
||||||
|
#define ORNO_BYTE_TIMEOUT 2500
|
||||||
|
#define SUN2K_RTS_DELAY 5000
|
||||||
|
#define SUN2K_BYTE_TIMEOUT 2500
|
||||||
|
#define DELAY_BETWEEN_DEVICES 100000 /* 100ms */
|
||||||
|
#define MQTT_BROKER "10.1.1.1"
|
||||||
|
#define MQTT_PORT 1883
|
||||||
|
#define DISABLE_MQTT 0 /* 0=włączony, 1=wyłączony */
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Automatyczna sekwencja odczytów:**
|
||||||
|
- **ORNO** (pełny odczyt: napięcia, prądy, moc, energia, częstotliwość)
|
||||||
|
- **100ms przerwa** (konfigurowalna)
|
||||||
|
- **SUN2K** (pełny odczyt falownika)
|
||||||
|
|
||||||
|
**4. MQTT automatycznie włączony:**
|
||||||
|
- Dane z ORNO → `/energy/orno/` (U, I, P, W, Hz)
|
||||||
|
- Dane z SUN2K → `/energy/SUN2K`
|
||||||
|
|
||||||
|
### **Test działania:**
|
||||||
|
|
||||||
|
```
|
||||||
|
==============================================
|
||||||
|
Energy Meter Reader - ORNO & SUN2K
|
||||||
|
Start: Sun Jan 25 19:31:54 2026
|
||||||
|
==============================================
|
||||||
|
Configuration:
|
||||||
|
Device: /dev/ttyUSB0
|
||||||
|
ORNO slave address: 2
|
||||||
|
SUN2K slave address: 3
|
||||||
|
MQTT: ENABLED
|
||||||
|
MQTT Broker: 10.1.1.1:1883
|
||||||
|
==============================================
|
||||||
|
|
||||||
|
Voltages: L1=216.2 V, L2=214.0 V, L3=227.2 V
|
||||||
|
Currents: L1=1.29 A, L2=8.95 A, L3=0.92 A
|
||||||
|
Power: Total=2.191 W, L1=0.180 W, L2=1.896 W, L3=0.115 W
|
||||||
|
Energy: Total=27198.211 kWh, L1=10579.260 kWh, L2=8610.290 kWh, L3=8008.660 kWh
|
||||||
|
Frequency: 49.9800 Hz
|
||||||
|
|
||||||
|
Waiting 100 ms before SUN2K...
|
||||||
|
|
||||||
|
=== SUN2K Reading Started ===
|
||||||
|
[... dane z SUN2K ...]
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Jak używać:**
|
||||||
|
|
||||||
|
**Kompilacja:**
|
||||||
|
```bash
|
||||||
|
bash cc-modb_orno3.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Uruchomienie:**
|
||||||
|
```bash
|
||||||
|
./modborno3
|
||||||
|
```
|
||||||
|
|
||||||
|
**Modyfikacja konfiguracji:**
|
||||||
|
1. Edytuj [modb_orno3.c:11-40](modb_orno3.c#L11-L40)
|
||||||
|
2. Przekompiluj: `bash cc-modb_orno3.sh`
|
||||||
|
3. Uruchom: `./modborno3`
|
||||||
|
|
||||||
|
Dokumentacja konfiguracji została zapisana w pliku [CONFIG.md](CONFIG.md).
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
Utworzę plik systemd service wraz z timerem do cyklicznego odczytu co 10 sekund.
|
||||||
|
|
||||||
|
|
||||||
|
## 🤖 Claude Response
|
||||||
|
|
||||||
|
## ✅ Pliki systemd service gotowe!
|
||||||
|
|
||||||
|
### **Utworzone pliki:**
|
||||||
|
|
||||||
|
1. **[read-energy.service](read-energy.service)** - jednostka serwisu systemd
|
||||||
|
- Uruchamia program `modborno3`
|
||||||
|
- Użytkownik: `ms`, grupa: `dialout`
|
||||||
|
- Logi do journald
|
||||||
|
|
||||||
|
2. **[read-energy.timer](read-energy.timer)** - timer systemd
|
||||||
|
- Uruchamia co **10 sekund**
|
||||||
|
- Start po 30s od bootu
|
||||||
|
- Dokładność: 1s
|
||||||
|
|
||||||
|
3. **[install-systemd.sh](install-systemd.sh)** - automatyczny skrypt instalacyjny
|
||||||
|
- Sprawdza wymagania
|
||||||
|
- Instaluje pliki
|
||||||
|
- Włącza i uruchamia timer
|
||||||
|
|
||||||
|
4. **[SYSTEMD_INSTALL.md](SYSTEMD_INSTALL.md)** - pełna dokumentacja
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **INSTALACJA (2 sposoby):**
|
||||||
|
|
||||||
|
#### **Metoda 1: Automatyczna (ZALECANA)**
|
||||||
|
```bash
|
||||||
|
sudo bash install-systemd.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **Metoda 2: Ręczna**
|
||||||
|
```bash
|
||||||
|
# 1. Skopiuj pliki
|
||||||
|
sudo cp read-energy.service /etc/systemd/system/
|
||||||
|
sudo cp read-energy.timer /etc/systemd/system/
|
||||||
|
|
||||||
|
# 2. Przeładuj systemd
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
|
||||||
|
# 3. Włącz i uruchom
|
||||||
|
sudo systemctl enable read-energy.timer
|
||||||
|
sudo systemctl start read-energy.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **SPRAWDZENIE:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Status timera
|
||||||
|
sudo systemctl status read-energy.timer
|
||||||
|
|
||||||
|
# Kiedy następny odczyt
|
||||||
|
systemctl list-timers read-energy.timer
|
||||||
|
|
||||||
|
# Logi w czasie rzeczywistym
|
||||||
|
sudo journalctl -u read-energy.service -f
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **KONFIGURACJA TIMERA:**
|
||||||
|
|
||||||
|
**Plik: read-energy.timer**
|
||||||
|
```ini
|
||||||
|
OnBootSec=30s # Start po 30s od boot
|
||||||
|
OnUnitActiveSec=10s # Powtarzaj co 10 sekund ← TUTAJ ZMIEŃ
|
||||||
|
AccuracySec=1s # Dokładność 1s
|
||||||
|
```
|
||||||
|
|
||||||
|
**Zmiana interwału:**
|
||||||
|
1. Edytuj: `/etc/systemd/system/read-energy.timer`
|
||||||
|
2. Zmień `OnUnitActiveSec=10s` na np. `OnUnitActiveSec=30s`
|
||||||
|
3. Przeładuj:
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl restart read-energy.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **PRZYDATNE KOMENDY:**
|
||||||
|
|
||||||
|
| Akcja | Komenda |
|
||||||
|
|-------|---------|
|
||||||
|
| **Status timera** | `sudo systemctl status read-energy.timer` |
|
||||||
|
| **Status serwisu** | `sudo systemctl status read-energy.service` |
|
||||||
|
| **Logi (realtime)** | `sudo journalctl -u read-energy.service -f` |
|
||||||
|
| **Logi (ostatnie 100)** | `sudo journalctl -u read-energy.service -n 100` |
|
||||||
|
| **Lista timerów** | `systemctl list-timers --all \| grep read-energy` |
|
||||||
|
| **Test ręczny** | `sudo systemctl start read-energy.service` |
|
||||||
|
| **Restart** | `sudo systemctl restart read-energy.timer` |
|
||||||
|
| **Stop** | `sudo systemctl stop read-energy.timer` |
|
||||||
|
| **Wyłącz autostart** | `sudo systemctl disable read-energy.timer` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **JAK DZIAŁA:**
|
||||||
|
|
||||||
|
1. Timer (`read-energy.timer`) uruchamia się co 10 sekund
|
||||||
|
2. Każde uruchomienie wywołuje service (`read-energy.service`)
|
||||||
|
3. Service uruchamia program `modborno3`
|
||||||
|
4. Program odczytuje ORNO → czeka 100ms → odczytuje SUN2K
|
||||||
|
5. Wysyła dane do MQTT (10.1.1.1:1883)
|
||||||
|
6. Kończy działanie
|
||||||
|
7. Timer czeka 10 sekund i powtarza
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
✅ **Wszystko gotowe do instalacji!** Uruchom: `sudo bash install-systemd.sh`
|
||||||
|
|
||||||
59
Opis_Utworzenia_html_z_cache_vscode_w_formacie_jsonl.md
Normal file
59
Opis_Utworzenia_html_z_cache_vscode_w_formacie_jsonl.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# 1. Stwórz Markdown z JSONL
|
||||||
|
cat > ~/claude_to_markdown.sh << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
INPUT="$1"
|
||||||
|
OUTPUT="$2"
|
||||||
|
|
||||||
|
cat > "$OUTPUT" << 'MD_START'
|
||||||
|
# Claude Code Conversation - Program modb_orno3.c
|
||||||
|
|
||||||
|
**Data:** 25 stycznia 2026
|
||||||
|
**Temat:** Analiza i modyfikacja programu do odczytu mierników energii ORNO i SUN2K
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
MD_START
|
||||||
|
|
||||||
|
jq -r '
|
||||||
|
select(.type == "user" or .type == "assistant") |
|
||||||
|
if .type == "user" then
|
||||||
|
"\n## 👤 User Query\n\n```\n" + (.message.content // "") + "\n```\n"
|
||||||
|
elif .type == "assistant" then
|
||||||
|
"\n## 🤖 Claude Response\n\n" + (
|
||||||
|
if (.message.content | type) == "array" then
|
||||||
|
(.message.content[] | select(.type == "text") | .text)
|
||||||
|
else
|
||||||
|
(.message.content // "")
|
||||||
|
end
|
||||||
|
) + "\n"
|
||||||
|
else
|
||||||
|
empty
|
||||||
|
end
|
||||||
|
' "$INPUT" >> "$OUTPUT"
|
||||||
|
|
||||||
|
echo "✓ Markdown saved to: $OUTPUT"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x ~/claude_to_markdown.sh
|
||||||
|
|
||||||
|
# 2. Generuj Markdown
|
||||||
|
~/claude_to_markdown.sh \
|
||||||
|
~/.claude/projects/-home-ms-projekty-python3-modebus-ornoserial3/3f3d08dc-f007-453b-8bd3-4aa0b1158050.jsonl \
|
||||||
|
~/projekty/python3/modebus/ornoserial3/Claude_conversation_full.md
|
||||||
|
|
||||||
|
# 3. Otwórz w VSCode
|
||||||
|
code ~/projekty/python3/modebus/ornoserial3/Claude_conversation_full.md
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "TERAZ W VSCODE:"
|
||||||
|
echo "=========================================="
|
||||||
|
echo "1. Zainstaluj rozszerzenie 'Markdown PDF' (yzane.markdown-pdf)"
|
||||||
|
echo " - Ctrl+Shift+X → wyszukaj 'Markdown PDF' → Install"
|
||||||
|
echo ""
|
||||||
|
echo "2. Eksportuj do PDF:"
|
||||||
|
echo " - Ctrl+Shift+P → 'Markdown PDF: Export (pdf)'"
|
||||||
|
echo ""
|
||||||
|
echo "3. PDF pojawi się w:"
|
||||||
|
echo " ~/projekty/python3/modebus/ornoserial3/Claude_conversation_full.pdf"
|
||||||
|
echo "=========================================="
|
||||||
189
SYSTEMD_INSTALL.md
Normal file
189
SYSTEMD_INSTALL.md
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
# Instalacja i konfiguracja systemd service
|
||||||
|
|
||||||
|
## Pliki
|
||||||
|
|
||||||
|
- `read-energy.service` - jednostka serwisu, która uruchamia program modborno3
|
||||||
|
- `read-energy.timer` - timer uruchamiający service co 10 sekund
|
||||||
|
|
||||||
|
## Instalacja
|
||||||
|
|
||||||
|
### 1. Skopiuj pliki do systemd
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp read-energy.service /etc/systemd/system/
|
||||||
|
sudo cp read-energy.timer /etc/systemd/system/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Przeładuj konfigurację systemd
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Włącz timer (autostart)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl enable read-energy.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Uruchom timer
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl start read-energy.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
## Zarządzanie
|
||||||
|
|
||||||
|
### Sprawdzenie statusu
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Status timera
|
||||||
|
sudo systemctl status read-energy.timer
|
||||||
|
|
||||||
|
# Status serwisu
|
||||||
|
sudo systemctl status read-energy.service
|
||||||
|
|
||||||
|
# Lista aktywnych timerów
|
||||||
|
systemctl list-timers --all | grep read-energy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Zatrzymanie
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Zatrzymaj timer (przestanie uruchamiać odczyty)
|
||||||
|
sudo systemctl stop read-energy.timer
|
||||||
|
|
||||||
|
# Wyłącz autostart
|
||||||
|
sudo systemctl disable read-energy.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ponowne uruchomienie
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl restart read-energy.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logi
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pokaż logi z ostatniego odczytu
|
||||||
|
sudo journalctl -u read-energy.service -n 50
|
||||||
|
|
||||||
|
# Logi w czasie rzeczywistym
|
||||||
|
sudo journalctl -u read-energy.service -f
|
||||||
|
|
||||||
|
# Logi z ostatniej godziny
|
||||||
|
sudo journalctl -u read-energy.service --since "1 hour ago"
|
||||||
|
|
||||||
|
# Logi z dzisiaj
|
||||||
|
sudo journalctl -u read-energy.service --since today
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testowanie
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ręczne uruchomienie (test)
|
||||||
|
sudo systemctl start read-energy.service
|
||||||
|
|
||||||
|
# Sprawdź czy działa
|
||||||
|
sudo systemctl status read-energy.service
|
||||||
|
|
||||||
|
# Zobacz logi
|
||||||
|
sudo journalctl -u read-energy.service -n 100 --no-pager
|
||||||
|
```
|
||||||
|
|
||||||
|
## Modyfikacja interwału
|
||||||
|
|
||||||
|
Aby zmienić interwał z 10s na inny:
|
||||||
|
|
||||||
|
1. Edytuj plik `/etc/systemd/system/read-energy.timer`
|
||||||
|
2. Zmień wartość `OnUnitActiveSec=10s` (np. `OnUnitActiveSec=30s` dla 30 sekund)
|
||||||
|
3. Przeładuj i zrestartuj:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl restart read-energy.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugowanie
|
||||||
|
|
||||||
|
### Sprawdź czy timer jest aktywny
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl is-active read-energy.timer
|
||||||
|
systemctl is-enabled read-energy.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sprawdź kiedy następny odczyt
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl list-timers read-energy.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sprawdź błędy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo journalctl -u read-energy.service -p err -n 50
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test połączenia Modbus
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ręcznie uruchom program
|
||||||
|
cd /home/ms/projekty/python3/modebus/ornoserial3
|
||||||
|
./modborno3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Wymagania
|
||||||
|
|
||||||
|
- Program `modborno3` musi być skompilowany i dostępny w:
|
||||||
|
`/home/ms/projekty/python3/modebus/ornoserial3/modborno3`
|
||||||
|
- Użytkownik `ms` musi być w grupie `dialout`
|
||||||
|
- Serwer MQTT musi być dostępny (10.1.1.1:1883)
|
||||||
|
- Port `/dev/ttyUSB0` musi być dostępny
|
||||||
|
|
||||||
|
### Sprawdź uprawnienia
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Sprawdź czy użytkownik jest w grupie dialout
|
||||||
|
groups ms
|
||||||
|
|
||||||
|
# Jeśli nie, dodaj:
|
||||||
|
sudo usermod -a -G dialout ms
|
||||||
|
|
||||||
|
# Wyloguj i zaloguj się ponownie
|
||||||
|
```
|
||||||
|
|
||||||
|
## Konfiguracja
|
||||||
|
|
||||||
|
Aby zmienić parametry (port, adresy Modbus, MQTT), edytuj plik:
|
||||||
|
`/home/ms/projekty/python3/modebus/ornoserial3/modb_orno3.c`
|
||||||
|
|
||||||
|
Sekcja konfiguracji znajduje się w liniach 11-42 (#define).
|
||||||
|
|
||||||
|
Po zmianie:
|
||||||
|
```bash
|
||||||
|
cd /home/ms/projekty/python3/modebus/ornoserial3
|
||||||
|
bash cc-modb_orno3.sh
|
||||||
|
sudo systemctl restart read-energy.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitorowanie w czasie rzeczywistym
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Terminal 1: logi
|
||||||
|
sudo journalctl -u read-energy.service -f
|
||||||
|
|
||||||
|
# Terminal 2: status
|
||||||
|
watch -n 2 'systemctl status read-energy.timer && echo && systemctl list-timers read-energy.timer'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usunięcie
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl stop read-energy.timer
|
||||||
|
sudo systemctl disable read-energy.timer
|
||||||
|
sudo rm /etc/systemd/system/read-energy.service
|
||||||
|
sudo rm /etc/systemd/system/read-energy.timer
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
```
|
||||||
BIN
__pycache__/crc16.cpython-313.pyc
Normal file
BIN
__pycache__/crc16.cpython-313.pyc
Normal file
Binary file not shown.
BIN
__pycache__/energymeter.cpython-313.pyc
Executable file
BIN
__pycache__/energymeter.cpython-313.pyc
Executable file
Binary file not shown.
BIN
__pycache__/mbusbase.cpython-313.pyc
Normal file
BIN
__pycache__/mbusbase.cpython-313.pyc
Normal file
Binary file not shown.
BIN
__pycache__/mbusclient.cpython-313.pyc
Normal file
BIN
__pycache__/mbusclient.cpython-313.pyc
Normal file
Binary file not shown.
1
cc-modb_orno3.sh
Executable file
1
cc-modb_orno3.sh
Executable file
@@ -0,0 +1 @@
|
|||||||
|
gcc -g -I /usr/include/modbus modb_orno3.c -lmosquitto -lmodbus -o modborno3
|
||||||
32
claude_markdown.sh
Executable file
32
claude_markdown.sh
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
INPUT="$1"
|
||||||
|
OUTPUT="$2"
|
||||||
|
|
||||||
|
cat > "$OUTPUT" << 'MD_START'
|
||||||
|
---
|
||||||
|
title: "Claude Code Conversation - modb_orno3"
|
||||||
|
author: "Claude & User"
|
||||||
|
date: "25 stycznia 2026"
|
||||||
|
geometry: margin=2cm
|
||||||
|
---
|
||||||
|
|
||||||
|
MD_START
|
||||||
|
|
||||||
|
jq -r '
|
||||||
|
select(.type == "user" or .type == "assistant") |
|
||||||
|
if .type == "user" then
|
||||||
|
"\n## 👤 User Query\n\n```\n" + (.message.content // "") + "\n```\n"
|
||||||
|
elif .type == "assistant" then
|
||||||
|
"\n## 🤖 Claude Response\n\n" + (
|
||||||
|
if (.message.content | type) == "array" then
|
||||||
|
(.message.content[] | select(.type == "text") | .text)
|
||||||
|
else
|
||||||
|
(.message.content // "")
|
||||||
|
end
|
||||||
|
) + "\n"
|
||||||
|
else
|
||||||
|
empty
|
||||||
|
end
|
||||||
|
' "$INPUT" >> "$OUTPUT"
|
||||||
|
|
||||||
|
echo "✓ Markdown saved to: $OUTPUT"
|
||||||
49
crc16.py
Normal file
49
crc16.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
INITIAL_MODBUS = 0xFFFF
|
||||||
|
INITIAL_DF1 = 0x0000
|
||||||
|
|
||||||
|
table = (
|
||||||
|
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
|
||||||
|
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
|
||||||
|
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
|
||||||
|
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
|
||||||
|
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
|
||||||
|
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
|
||||||
|
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
|
||||||
|
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
|
||||||
|
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
|
||||||
|
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
|
||||||
|
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
|
||||||
|
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
|
||||||
|
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
|
||||||
|
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
|
||||||
|
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
|
||||||
|
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
|
||||||
|
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
|
||||||
|
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
|
||||||
|
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
|
||||||
|
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
|
||||||
|
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
|
||||||
|
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
|
||||||
|
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
|
||||||
|
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
|
||||||
|
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
|
||||||
|
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
|
||||||
|
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
|
||||||
|
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
|
||||||
|
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
|
||||||
|
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
|
||||||
|
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
|
||||||
|
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 )
|
||||||
|
|
||||||
|
def calcByte( charek, crc):
|
||||||
|
if type(charek) == type("c"):
|
||||||
|
bajt = ord( charek)
|
||||||
|
else:
|
||||||
|
bajt = charek
|
||||||
|
crc = (crc >> 8) ^ table[(crc ^ bajt) & 0xFF]
|
||||||
|
return (crc & 0xFFFF)
|
||||||
|
|
||||||
|
def calcString( st, crc):
|
||||||
|
for charek in st:
|
||||||
|
crc = (crc >> 8) ^ table[(crc ^ ord(charek)) & 0xFF]
|
||||||
|
return crc
|
||||||
102
energymeter.py
Normal file
102
energymeter.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
# ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||
|
# Copyright (C) 2017 Piotr Murawski
|
||||||
|
# Niniejszy program jest wolnym oprogramowaniem; mozesz go
|
||||||
|
# rozprowadzac dalej i/lub modyfikowac na warunkach Powszechnej
|
||||||
|
# Licencji Publicznej GNU, wydanej przez Fundacje Wolnego
|
||||||
|
# Oprogramowania - wedlug wersji 3-ciej tej Licencji lub ktorejs
|
||||||
|
# z pozniejszych wersji.
|
||||||
|
# Niniejszy program rozpowszechniany jest z nadzieja iz bedzie on
|
||||||
|
# uzyteczny - jednak BEZ JAKIEJKOLWIEK GWARANCJI, nawet domyslnej
|
||||||
|
# gwarancji PRZYDATNOSCI HANDLOWEJ albo PRZYDATNOSCI DO OKRESLONYCH
|
||||||
|
# ZASTOSOWAN. W celu uzyskania blizszych informacji - Powszechna
|
||||||
|
# Licencja Publiczna GNU.
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
# Z pewnoscia wraz z niniejszym programem otrzymales tez egzemplarz
|
||||||
|
# Powszechnej Licencji Publicznej GNU (GNU General Public License);
|
||||||
|
# jesli nie - napisz do Free Software Foundation, Inc., 675 Mass Ave,
|
||||||
|
# Cambridge, MA 02139, USA.
|
||||||
|
# Powszechna Licencja GNU dostepna jest rowniez na stronie:
|
||||||
|
# http://www.gnu.org/licenses/licenses.html
|
||||||
|
# nieoficjalne polskie tlumaczenie na
|
||||||
|
# http://www.gnu.org.pl
|
||||||
|
# ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||
|
|
||||||
|
import time
|
||||||
|
import mbusbase
|
||||||
|
import mbusclient
|
||||||
|
from mbusbase import bytestr
|
||||||
|
|
||||||
|
|
||||||
|
energyclient = None
|
||||||
|
|
||||||
|
|
||||||
|
def createClient(portDevice, adres):
|
||||||
|
tecl = mbusclient.mbclientserial()
|
||||||
|
tecl.adres = adres
|
||||||
|
tecl.transportOpen(portDevice, brate=9600)
|
||||||
|
return tecl
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
print("ERROR: can't get modbus client to work!")
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def readRegs(client, start, cnt):
|
||||||
|
r03 = mbusbase.request03(client.adres, start, cnt)
|
||||||
|
bytes=r03.rtu();
|
||||||
|
client.transportSend(bytes)
|
||||||
|
reply=client.getReply()
|
||||||
|
if reply:
|
||||||
|
if reply.fail:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return reply.vals
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def writeRegs(client, addr, regs):
|
||||||
|
r16 = mbusbase.request16(client.adres,addr,regs)
|
||||||
|
bytes=r16.rtu();
|
||||||
|
client.transportSend(bytes)
|
||||||
|
reply=client.getReply()
|
||||||
|
if reply:
|
||||||
|
if reply.fail:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return reply.vals
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def testPort(portDevice, adres):
|
||||||
|
mbcli = createClient(portDevice, adres)
|
||||||
|
if mbcli!=None:
|
||||||
|
regs = readRegs(mbcli, 0x40, 1)
|
||||||
|
mbcli.transportClose()
|
||||||
|
if regs!=None:
|
||||||
|
if len(regs)>=1:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def startClient(portDevice, adres):
|
||||||
|
print("Energy MODBUS: " + portDevice)
|
||||||
|
mbcli = createClient(portDevice, adres)
|
||||||
|
if mbcli!=None:
|
||||||
|
global energyclient
|
||||||
|
energyclient = mbcli
|
||||||
|
return mbcli
|
||||||
78
install-systemd.sh
Executable file
78
install-systemd.sh
Executable file
@@ -0,0 +1,78 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Skrypt instalacji systemd service dla read-energy
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo "Instalacja read-energy systemd service"
|
||||||
|
echo "========================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Sprawdź czy jesteś użytkownikiem root lub masz sudo
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo "Uruchom jako root lub z sudo:"
|
||||||
|
echo " sudo bash install-systemd.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Sprawdź czy program istnieje
|
||||||
|
PROG_PATH="/home/ms/projekty/python3/modebus/ornoserial3/modborno3"
|
||||||
|
if [ ! -f "$PROG_PATH" ]; then
|
||||||
|
echo "ERROR: Program nie istnieje: $PROG_PATH"
|
||||||
|
echo "Najpierw skompiluj program:"
|
||||||
|
echo " cd /home/ms/projekty/python3/modebus/ornoserial3"
|
||||||
|
echo " bash cc-modb_orno3.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✓ Program znaleziony: $PROG_PATH"
|
||||||
|
|
||||||
|
# Sprawdź czy użytkownik ms jest w grupie dialout
|
||||||
|
if ! groups ms | grep -q dialout; then
|
||||||
|
echo "⚠ Użytkownik 'ms' nie jest w grupie 'dialout'"
|
||||||
|
echo " Dodaję do grupy..."
|
||||||
|
usermod -a -G dialout ms
|
||||||
|
echo "✓ Dodano do grupy dialout (wymagane ponowne logowanie)"
|
||||||
|
else
|
||||||
|
echo "✓ Użytkownik 'ms' jest w grupie 'dialout'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Kopiuj pliki
|
||||||
|
echo ""
|
||||||
|
echo "Kopiowanie plików do /etc/systemd/system/..."
|
||||||
|
cp -v read-energy.service /etc/systemd/system/
|
||||||
|
cp -v read-energy.timer /etc/systemd/system/
|
||||||
|
|
||||||
|
# Przeładuj systemd
|
||||||
|
echo ""
|
||||||
|
echo "Przeładowywanie systemd..."
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
# Włącz timer
|
||||||
|
echo ""
|
||||||
|
echo "Włączanie timera (autostart)..."
|
||||||
|
systemctl enable read-energy.timer
|
||||||
|
|
||||||
|
# Uruchom timer
|
||||||
|
echo ""
|
||||||
|
echo "Uruchamianie timera..."
|
||||||
|
systemctl start read-energy.timer
|
||||||
|
|
||||||
|
# Status
|
||||||
|
echo ""
|
||||||
|
echo "========================================"
|
||||||
|
echo "Instalacja zakończona!"
|
||||||
|
echo "========================================"
|
||||||
|
echo ""
|
||||||
|
systemctl status read-energy.timer --no-pager
|
||||||
|
echo ""
|
||||||
|
echo "Timer będzie uruchamiał odczyt co 10 sekund."
|
||||||
|
echo ""
|
||||||
|
echo "Komendy:"
|
||||||
|
echo " sudo systemctl status read-energy.timer - status timera"
|
||||||
|
echo " sudo systemctl status read-energy.service - status serwisu"
|
||||||
|
echo " sudo journalctl -u read-energy.service -f - logi w czasie rzeczywistym"
|
||||||
|
echo " systemctl list-timers read-energy.timer - kiedy następny odczyt"
|
||||||
|
echo ""
|
||||||
|
echo "Więcej informacji: SYSTEMD_INSTALL.md"
|
||||||
496
mbusbase.py
Normal file
496
mbusbase.py
Normal file
@@ -0,0 +1,496 @@
|
|||||||
|
#!/usr//bin//env python
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
# ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||
|
# Copyright (C) 2017 Piotr Murawski
|
||||||
|
# Niniejszy program jest wolnym oprogramowaniem; mozesz go
|
||||||
|
# rozprowadzac dalej i//lub modyfikowac na warunkach Powszechnej
|
||||||
|
# Licencji Publicznej GNU, wydanej przez Fundacje Wolnego
|
||||||
|
# Oprogramowania - wedlug wersji 3-ciej tej Licencji lub ktorejs
|
||||||
|
# z pozniejszych wersji.
|
||||||
|
# Niniejszy program rozpowszechniany jest z nadzieja iz bedzie on
|
||||||
|
# uzyteczny - jednak BEZ JAKIEJKOLWIEK GWARANCJI, nawet domyslnej
|
||||||
|
# gwarancji PRZYDATNOSCI HANDLOWEJ albo PRZYDATNOSCI DO OKRESLONYCH
|
||||||
|
# ZASTOSOWAN. W celu uzyskania blizszych informacji - Powszechna
|
||||||
|
# Licencja Publiczna GNU.
|
||||||
|
# This program is free software: you can redistribute it and//or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
# Z pewnoscia wraz z niniejszym programem otrzymales tez egzemplarz
|
||||||
|
# Powszechnej Licencji Publicznej GNU (GNU General Public License);
|
||||||
|
# jesli nie - napisz do Free Software Foundation, Inc., 675 Mass Ave,
|
||||||
|
# Cambridge, MA 02139, USA.
|
||||||
|
# Powszechna Licencja GNU dostepna jest rowniez na stronie:
|
||||||
|
# http://www.gnu.org//licenses//licenses.html
|
||||||
|
# nieoficjalne polskie tlumaczenie na
|
||||||
|
# http://www.gnu.org.pl
|
||||||
|
# ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||
|
|
||||||
|
from crc16 import *
|
||||||
|
import ctypes
|
||||||
|
|
||||||
|
|
||||||
|
FAILCODE_CRC = 1
|
||||||
|
FAILCODE_REPLY = 3
|
||||||
|
|
||||||
|
|
||||||
|
class MbusbaseFail(Exception):
|
||||||
|
def __init__(self, message, code, fail=0, reply=None):
|
||||||
|
super(MbusbaseFail, self).__init__(message)
|
||||||
|
self.code = code
|
||||||
|
self.fail = reply
|
||||||
|
self.reply = reply
|
||||||
|
|
||||||
|
def bytestr(bytes):
|
||||||
|
ot=''
|
||||||
|
for by in bytes:
|
||||||
|
ot=ot+('%02X ' % by)
|
||||||
|
return ot
|
||||||
|
|
||||||
|
|
||||||
|
class errcode(object):
|
||||||
|
NOERROR =0x00
|
||||||
|
ILLEGAL_FUNCTION =0x01
|
||||||
|
ILLEGAL_DATA_ADDRESS =0x02
|
||||||
|
ILLEGAL_DATA_VALUE =0x03
|
||||||
|
SLAVE_DEVICE_FAILURE =0x04
|
||||||
|
ACKNOWLEDGE =0x05
|
||||||
|
SLAVE_DEVICE_BUSY =0x06
|
||||||
|
MEMORY_PARITY_ERROR =0x08
|
||||||
|
GATEWAY_PATH_UNAVAILABLE =0x0A
|
||||||
|
GATEWAY_TARGET_FAILED_TO_RESPOND =0x0B
|
||||||
|
names={
|
||||||
|
NOERROR :'no error',
|
||||||
|
ILLEGAL_FUNCTION :'illegal function',
|
||||||
|
ILLEGAL_DATA_ADDRESS :'bad data address',
|
||||||
|
ILLEGAL_DATA_VALUE :'bad data value',
|
||||||
|
SLAVE_DEVICE_FAILURE :'slave failure',
|
||||||
|
ACKNOWLEDGE :'acknowledge',
|
||||||
|
SLAVE_DEVICE_BUSY :'slave device busy',
|
||||||
|
MEMORY_PARITY_ERROR :'memory parity',
|
||||||
|
GATEWAY_PATH_UNAVAILABLE :'gateway path unavailable',
|
||||||
|
GATEWAY_TARGET_FAILED_TO_RESPOND :'gateway target failed to respond',
|
||||||
|
}
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def makeFrame8(addr, code, data):
|
||||||
|
lent=1+1+len(data);
|
||||||
|
bytes=bytearray(lent);
|
||||||
|
bytes[0] = addr;
|
||||||
|
bytes[1] = code;
|
||||||
|
idx=0;
|
||||||
|
for dat in data:
|
||||||
|
bytes[2] = dat & 0xFF;
|
||||||
|
idx+=1
|
||||||
|
return bytes
|
||||||
|
|
||||||
|
|
||||||
|
def makeFrame16(addr, code, data):
|
||||||
|
lent=1+1+len(data)*2;
|
||||||
|
bytes=bytearray(lent);
|
||||||
|
bytes[0] = addr;
|
||||||
|
bytes[1] = code;
|
||||||
|
idx=0;
|
||||||
|
for dat in data:
|
||||||
|
bytes[2+idx] = dat >> 8;
|
||||||
|
bytes[3+idx] = dat & 0xFF;
|
||||||
|
idx+=2
|
||||||
|
return bytes
|
||||||
|
|
||||||
|
|
||||||
|
def makeFrameAppendStart(addr, code):
|
||||||
|
bytes=bytearray();
|
||||||
|
if addr:
|
||||||
|
bytes.append(addr)
|
||||||
|
bytes.append(code)
|
||||||
|
return bytes
|
||||||
|
|
||||||
|
|
||||||
|
def makeFrameAppend8(data, value):
|
||||||
|
data.append(value)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def makeFrameAppend16(data, value):
|
||||||
|
if value<0: value=ctypes.c_ushort(value).value
|
||||||
|
data.append(value >> 8)
|
||||||
|
data.append(value & 0xFF)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def makeRtuFrame(frmabytes):
|
||||||
|
lent=len(frmabytes)
|
||||||
|
frmabytes.append(0)
|
||||||
|
frmabytes.append(0)
|
||||||
|
crc = INITIAL_MODBUS
|
||||||
|
for ix in range(0,lent):
|
||||||
|
crc = calcByte( frmabytes[ix], crc)
|
||||||
|
frmabytes[lent] = crc & 0xFF
|
||||||
|
frmabytes[lent+1] = crc >> 8
|
||||||
|
return frmabytes
|
||||||
|
|
||||||
|
|
||||||
|
def makeTcpFrame(frmabytes, sequence):
|
||||||
|
lent=len(frmabytes)
|
||||||
|
frmabytes.insert(0,lent)
|
||||||
|
frmabytes.insert(0,0)
|
||||||
|
frmabytes.insert(0,0)
|
||||||
|
frmabytes.insert(0,0)
|
||||||
|
frmabytes.insert(0,sequence & 0xFF)
|
||||||
|
frmabytes.insert(0,sequence >> 8)
|
||||||
|
return frmabytes
|
||||||
|
|
||||||
|
|
||||||
|
def get16(arrayin):
|
||||||
|
try:
|
||||||
|
return (arrayin[2:], (arrayin[0]<<8) | arrayin[1])
|
||||||
|
except:
|
||||||
|
return (arrayin, None)
|
||||||
|
|
||||||
|
def get8(arrayin):
|
||||||
|
try:
|
||||||
|
return (arrayin[1:], arrayin[0])
|
||||||
|
except:
|
||||||
|
return (arrayin, None)
|
||||||
|
|
||||||
|
|
||||||
|
class framebase(object):
|
||||||
|
def __init__(self,adres,code):
|
||||||
|
self.bytes = makeFrameAppendStart(adres,code)
|
||||||
|
self.tcpseq=0
|
||||||
|
pass
|
||||||
|
def rtu(self):
|
||||||
|
return makeRtuFrame(self.bytes)
|
||||||
|
def tcp(self, sequence):
|
||||||
|
self.tcpseq=sequence
|
||||||
|
return makeTcpFrame(self.bytes, sequence)
|
||||||
|
|
||||||
|
|
||||||
|
class response(framebase):
|
||||||
|
reqCode = 0x01
|
||||||
|
|
||||||
|
|
||||||
|
#-----------------------------------------------------
|
||||||
|
# 80 - error response
|
||||||
|
|
||||||
|
class responseError(response):
|
||||||
|
def __init__(self,adres,reqCodeRep,errCodek):
|
||||||
|
self.reqCode = reqCodeRep | 0x80
|
||||||
|
super(responseError,self).__init__(adres,self.reqCode)
|
||||||
|
self.bytes = makeFrameAppend8(self.bytes, errCodek)
|
||||||
|
|
||||||
|
|
||||||
|
#-----------------------------------------------------
|
||||||
|
# 01 - read coils
|
||||||
|
|
||||||
|
class request01(framebase):
|
||||||
|
reqCode = 0x01
|
||||||
|
def __init__(self,adres,val16_1,val16_2):
|
||||||
|
super(request01,self).__init__(adres,self.reqCode)
|
||||||
|
self.bytes = makeFrameAppend16(self.bytes, val16_1)
|
||||||
|
self.bytes = makeFrameAppend16(self.bytes, val16_2)
|
||||||
|
#print(bytestr(self.bytes))
|
||||||
|
|
||||||
|
class response01(response):
|
||||||
|
reqCode = 0x01
|
||||||
|
def __init__(self,adres,coilsarr):
|
||||||
|
super(response01,self).__init__(adres,self.reqCode)
|
||||||
|
coilcnt = len(coilsarr)
|
||||||
|
bytescnt = 1+((coilcnt -1 ) >> 3)
|
||||||
|
self.bytes = makeFrameAppend8(self.bytes, bytescnt)
|
||||||
|
byteidx=0
|
||||||
|
coilbyte=0
|
||||||
|
for coil in coilsarr:
|
||||||
|
if coil:
|
||||||
|
coilbyte |= 1 << byteidx
|
||||||
|
byteidx+=1
|
||||||
|
if byteidx==8:
|
||||||
|
self.bytes = makeFrameAppend8(self.bytes, coilbyte)
|
||||||
|
byteidx=0
|
||||||
|
coilbyte = 0
|
||||||
|
if byteidx:
|
||||||
|
self.bytes = makeFrameAppend8(self.bytes, coilbyte)
|
||||||
|
|
||||||
|
|
||||||
|
#-----------------------------------------------------
|
||||||
|
# 02 - read discrete inputs
|
||||||
|
|
||||||
|
class request02(request01):
|
||||||
|
reqCode = 0x02
|
||||||
|
|
||||||
|
|
||||||
|
class response02(response01):
|
||||||
|
reqCode = 0x02
|
||||||
|
|
||||||
|
|
||||||
|
#-----------------------------------------------------
|
||||||
|
# 03 - read holding registers
|
||||||
|
|
||||||
|
class request03(request01):
|
||||||
|
reqCode = 0x03
|
||||||
|
|
||||||
|
class response03(response):
|
||||||
|
reqCode = 0x03
|
||||||
|
def __init__(self,adres,regarr):
|
||||||
|
super(response03,self).__init__(adres,self.reqCode)
|
||||||
|
regcnt = len(regarr)
|
||||||
|
self.bytes = makeFrameAppend8(self.bytes, 2*regcnt)
|
||||||
|
for reg in regarr:
|
||||||
|
self.bytes = makeFrameAppend16(self.bytes, reg)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#-----------------------------------------------------
|
||||||
|
# 04 - read input register
|
||||||
|
|
||||||
|
class request04(request01):
|
||||||
|
reqCode = 0x04
|
||||||
|
|
||||||
|
class response04(response03):
|
||||||
|
reqCode = 0x04
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#-----------------------------------------------------
|
||||||
|
# 05 - write single coil
|
||||||
|
|
||||||
|
class request05(request01):
|
||||||
|
reqCode = 0x05
|
||||||
|
def __init__(self,adres,addr,state):
|
||||||
|
super(request01,self).__init__(adres,self.reqCode)
|
||||||
|
statenumber=0x0000
|
||||||
|
if state:
|
||||||
|
statenumber=0xFF00 # as specified in the standard
|
||||||
|
self.bytes = makeFrameAppend16(self.bytes, addr)
|
||||||
|
self.bytes = makeFrameAppend16(self.bytes, statenumber)
|
||||||
|
pass
|
||||||
|
|
||||||
|
class response05(response):
|
||||||
|
reqCode = 0x05
|
||||||
|
def __init__(self,adres,addr,state):
|
||||||
|
super(response05,self).__init__(adres,self.reqCode)
|
||||||
|
statenumber=0x0000
|
||||||
|
if state:
|
||||||
|
statenumber=0xFF00 # as specified in the standard
|
||||||
|
self.bytes = makeFrameAppend16(self.bytes, addr)
|
||||||
|
self.bytes = makeFrameAppend16(self.bytes, statenumber)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#-----------------------------------------------------
|
||||||
|
# 06 - write single register
|
||||||
|
|
||||||
|
class request06(request01):
|
||||||
|
reqCode = 0x06
|
||||||
|
|
||||||
|
class response06(response):
|
||||||
|
reqCode = 0x06
|
||||||
|
def __init__(self,adres,addr,value):
|
||||||
|
super(response06,self).__init__(adres,self.reqCode)
|
||||||
|
self.bytes = makeFrameAppend16(self.bytes, addr)
|
||||||
|
self.bytes = makeFrameAppend16(self.bytes, value)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#-----------------------------------------------------
|
||||||
|
# 15 - write multiple coils
|
||||||
|
|
||||||
|
class request15(framebase):
|
||||||
|
reqCode = 0x0F
|
||||||
|
def __init__(self,adres,startaddr,coilsarr):
|
||||||
|
super(request15,self).__init__(adres,self.reqCode)
|
||||||
|
self.bytes = makeFrameAppend16(self.bytes, startaddr)
|
||||||
|
coilcnt = len(coilsarr)
|
||||||
|
self.bytes = makeFrameAppend16(self.bytes, coilcnt)
|
||||||
|
bytescnt = 1+((coilcnt -1 ) >> 3)
|
||||||
|
self.bytes = makeFrameAppend8(self.bytes, bytescnt)
|
||||||
|
byteidx=0
|
||||||
|
coilbyte=0
|
||||||
|
for coil in coilsarr:
|
||||||
|
if coil:
|
||||||
|
coilbyte |= 1 << byteidx
|
||||||
|
byteidx+=1
|
||||||
|
if byteidx==8:
|
||||||
|
self.bytes = makeFrameAppend8(self.bytes, coilbyte)
|
||||||
|
byteidx=0
|
||||||
|
coilbyte = 0
|
||||||
|
if byteidx:
|
||||||
|
self.bytes = makeFrameAppend8(self.bytes, coilbyte)
|
||||||
|
|
||||||
|
class response15(response):
|
||||||
|
reqCode = 0x0F
|
||||||
|
def __init__(self,adres,addr,quantity):
|
||||||
|
super(response15,self).__init__(adres,self.reqCode)
|
||||||
|
self.bytes = makeFrameAppend16(self.bytes, addr)
|
||||||
|
self.bytes = makeFrameAppend16(self.bytes, quantity)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#-----------------------------------------------------
|
||||||
|
# 16 - write multiple registers
|
||||||
|
|
||||||
|
class request16(framebase):
|
||||||
|
reqCode = 0x10
|
||||||
|
def __init__(self,adres,startaddr,regarr):
|
||||||
|
super(request16,self).__init__(adres,self.reqCode)
|
||||||
|
self.bytes = makeFrameAppend16(self.bytes, startaddr)
|
||||||
|
regcnt = len(regarr)
|
||||||
|
self.bytes = makeFrameAppend16(self.bytes, regcnt)
|
||||||
|
self.bytes = makeFrameAppend8(self.bytes, 2*regcnt)
|
||||||
|
for reg in regarr:
|
||||||
|
self.bytes = makeFrameAppend16(self.bytes, reg)
|
||||||
|
|
||||||
|
class response16(response15):
|
||||||
|
reqCode = 0x10
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class received(object):
|
||||||
|
FAIL_OK = 0
|
||||||
|
FAIL_CRC = 1
|
||||||
|
FAIL_BAD = 2
|
||||||
|
FAIL_REPLY = 3
|
||||||
|
fcode = 0
|
||||||
|
address = 0
|
||||||
|
fail = 0
|
||||||
|
failCode= 0
|
||||||
|
data = None
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
#-----------------------------------------------------
|
||||||
|
# Parse RTU
|
||||||
|
|
||||||
|
class receivedRtu(received):
|
||||||
|
def __init__(self,rxbytes, nocrc=False):
|
||||||
|
super(receivedRtu,self).__init__()
|
||||||
|
#print("RX:::" + bytestr(rxbytes))
|
||||||
|
rxcnt = len(rxbytes)
|
||||||
|
limit=4
|
||||||
|
if nocrc: limit=3
|
||||||
|
if rxcnt < limit:
|
||||||
|
self.fail = self.FAIL_BAD
|
||||||
|
self.failCode = 1000
|
||||||
|
self.failText = "receive RTU"
|
||||||
|
else:
|
||||||
|
if not nocrc:
|
||||||
|
crc = INITIAL_MODBUS
|
||||||
|
for bajt in rxbytes:
|
||||||
|
crc = calcByte( bajt, crc)
|
||||||
|
else:
|
||||||
|
crc=0
|
||||||
|
if crc:
|
||||||
|
self.fail = self.FAIL_CRC
|
||||||
|
self.failCode = 1000
|
||||||
|
self.failText = "CRC"
|
||||||
|
raise MbusbaseFail('Bad CRC',FAILCODE_CRC, reply=self)
|
||||||
|
else:
|
||||||
|
self.address = rxbytes[0]
|
||||||
|
self.fcode = rxbytes[1] & 0x7F
|
||||||
|
if rxbytes[1]>127:
|
||||||
|
self.fail = self.FAIL_REPLY
|
||||||
|
self.data = bytearray(rxbytes)
|
||||||
|
errortext = 'Host replied fail: ('+str(self.FAIL_REPLY)+') '+errcode.names[self.FAIL_REPLY]
|
||||||
|
raise MbusbaseFail(errortext,FAILCODE_REPLY, self.FAIL_REPLY, reply=self)
|
||||||
|
else:
|
||||||
|
if not nocrc:
|
||||||
|
self.data = bytearray(rxbytes[2:-2])
|
||||||
|
else:
|
||||||
|
self.data = bytearray(rxbytes[2:])
|
||||||
|
|
||||||
|
|
||||||
|
def clientParse(self):
|
||||||
|
self.vals=None
|
||||||
|
if self.fail == self.FAIL_REPLY:
|
||||||
|
self.vals=[self.data[1] & 0x7F, self.data[2]]
|
||||||
|
self.failCode=self.data[2]
|
||||||
|
self.failText=errcode.names[self.failCode]
|
||||||
|
|
||||||
|
elif self.fcode==3 or self.fcode==4:
|
||||||
|
self.vals=[]
|
||||||
|
for byi in range(0,self.data[0] // 2):
|
||||||
|
val16=self.data[1+byi*2]*256 + self.data[2+byi*2]
|
||||||
|
self.vals.append(val16)
|
||||||
|
|
||||||
|
elif self.fcode==1 or self.fcode==2:
|
||||||
|
self.vals=[]
|
||||||
|
for byi in range(0,self.data[0]):
|
||||||
|
for biti in range(0,8):
|
||||||
|
bit = (self.data[1+byi] >> biti) & 1
|
||||||
|
self.vals.append(bit)
|
||||||
|
|
||||||
|
elif self.fcode==5 or self.fcode==6 or self.fcode==15 or self.fcode==16:
|
||||||
|
self.vals=[]
|
||||||
|
#print(bytestr(self.data))
|
||||||
|
val16=self.data[0]*256 + self.data[1]
|
||||||
|
self.vals.append(val16)
|
||||||
|
val16=self.data[2]*256 + self.data[3]
|
||||||
|
self.vals.append(val16)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def parseRtu(frrtu):
|
||||||
|
#IF read 16 bit regs
|
||||||
|
valstxt=''
|
||||||
|
if frrtu[1]==0x03 or frrtu[1]==0x04:
|
||||||
|
vals=[]
|
||||||
|
valstxt=''
|
||||||
|
for byi in range(0,frrtu[2] // 2):
|
||||||
|
val16=frrtu[3+byi*2]*256 + frrtu[4+byi*2]
|
||||||
|
vals.append(val16)
|
||||||
|
valstxt+='%5u ' % val16
|
||||||
|
|
||||||
|
|
||||||
|
if frrtu[1]==0x01 or frrtu[1]==0x02:
|
||||||
|
vals=[]
|
||||||
|
for byi in range(0,frrtu[2]):
|
||||||
|
for biti in range(0,8):
|
||||||
|
bit = (frrtu[3+byi] >> biti) & 1;
|
||||||
|
valstxt+='%u ' % bit
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#====================================================================
|
||||||
|
#
|
||||||
|
# TESTS
|
||||||
|
#
|
||||||
|
#====================================================================
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
req = request05(0x12,0xA,1).rtu()
|
||||||
|
print(bytestr(req) )
|
||||||
|
req = request15(0xCC,0x13,[1,0,1,1,0,0,1,1,1]).rtu()
|
||||||
|
print(bytestr(req) )
|
||||||
|
req = request16(0xCC,0x01,[0x000A,0x0102]).rtu()
|
||||||
|
print(bytestr(req) )
|
||||||
|
print("response")
|
||||||
|
print(bytestr(response04(0x11,[0x000A,0x0102]).rtu()) )
|
||||||
|
print(bytestr(response06(0x11,0xC00A,0x4008).rtu()) )
|
||||||
|
print(bytestr(response15(0x11,0xC00A,0x4008).rtu()) )
|
||||||
|
print(bytestr(response16(0x11,0xC00A,0x4008).rtu()) )
|
||||||
|
|
||||||
|
|
||||||
|
rxrtu = receivedRtu( req )
|
||||||
|
|
||||||
|
print(bytestr(rxrtu.data))
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
347
mbusclient.py
Normal file
347
mbusclient.py
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
# ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||
|
# Copyright (C) 2017 Piotr Murawski
|
||||||
|
# Niniejszy program jest wolnym oprogramowaniem; mozesz go
|
||||||
|
# rozprowadzac dalej i/lub modyfikowac na warunkach Powszechnej
|
||||||
|
# Licencji Publicznej GNU, wydanej przez Fundacje Wolnego
|
||||||
|
# Oprogramowania - wedlug wersji 3-ciej tej Licencji lub ktorejs
|
||||||
|
# z pozniejszych wersji.
|
||||||
|
# Niniejszy program rozpowszechniany jest z nadzieja iz bedzie on
|
||||||
|
# uzyteczny - jednak BEZ JAKIEJKOLWIEK GWARANCJI, nawet domyslnej
|
||||||
|
# gwarancji PRZYDATNOSCI HANDLOWEJ albo PRZYDATNOSCI DO OKRESLONYCH
|
||||||
|
# ZASTOSOWAN. W celu uzyskania blizszych informacji - Powszechna
|
||||||
|
# Licencja Publiczna GNU.
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
# Z pewnoscia wraz z niniejszym programem otrzymales tez egzemplarz
|
||||||
|
# Powszechnej Licencji Publicznej GNU (GNU General Public License);
|
||||||
|
# jesli nie - napisz do Free Software Foundation, Inc., 675 Mass Ave,
|
||||||
|
# Cambridge, MA 02139, USA.
|
||||||
|
# Powszechna Licencja GNU dostepna jest rowniez na stronie:
|
||||||
|
# http://www.gnu.org/licenses/licenses.html
|
||||||
|
# nieoficjalne polskie tlumaczenie na
|
||||||
|
# http://www.gnu.org.pl
|
||||||
|
# ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import time
|
||||||
|
import serial
|
||||||
|
import mbusbase
|
||||||
|
from mbusbase import get16, get8, bytestr, errcode
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
FAILCODE_SOCKET = 1
|
||||||
|
FAILCODE_CONNECT = 2
|
||||||
|
FAILCODE_NODATA = 3
|
||||||
|
FAILCODE_TIMEOUT = 4
|
||||||
|
FAILCODE_SOCKETSEND = 5
|
||||||
|
FAILCODE_SOCKETREAD = 6
|
||||||
|
FAILCODE_LENGTH = 7
|
||||||
|
FAILCODE_SERIAL = 8
|
||||||
|
FAILCODE_SERIALWRITE = 9
|
||||||
|
FAILCODE_SERIALREAD = 10
|
||||||
|
FAILCODE_TCPSEQUENCE = 11
|
||||||
|
BIT_RATE = 4800
|
||||||
|
|
||||||
|
class MbclientFail(Exception):
|
||||||
|
def __init__(self, message, code):
|
||||||
|
super(MbclientFail, self).__init__(message)
|
||||||
|
self.code = code
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class mbclient:
|
||||||
|
transportOpen=False
|
||||||
|
TOUTMSG=1.0
|
||||||
|
TOUTCHAR=6
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def getReply(self):
|
||||||
|
bstr=self.transportRead(3,self.TOUTMSG)
|
||||||
|
if bstr != '' and bstr!=None:
|
||||||
|
if len(bstr)<3:
|
||||||
|
raise MbclientFail("Not enough bytes received, <3",FAILCODE_NODATA)
|
||||||
|
bajty=bytearray(bstr)
|
||||||
|
#print(bytestr)(bajty)
|
||||||
|
# find expected length
|
||||||
|
bytescnt=0
|
||||||
|
if bajty[1]==1 or bajty[1]==2:
|
||||||
|
bytescnt = int(bajty[2])
|
||||||
|
elif bajty[1]==3 or bajty[1]==4:
|
||||||
|
bytescnt = int(bajty[2])
|
||||||
|
elif bajty[1]==5:
|
||||||
|
bytescnt = 4
|
||||||
|
elif bajty[1]==5 or bajty[1]==6:
|
||||||
|
bytescnt = 3
|
||||||
|
elif bajty[1]==5 or bajty[1]==15 or bajty[1]==16:
|
||||||
|
bytescnt = 4
|
||||||
|
elif bajty[1]>127:
|
||||||
|
bytescnt = 0
|
||||||
|
bytescnt+=2
|
||||||
|
#print('Expected bytes : ' + str)(bytescnt)
|
||||||
|
bstr=self.transportRead(bytescnt,self.charTout * 256)
|
||||||
|
#print('bytes : ',len)(bstr)
|
||||||
|
if bstr != '':
|
||||||
|
if len(bstr)<bytescnt:
|
||||||
|
return None
|
||||||
|
bajtydata=bytearray(bstr)
|
||||||
|
#print(bytestr)(bajtydata)
|
||||||
|
apdu=bajty+bajtydata
|
||||||
|
rtu = mbusbase.receivedRtu(apdu)
|
||||||
|
rtu.clientParse()
|
||||||
|
return rtu
|
||||||
|
else:
|
||||||
|
raise MbclientFail("Receive timeout",FAILCODE_TIMEOUT)
|
||||||
|
|
||||||
|
|
||||||
|
def transportOpen(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def transportClose(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def sendRequest(self, request):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def transportSend(self, rawbytes):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def transportRead(self, count,timeout=None):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def readCoils(self, adres, start, cnt):
|
||||||
|
self.sendRequest(mbusbase.request01(adres, start, cnt) )
|
||||||
|
return self.awaitReply()
|
||||||
|
|
||||||
|
def readInputs(self, adres, start, cnt):
|
||||||
|
self.sendRequest(mbusbase.request02(adres, start, cnt) )
|
||||||
|
return self.awaitReply()
|
||||||
|
|
||||||
|
def readInputRegs(self, adres, start, cnt):
|
||||||
|
self.sendRequest(mbusbase.request04(adres, start, cnt) )
|
||||||
|
return self.awaitReply()
|
||||||
|
|
||||||
|
def readHoldingRegs(self, adres, start, cnt):
|
||||||
|
self.sendRequest(mbusbase.request03(adres, start, cnt) )
|
||||||
|
return self.awaitReply()
|
||||||
|
|
||||||
|
def writeCoil(self, adres, addr, state):
|
||||||
|
self.sendRequest(mbusbase.request05(adres, addr, state) )
|
||||||
|
return self.awaitReply()
|
||||||
|
|
||||||
|
def writeCoils(self, adres, startaddr,coilsarr):
|
||||||
|
self.sendRequest(mbusbase.request15(adres, startaddr,coilsarr) )
|
||||||
|
return self.awaitReply()
|
||||||
|
|
||||||
|
def writeRegister(self, adres, addr, value):
|
||||||
|
self.sendRequest(mbusbase.request06(adres, addr, value) )
|
||||||
|
return self.awaitReply()
|
||||||
|
|
||||||
|
def writeRegisters(self, adres, startaddr,regarr):
|
||||||
|
self.sendRequest(mbusbase.request16(adres, startaddr,regarr) )
|
||||||
|
return self.awaitReply()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class mbclienttcp(mbclient):
|
||||||
|
portdev=None
|
||||||
|
def transportOpen(self, clientAddress, timeout=0.9, port=502):
|
||||||
|
self.clientAddress = clientAddress
|
||||||
|
self.mainTout=timeout
|
||||||
|
self.socke=None
|
||||||
|
self.sequence=1
|
||||||
|
self.port=port
|
||||||
|
self.charTout = timeout / 256
|
||||||
|
self.lastsequence = 0
|
||||||
|
serverAddress = (self.clientAddress, self.port)
|
||||||
|
try:
|
||||||
|
self.socke = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
except Exception as inst:
|
||||||
|
raise MbclientFail("Can't open socket ["+inst.args[1]+']',FAILCODE_SOCKET)
|
||||||
|
try:
|
||||||
|
print('Connecting to %s:%s' % serverAddress, file=sys.stderr)
|
||||||
|
self.socke.connect(serverAddress)
|
||||||
|
except Exception as inst:
|
||||||
|
raise MbclientFail("Can't connect socket @"+ str(self.clientAddress)+":"+str(self.port)+" ["+inst.args[1]+']',FAILCODE_CONNECT)
|
||||||
|
|
||||||
|
self.transportOpen=True
|
||||||
|
|
||||||
|
|
||||||
|
def transportClose(self):
|
||||||
|
try:
|
||||||
|
self.socke.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def transportSend(self, rawbytes):
|
||||||
|
if not self.transportOpen:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
self.socke.sendall(rawbytes)
|
||||||
|
return
|
||||||
|
except Exception as inst:
|
||||||
|
raise MbclientFail("Socket send error ["+inst.args[1]+']',FAILCODE_SOCKETSEND)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def transportRead(self, count, timeout):
|
||||||
|
self.socke.settimeout(timeout)
|
||||||
|
if not self.transportOpen:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return self.socke.recv(count, socket.MSG_WAITALL)
|
||||||
|
except Exception as inst:
|
||||||
|
raise MbclientFail("Socket read error ["+inst.args[1]+']',FAILCODE_SOCKETREAD)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def sendRequest(self, request):
|
||||||
|
bytes=request.tcp(self.sequence)
|
||||||
|
self.lastsequence=self.sequence
|
||||||
|
self.sequence+=1
|
||||||
|
return self.transportSend(bytes)
|
||||||
|
|
||||||
|
|
||||||
|
def awaitReply(self):
|
||||||
|
bstr=self.transportRead(6,self.TOUTMSG)
|
||||||
|
if len(bstr)!=6:
|
||||||
|
raise MbclientFail("Not enough bytes received, <6 ",FAILCODE_NODATA)
|
||||||
|
else:
|
||||||
|
bajty=bytearray(bstr)
|
||||||
|
#print("|aaa|" + bytestr)(bajty)
|
||||||
|
sequence=bajty[0]*256 + bajty[1]
|
||||||
|
length=bajty[4]*256 + bajty[5]
|
||||||
|
if self.lastsequence != sequence:
|
||||||
|
raise MbclientFail("Sequence mismatch: expected "+str(self.lastsequence)+", got "+str(sequence),FAILCODE_TCPSEQUENCE)
|
||||||
|
if length:
|
||||||
|
bstr=self.transportRead(length,self.charTout * 256)
|
||||||
|
#print('bytes : ',len)(bstr)
|
||||||
|
if bstr != '':
|
||||||
|
if len(bstr)<length:
|
||||||
|
raise MbclientFail("Not enough bytes received ",FAILCODE_NODATA)
|
||||||
|
bajtydata=bytearray(bstr)
|
||||||
|
#print("|bbb|"+bytestr)(bajtydata)
|
||||||
|
apdu=bajtydata
|
||||||
|
rtu = mbusbase.receivedRtu(apdu, True)
|
||||||
|
rtu.clientParse()
|
||||||
|
return rtu
|
||||||
|
else:
|
||||||
|
raise MbclientFail("TCP header length = 0 ",FAILCODE_LENGTH)
|
||||||
|
|
||||||
|
return reply
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def setTimeout(self, tout):
|
||||||
|
self.timeout = tout
|
||||||
|
|
||||||
|
class mbclientserial(mbclient):
|
||||||
|
portdev=None
|
||||||
|
def transportOpen(self, device, brate=BIT_RATE, stopbit=1, timeout=0.9, parity=serial.PARITY_EVEN, stopbits=serial.STOPBITS_ONE):
|
||||||
|
self.mainTout=timeout
|
||||||
|
self.charTout=12*1/brate
|
||||||
|
self.portdev=None
|
||||||
|
try:
|
||||||
|
self.portdev = serial.Serial(device, timeout = timeout, baudrate=BIT_RATE, parity=serial.PARITY_EVEN, stopbits=serial.STOPBITS_ONE)
|
||||||
|
if not hasattr(self.portdev, 'setTimeout'):
|
||||||
|
serial.Serial.setTimeout = setTimeout
|
||||||
|
except Exception as inst:
|
||||||
|
self.portdev = None
|
||||||
|
raise MbclientFail("Can't open serial "+str(device)+" ["+inst.args[1]+']',FAILCODE_SERIAL)
|
||||||
|
# flush some garbage
|
||||||
|
self.portdev.setTimeout(0.01)
|
||||||
|
self.portdev.read(16)
|
||||||
|
# configure for good
|
||||||
|
self.portdev.setTimeout(timeout)
|
||||||
|
self.portdev.baudrate=brate
|
||||||
|
self.portdev.parity=parity #serial.PARITY_EVEN
|
||||||
|
self.portdev.stopbits=stopbits
|
||||||
|
self.transportOpen=True
|
||||||
|
|
||||||
|
def transportClose(self):
|
||||||
|
try:
|
||||||
|
self.portdev.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def transportSend(self, rawbytes):
|
||||||
|
if not self.transportOpen:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
bb=bytearray(1)
|
||||||
|
for b in rawbytes:
|
||||||
|
bb[0]=b
|
||||||
|
self.portdev.write(bb)
|
||||||
|
#return self.portdev.write(rawbytes)
|
||||||
|
return
|
||||||
|
except Exception as inst:
|
||||||
|
raise MbclientFail("Can't write serial ["+inst.args[1]+']',FAILCODE_SERIALWRITE)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def transportRead(self, count,timeout):
|
||||||
|
|
||||||
|
if not self.transportOpen:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
if timeout:
|
||||||
|
if timeout==self.TOUTMSG: self.portdev.setTimeout(self.mainTout)
|
||||||
|
elif timeout==self.TOUTCHAR: self.portdev.setTimeout(self.charTout * count)
|
||||||
|
else: self.portdev.setTimeout(timeout)
|
||||||
|
return self.portdev.read(count)
|
||||||
|
except Exception as inst:
|
||||||
|
raise MbclientFail("Can't read serial ["+inst.args[1]+']',FAILCODE_SERIALREAD)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def sendRequest(self, request):
|
||||||
|
bytes=request.rtu();
|
||||||
|
return self.transportSend(bytes)
|
||||||
|
|
||||||
|
def awaitReply(self):
|
||||||
|
reply=self.getReply()
|
||||||
|
return reply
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class mbclientdevice:
|
||||||
|
def __init__(self, mbaddr, mbclientiface):
|
||||||
|
self.mbaddr = mbaddr
|
||||||
|
self.mbclientiface = mbclientiface
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
tecl = mbclientserial()
|
||||||
|
tecl.transportOpen('/dev/ttyUSB0', brate=BIT_RATE)
|
||||||
|
#r03 = mbusbase.request01(1,0x3,20)
|
||||||
|
r03 = mbusbase.request03(1,1,10)
|
||||||
|
print(bytestr(r03.rtu()))
|
||||||
|
tecl.transportSend(r03.rtu())
|
||||||
|
reply=tecl.getReply()
|
||||||
|
if reply:
|
||||||
|
if reply.fail:
|
||||||
|
print('error', reply.fail)
|
||||||
|
else:
|
||||||
|
print(reply.vals)
|
||||||
|
|
||||||
|
#time.sleep(0.5)
|
||||||
|
pass
|
||||||
871
modb_orno3.c
Normal file
871
modb_orno3.c
Normal file
@@ -0,0 +1,871 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include </usr/include/modbus/modbus-rtu.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <mosquitto.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* KONFIGURACJA PROGRAMU - EDYTUJ TU */
|
||||||
|
/* ============================================ */
|
||||||
|
#define READ_LOOP true /* false = jeden odczyt, true = pętla co 5s */
|
||||||
|
#define VOLTAGE_BUFFER_SIZE 12
|
||||||
|
#define VOLTAGE_FLUCTUATION_THRESHOLD 0.05 // 5%
|
||||||
|
#define HIGH_FREQ_MODE_DURATION 300 // 300 seconds
|
||||||
|
#define READ_ORNO true /* Czy odczytywać ORNO */
|
||||||
|
#define READ_SUN2K true /* Czy odczytywać SUN2K */
|
||||||
|
|
||||||
|
/* Adresy urządzeń Modbus RTU */
|
||||||
|
#define SUN2000_SLAVE_ADR 3
|
||||||
|
#define ORNO_SLAVE_ADR 2
|
||||||
|
|
||||||
|
/* Port szeregowy */
|
||||||
|
#define USB_DEV_DEFAULT "/dev/ttyUSB0"
|
||||||
|
|
||||||
|
/* Parametry timing dla ORNO (mikrosekund) */
|
||||||
|
#define ORNO_RTS_DELAY 5000 /* RTS delay: 5ms */
|
||||||
|
#define ORNO_BYTE_TIMEOUT 2500 /* Byte timeout: 2.5ms */
|
||||||
|
|
||||||
|
/* Parametry timing dla SUN2K (mikrosekund) */
|
||||||
|
#define SUN2K_RTS_DELAY 5000 /* RTS delay: 5ms */
|
||||||
|
#define SUN2K_BYTE_TIMEOUT 2500 /* Byte timeout: 2.5ms */
|
||||||
|
|
||||||
|
/* Przerwa między ORNO a SUN2K (mikrosekund) */
|
||||||
|
#define DELAY_BETWEEN_DEVICES 10000 /* 10ms */
|
||||||
|
|
||||||
|
/* MQTT - ustaw 1 aby wyłączyć MQTT */
|
||||||
|
#define DISABLE_MQTT 0 /* 0 = MQTT włączony, 1 = wyłączony */
|
||||||
|
|
||||||
|
/* Broker MQTT */
|
||||||
|
#define MQTT_BROKER "10.1.1.1"
|
||||||
|
#define MQTT_PORT 1883
|
||||||
|
|
||||||
|
/* InfluxDB Configuration */
|
||||||
|
#define DISABLE_INFLUX 0 /* 0 = InfluxDB włączony, 1 = wyłączony */
|
||||||
|
#define INFLUX_HOST "10.1.1.1"
|
||||||
|
#define INFLUX_PORT 5086
|
||||||
|
#define INFLUX_ORG "sic" /* Zmień na swoją organizację */
|
||||||
|
#define INFLUX_BUCKET "energydb" /* Zmień na swój bucket */
|
||||||
|
#define INFLUX_TOKEN "BCIZ6kcCAVgpcwSfU0PBS7m0Zb6an93kuOtizbEtlXub-uaoYp4dmIQBQYaJCv8_KE4QYYZ08jxtpaZ3TUWP-Q==" /* Zmień na swój token */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
/*
|
||||||
|
'Model' : 'ID'
|
||||||
|
'addr' : '30000',
|
||||||
|
'registers' : 15,
|
||||||
|
'name' : 'Model',
|
||||||
|
'scale' : 1,
|
||||||
|
'type' : 'str',
|
||||||
|
'units' : '' ,
|
||||||
|
'use' : 'info',
|
||||||
|
'method' : 'hold'
|
||||||
|
*/
|
||||||
|
enum regtype
|
||||||
|
{
|
||||||
|
rgStr,
|
||||||
|
rgU16,
|
||||||
|
rgU32,
|
||||||
|
rgI16,
|
||||||
|
rgI32,
|
||||||
|
rgB16,
|
||||||
|
rgB32,
|
||||||
|
rgData,
|
||||||
|
rgFloat
|
||||||
|
};
|
||||||
|
typedef struct s_mb_reg
|
||||||
|
{
|
||||||
|
const char *reg_id;
|
||||||
|
int ireg;
|
||||||
|
int num_reg;
|
||||||
|
char *desc;
|
||||||
|
int scale;
|
||||||
|
enum regtype type;
|
||||||
|
char *units;
|
||||||
|
char *use;
|
||||||
|
char *method;
|
||||||
|
char buf_last_val[30];
|
||||||
|
} t_mb_reg;
|
||||||
|
|
||||||
|
t_mb_reg mbReg[] = {
|
||||||
|
|
||||||
|
{"Start", 32091, 2, "Startup time", 1, rgData, "s", "info", "hold", ""},
|
||||||
|
{"Shutdown", 32093, 2, "Shutdown time", 1, rgData, "s", "info", "hold", ""},
|
||||||
|
{"Time", 40000, 2, "Current time", 1, rgData, "s", "info", "hold", ""},
|
||||||
|
{"State1", 32000, 1, "Status 1", 1, rgB16, "", "stat", "hold", ""},
|
||||||
|
{"Alarm1", 32008, 1, "Alarm 1", 1, rgB16, "", "stat", "hold", ""},
|
||||||
|
{"Status", 32089, 1, "Device status", 1, rgU16, "", "stat", "hold", ""},
|
||||||
|
{"Fault", 32090, 1, "Fault code", 1, rgU16, "", "stat", "hold", ""},
|
||||||
|
{"PV_P", 32064, 2, "Input power", 1000, rgI32, "kW", "data", "hold", ""},
|
||||||
|
{"U_A", 32069, 1, "Phase Voltage A", 10, rgU16, "V", "data", "hold", ""},
|
||||||
|
{"U_B", 32070, 1, "Phase Voltage B", 10, rgU16, "V", "data", "hold", ""},
|
||||||
|
{"U_C", 32071, 1, "Phase Voltage C", 10, rgU16, "V", "ext", "hold", ""},
|
||||||
|
{"P_peak", 32078, 2, "Peak Power", 1000, rgI32, "kW", "data", "hold", ""},
|
||||||
|
{"P_active", 32080, 2, "Active power", 1000, rgI32, "kW", "data", "hold", ""},
|
||||||
|
{"P_reactive", 32082, 2, "Reactive power", 1000, rgI32, "kVar", "data", "hold", ""},
|
||||||
|
{"Frequency", 32085, 1, "Grid frequency", 100, rgU16, "Hz", "data", "hold", ""},
|
||||||
|
{"Temp", 32087, 1, "Internal temperature", 10, rgI16, "°C", "data", "hold", ""},
|
||||||
|
{"P_accum", 32106, 2, "Accumulated energy yield", 100, rgU32, "kWh", "data", "hold", ""},
|
||||||
|
{"P_daily", 32114, 2, "Daily energy yield", 100, rgU32, "kWh", "data", "hold", ""}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
t_mb_reg mbReg_Orno[] = {
|
||||||
|
|
||||||
|
{"L1 Volt", 0x000e, 2, "L1 Volt", 1, rgU32, "V", "info", "hold", ""},
|
||||||
|
{"L2 Volt", 0x0010, 2, "L2 Volt", 1, rgU32, "V", "info", "hold", ""},
|
||||||
|
{"L3 Volt", 0x0012, 2, "L3 Volt", 10, rgU32, "V", "info", "hold", ""},
|
||||||
|
{"Tot Power", 0x001c, 2, "Tot Power", 1, rgU32, "kW", "info", "hold", ""},
|
||||||
|
{"L1 Tot Power", 0x001e, 2, "L1 Tot Power", 1, rgU32, "kW", "info", "hold", ""},
|
||||||
|
{"L2 Tot Power", 0x0020, 2, "L2 Tot Power", 1, rgU32, "kW", "info", "hold", ""},
|
||||||
|
{"L3 Tot Power", 0x0022, 2, "L3 Tot Power", 1, rgU32, "kW", "info", "hold", ""},
|
||||||
|
{"Tot Energ", 0x0100, 2, "Tot Energ", 1, rgU32, "kWh", "info", "hold", ""},
|
||||||
|
{"L1 Tot Energ", 0x0102, 2, "L1 Tot Energ", 1, rgU32, "kWh", "info", "hold", ""},
|
||||||
|
{"L2 Tot Energ", 0x0104, 2, "L2 Tot Energ", 1, rgU32, "kWh", "info", "hold", ""},
|
||||||
|
{"L3 Tot Energ", 0x0106, 2, "L3 Tot Energ", 1, rgU32, "kWh", "info", "hold", ""}};
|
||||||
|
|
||||||
|
void mosq_log_callback(struct mosquitto *mosq, void *userdata, int level, const char *str)
|
||||||
|
{
|
||||||
|
/* Pring all log messages regardless of level. */
|
||||||
|
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
//case MOSQ_LOG_DEBUG:
|
||||||
|
//case MOSQ_LOG_INFO:
|
||||||
|
//case MOSQ_LOG_NOTICE:
|
||||||
|
case MOSQ_LOG_WARNING:
|
||||||
|
case MOSQ_LOG_ERR:
|
||||||
|
{
|
||||||
|
printf("MQTT: %i:%s\n", level, str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct mosquitto *mosq = NULL;
|
||||||
|
void mqtt_setup()
|
||||||
|
{
|
||||||
|
if (DISABLE_MQTT) {
|
||||||
|
printf("MQTT: disabled by configuration\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *host = MQTT_BROKER;
|
||||||
|
int port = MQTT_PORT;
|
||||||
|
int keepalive = 60;
|
||||||
|
bool clean_session = true;
|
||||||
|
|
||||||
|
mosquitto_lib_init();
|
||||||
|
mosq = mosquitto_new(NULL, clean_session, NULL);
|
||||||
|
if (!mosq)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "MQTT: Error: Out of memory.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
mosquitto_log_callback_set(mosq, mosq_log_callback);
|
||||||
|
|
||||||
|
if (mosquitto_connect(mosq, host, port, keepalive))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "MQTT: Unable to connect.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
int loop = mosquitto_loop_start(mosq);
|
||||||
|
if (loop != MOSQ_ERR_SUCCESS)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "MQTT: Unable to start loop: %i\n", loop);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int mqtt_send(char *topic, char *buf)
|
||||||
|
{
|
||||||
|
if (DISABLE_MQTT) return 0;
|
||||||
|
return mosquitto_publish(mosq, NULL, topic, strlen(buf), buf, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int mqtt_send_SUN2K(t_mb_reg *reg_to_send, char *_str_buf)
|
||||||
|
{
|
||||||
|
if (DISABLE_MQTT) return 0;
|
||||||
|
|
||||||
|
/* Don't send if str_buf is empty or NULL - means read failed */
|
||||||
|
if (!_str_buf || strlen(_str_buf) == 0) {
|
||||||
|
printf("MQTT: Skipping %s - no valid data\n", reg_to_send->reg_id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char topic[128];
|
||||||
|
char buf[128];
|
||||||
|
snprintf(topic, sizeof(topic), "/energy/SUN2K");
|
||||||
|
snprintf(buf, sizeof(buf), "{\"%s\":%s}", reg_to_send->reg_id, _str_buf);
|
||||||
|
|
||||||
|
/* Save last valid value */
|
||||||
|
strncpy(reg_to_send->buf_last_val, _str_buf, sizeof(reg_to_send->buf_last_val)-1);
|
||||||
|
reg_to_send->buf_last_val[sizeof(reg_to_send->buf_last_val)-1] = '\0';
|
||||||
|
|
||||||
|
printf("MQTT: Publishing %s\n", buf);
|
||||||
|
return mosquitto_publish(mosq, NULL, topic, strlen(buf), buf, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int mqtt_send_U(float U1, float U2, float U3)
|
||||||
|
{
|
||||||
|
if (DISABLE_MQTT) return 0;
|
||||||
|
char buf[128];
|
||||||
|
sprintf(buf, "{\"U1\":%f,\"U2\":%f,\"U3\":%f}", U1, U2, U3);
|
||||||
|
return mosquitto_publish(mosq, NULL, "/energy/orno/U", strlen(buf), buf, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int mqtt_send_P(float P_Tot, float P1, float P2, float P3)
|
||||||
|
{
|
||||||
|
if (DISABLE_MQTT) return 0;
|
||||||
|
char buf[128];
|
||||||
|
sprintf(buf, "{\"P_Tot\":%f,\"P1\":%f,\"P2\":%f,\"P3\":%f}", P_Tot, P1, P2, P3);
|
||||||
|
return mosquitto_publish(mosq, NULL, "/energy/orno/P", strlen(buf), buf, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int mqtt_send_W(float W_Tot, float W1, float W2, float W3)
|
||||||
|
{
|
||||||
|
if (DISABLE_MQTT) return 0;
|
||||||
|
char buf[128];
|
||||||
|
sprintf(buf, "{\"W_Tot\":%f,\"W1\":%f,\"W2\":%f,\"W3\":%f}", W_Tot, W1, W2, W3);
|
||||||
|
return mosquitto_publish(mosq, NULL, "/energy/orno/W", strlen(buf), buf, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int mqtt_send_I(float I1, float I2, float I3)
|
||||||
|
{
|
||||||
|
if (DISABLE_MQTT) return 0;
|
||||||
|
char buf[128];
|
||||||
|
sprintf(buf, "{\"I1\":%f,\"I2\":%f,\"I3\":%f}", I1, I2, I3);
|
||||||
|
return mosquitto_publish(mosq, NULL, "/energy/orno/I", strlen(buf), buf, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int mqtt_send_Hz(float Hz)
|
||||||
|
{
|
||||||
|
if (DISABLE_MQTT) return 0;
|
||||||
|
char buf[64];
|
||||||
|
sprintf(buf, "%f", Hz);
|
||||||
|
return mosquitto_publish(mosq, NULL, "/energy/orno/Hz", strlen(buf), buf, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* InfluxDB Functions */
|
||||||
|
int influx_send_post(char *data)
|
||||||
|
{
|
||||||
|
if (DISABLE_INFLUX) return 0;
|
||||||
|
|
||||||
|
int sock;
|
||||||
|
struct sockaddr_in server;
|
||||||
|
|
||||||
|
sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (sock == -1) {
|
||||||
|
perror("INFLUX: Could not create socket for InfluxDB");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.sin_addr.s_addr = inet_addr(INFLUX_HOST);
|
||||||
|
server.sin_family = AF_INET;
|
||||||
|
server.sin_port = htons(INFLUX_PORT);
|
||||||
|
|
||||||
|
/* Set timeout for socket */
|
||||||
|
struct timeval tv;
|
||||||
|
tv.tv_sec = 2;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);
|
||||||
|
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof tv);
|
||||||
|
|
||||||
|
if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
|
||||||
|
perror("INFLUX: Connect to InfluxDB failed");
|
||||||
|
close(sock);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char header[1024];
|
||||||
|
/* Construct HTTP POST request for InfluxDB v2 Write API */
|
||||||
|
snprintf(header, sizeof(header),
|
||||||
|
"POST /api/v2/write?org=%s&bucket=%s&precision=s HTTP/1.1\r\n"
|
||||||
|
"Host: %s:%d\r\n"
|
||||||
|
"Authorization: Token %s\r\n"
|
||||||
|
"Content-Length: %ld\r\n"
|
||||||
|
"Content-Type: text/plain; charset=utf-8\r\n"
|
||||||
|
"Connection: close\r\n\r\n",
|
||||||
|
INFLUX_ORG, INFLUX_BUCKET, INFLUX_HOST, INFLUX_PORT, INFLUX_TOKEN, strlen(data));
|
||||||
|
|
||||||
|
if (send(sock, header, strlen(header), 0) < 0) {
|
||||||
|
perror("INFLUX: Send header to InfluxDB failed");
|
||||||
|
close(sock);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (send(sock, data, strlen(data), 0) < 0) {
|
||||||
|
perror("INFLUX: Send body to InfluxDB failed");
|
||||||
|
close(sock);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(sock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int influx_send_U(float U1, float U2, float U3) {
|
||||||
|
char line[256];
|
||||||
|
snprintf(line, sizeof(line), "orno,device=orno,pomiar=voltage, L1=%.2f,L2=%.2f,L3=%.2f", U1, U2, U3);
|
||||||
|
return influx_send_post(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
int influx_send_I(float I1, float I2, float I3) {
|
||||||
|
char line[256];
|
||||||
|
snprintf(line, sizeof(line), "orno,device=orno,pomiar=current L1=%.2f,L2=%.2f,L3=%.2f", I1, I2, I3);
|
||||||
|
return influx_send_post(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
int influx_send_P(float P_Tot, float P1, float P2, float P3) {
|
||||||
|
char line[256];
|
||||||
|
snprintf(line, sizeof(line), "orno,device=orno,pomiar=power total=%.3f,L1=%.3f,L2=%.3f,L3=%.3f", P_Tot, P1, P2, P3);
|
||||||
|
return influx_send_post(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
int influx_send_W(float W_Tot, float W1, float W2, float W3) {
|
||||||
|
char line[256];
|
||||||
|
snprintf(line, sizeof(line), "orno,device=orno,pomiar=energy total=%.3f,L1=%.3f,L2=%.3f,L3=%.3f", W_Tot, W1, W2, W3);
|
||||||
|
return influx_send_post(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
int influx_send_Hz(float Hz) {
|
||||||
|
char line[256];
|
||||||
|
snprintf(line, sizeof(line), "orno,device=orno,pomiar=frequency value=%.4f", Hz);
|
||||||
|
return influx_send_post(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
int influx_send_SUN2K(t_mb_reg *reg_to_send, char *_str_buf) {
|
||||||
|
if (!_str_buf || strlen(_str_buf) == 0) return 0;
|
||||||
|
char line[256];
|
||||||
|
/* reg_id used as tag, value as field */
|
||||||
|
snprintf(line, sizeof(line), "sun2k,device=sun2k,pomiar=%s value=%s", reg_to_send->reg_id, _str_buf);
|
||||||
|
return influx_send_post(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate float value - check if not NaN, Inf, or out of reasonable range */
|
||||||
|
int is_valid_float(float value, float min_val, float max_val)
|
||||||
|
{
|
||||||
|
if (isnan(value)) {
|
||||||
|
printf("VALIDATE: WARNING: Value is NaN\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (isinf(value)) {
|
||||||
|
printf("VALIDATE: WARNING: Value is Inf\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (value < min_val || value > max_val) {
|
||||||
|
printf("VALIDATE: WARNING: Value %.2f out of range [%.2f, %.2f]\n", value, min_val, max_val);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Timed wrapper for modbus_read_registers - logs start/stop and duration */
|
||||||
|
int modbus_read_timed(modbus_t *ctx, int addr, int nb, uint16_t *dest)
|
||||||
|
{
|
||||||
|
struct timeval t0, t1;
|
||||||
|
gettimeofday(&t0, NULL);
|
||||||
|
int res = modbus_read_registers(ctx, addr, nb, dest);
|
||||||
|
gettimeofday(&t1, NULL);
|
||||||
|
long ms = (t1.tv_sec - t0.tv_sec) * 1000 + (t1.tv_usec - t0.tv_usec) / 1000;
|
||||||
|
if (res < 0) {
|
||||||
|
printf("MBUS: modbus_read_registers addr=0x%X nb=%d -> ERR (%s) elapsed=%ldms\n", addr, nb, modbus_strerror(errno), ms);
|
||||||
|
} else {
|
||||||
|
printf("MBUS: modbus_read_registers addr=0x%X nb=%d -> OK (%d) elapsed=%ldms\n", addr, nb, res, ms);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mosq_test()
|
||||||
|
{
|
||||||
|
mqtt_setup();
|
||||||
|
int i = -1000;
|
||||||
|
int k = 10;
|
||||||
|
char *buf = malloc(64);
|
||||||
|
while (k-- > 0)
|
||||||
|
{
|
||||||
|
sprintf(buf, "i=%i", i++);
|
||||||
|
int snd = mqtt_send("/testtopic", buf);
|
||||||
|
if (snd != 0)
|
||||||
|
printf("TEST: mqtt_send error=%i\n", snd);
|
||||||
|
usleep(5000000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
/* Użyj wartości z #define */
|
||||||
|
const char *USB_DEV = USB_DEV_DEFAULT;
|
||||||
|
int ORNO_SLAVE = ORNO_SLAVE_ADR;
|
||||||
|
int SUN2000_SLAVE = SUN2000_SLAVE_ADR;
|
||||||
|
int do_orno = READ_ORNO;
|
||||||
|
int do_sun2k = READ_SUN2K;
|
||||||
|
|
||||||
|
float voltage_buffer_L1[VOLTAGE_BUFFER_SIZE] = {0};
|
||||||
|
float voltage_buffer_L2[VOLTAGE_BUFFER_SIZE] = {0};
|
||||||
|
float voltage_buffer_L3[VOLTAGE_BUFFER_SIZE] = {0};
|
||||||
|
int voltage_buffer_index = 0;
|
||||||
|
int voltage_buffer_items = 0;
|
||||||
|
time_t high_frequency_mode_end_time = 0;
|
||||||
|
|
||||||
|
time_t rawtime;
|
||||||
|
struct tm *timeinfo;
|
||||||
|
|
||||||
|
time(&rawtime);
|
||||||
|
timeinfo = localtime(&rawtime);
|
||||||
|
printf("==============================================\n");
|
||||||
|
printf("Energy Meter Reader - ORNO & SUN2K\n");
|
||||||
|
printf("Start: %s", asctime(timeinfo));
|
||||||
|
printf("==============================================\n");
|
||||||
|
printf("CFG: Configuration:\n");
|
||||||
|
printf("CFG: Device: %s\n", USB_DEV);
|
||||||
|
printf("CFG: ORNO slave address: %d\n", ORNO_SLAVE);
|
||||||
|
printf("CFG: SUN2K slave address: %d\n", SUN2000_SLAVE);
|
||||||
|
printf("CFG: MQTT: %s\n", DISABLE_MQTT ? "DISABLED" : "ENABLED");
|
||||||
|
printf("CFG: MQTT Broker: %s:%d\n", MQTT_BROKER, MQTT_PORT);
|
||||||
|
printf("CFG: InfluxDB: %s (%s:%d)\n", DISABLE_INFLUX ? "DISABLED" : "ENABLED", INFLUX_HOST, INFLUX_PORT);
|
||||||
|
printf("==============================================\n\n");
|
||||||
|
|
||||||
|
mqtt_setup();
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (do_orno)
|
||||||
|
{
|
||||||
|
//Create a new RTU context with proper serial parameters (in this example,
|
||||||
|
//device name /dev/ttyS0, baud rate 9600, no parity bit, 8 data bits, 1 stop bit)
|
||||||
|
modbus_t *ctx = modbus_new_rtu(USB_DEV, 9600, 'E', 8, 1);
|
||||||
|
//Set the Modbus address of the remote slave (to 2)
|
||||||
|
modbus_set_slave(ctx, ORNO_SLAVE);
|
||||||
|
if (!ctx)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ORNO: Failed to create the context: %s\n", modbus_strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modbus_connect(ctx) == -1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ORNO: Unable to connect: %s\n", modbus_strerror(errno));
|
||||||
|
modbus_free(ctx);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enable libmodbus debug to print raw frames */
|
||||||
|
/* modbus_set_debug(ctx, TRUE); */ /* Disabled - causes timing issues */
|
||||||
|
/* Configure RTS delay and timeouts from #define */
|
||||||
|
modbus_rtu_set_rts_delay(ctx, ORNO_RTS_DELAY);
|
||||||
|
modbus_set_response_timeout(ctx, 0, 900000); /* 0.9s */
|
||||||
|
modbus_set_byte_timeout(ctx, 0, ORNO_BYTE_TIMEOUT);
|
||||||
|
|
||||||
|
/* Display current configuration */
|
||||||
|
int rts_delay = modbus_rtu_get_rts_delay(ctx);
|
||||||
|
uint32_t response_timeout_sec, response_timeout_usec;
|
||||||
|
uint32_t byte_timeout_sec, byte_timeout_usec;
|
||||||
|
modbus_get_response_timeout(ctx, &response_timeout_sec, &response_timeout_usec);
|
||||||
|
modbus_get_byte_timeout(ctx, &byte_timeout_sec, &byte_timeout_usec);
|
||||||
|
printf("ORNO: RTS Delay %u us\n", rts_delay);
|
||||||
|
printf("ORNO: Response Timeout %u,%06u s\n", response_timeout_sec, response_timeout_usec);
|
||||||
|
printf("ORNO: Byte Timeout %u,%06u s\n", byte_timeout_sec, byte_timeout_usec);
|
||||||
|
|
||||||
|
uint16_t reg[32]; // will store read registers values
|
||||||
|
uint16_t reg2[2]; // will store read registers values
|
||||||
|
float d = 0.0;
|
||||||
|
|
||||||
|
int num = 0;
|
||||||
|
int proba = 0;
|
||||||
|
float U1, U2, U3;
|
||||||
|
float I1, I2, I3;
|
||||||
|
float P_Tot, P1, P2, P3;
|
||||||
|
float W_Tot, W1, W2, W3;
|
||||||
|
float Freq;
|
||||||
|
|
||||||
|
long l = 0;
|
||||||
|
|
||||||
|
/* Flush serial buffer and wait for device to be ready */
|
||||||
|
modbus_flush(ctx);
|
||||||
|
usleep(100000); /* 100ms delay before first read */
|
||||||
|
|
||||||
|
memset((char *)reg, 0, sizeof(reg));
|
||||||
|
//NAPIECIA
|
||||||
|
num = modbus_read_timed(ctx, 0xe, 6, reg); // ORNO
|
||||||
|
// num = modbus_read_registers(ctx, 32072, 2, reg); // SUN2000
|
||||||
|
if (num != 6)
|
||||||
|
{ // number of read registers is not the one expected
|
||||||
|
printf("ORNO: %x \t\n", 0xe);
|
||||||
|
printf("ORNO: Failed to read: %s\n", modbus_strerror(errno));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
U1 = modbus_get_float_abcd(®[0]);
|
||||||
|
U2 = modbus_get_float_abcd(®[2]);
|
||||||
|
U3 = modbus_get_float_abcd(®[4]);
|
||||||
|
printf("ORNO: Voltages: L1=%.1f V, L2=%.1f V, L3=%.1f V\n", U1, U2, U3);
|
||||||
|
|
||||||
|
// Add to circular buffer
|
||||||
|
voltage_buffer_L1[voltage_buffer_index] = U1;
|
||||||
|
voltage_buffer_L2[voltage_buffer_index] = U2;
|
||||||
|
voltage_buffer_L3[voltage_buffer_index] = U3;
|
||||||
|
voltage_buffer_index = (voltage_buffer_index + 1) % VOLTAGE_BUFFER_SIZE;
|
||||||
|
if (voltage_buffer_items < VOLTAGE_BUFFER_SIZE) {
|
||||||
|
voltage_buffer_items++;
|
||||||
|
}
|
||||||
|
|
||||||
|
float avg_L1 = 0, avg_L2 = 0, avg_L3 = 0;
|
||||||
|
for (int i = 0; i < voltage_buffer_items; i++) {
|
||||||
|
avg_L1 += voltage_buffer_L1[i];
|
||||||
|
avg_L2 += voltage_buffer_L2[i];
|
||||||
|
avg_L3 += voltage_buffer_L3[i];
|
||||||
|
}
|
||||||
|
avg_L1 /= voltage_buffer_items;
|
||||||
|
avg_L2 /= voltage_buffer_items;
|
||||||
|
avg_L3 /= voltage_buffer_items;
|
||||||
|
|
||||||
|
if (fabs(U1 - avg_L1) > avg_L1 * VOLTAGE_FLUCTUATION_THRESHOLD ||
|
||||||
|
fabs(U2 - avg_L2) > avg_L2 * VOLTAGE_FLUCTUATION_THRESHOLD ||
|
||||||
|
fabs(U3 - avg_L3) > avg_L3 * VOLTAGE_FLUCTUATION_THRESHOLD) {
|
||||||
|
printf("ORNO: Voltage fluctuation detected! Switching to high frequency polling for %d seconds.\n", HIGH_FREQ_MODE_DURATION);
|
||||||
|
high_frequency_mode_end_time = time(NULL) + HIGH_FREQ_MODE_DURATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate voltages (150V - 280V is reasonable range for EU grid) */
|
||||||
|
if (is_valid_float(U1, 150.0, 280.0) &&
|
||||||
|
is_valid_float(U2, 150.0, 280.0) &&
|
||||||
|
is_valid_float(U3, 150.0, 280.0)) {
|
||||||
|
mqtt_send_U(U1, U2, U3);
|
||||||
|
influx_send_U(U1, U2, U3);
|
||||||
|
printf("ORNO: MQTT: Published voltages\n");
|
||||||
|
} else {
|
||||||
|
printf("ORNO: MQTT: Skipping voltages - invalid values\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usleep(100000); /* 100ms delay between queries */
|
||||||
|
|
||||||
|
// PRADY (Currents)
|
||||||
|
memset((char *)reg, 0, sizeof(reg));
|
||||||
|
num = modbus_read_timed(ctx, 0x16, 6, reg); // ORNO
|
||||||
|
if (num != 6)
|
||||||
|
{
|
||||||
|
printf("ORNO: %x \t\n", 0x16);
|
||||||
|
printf("ORNO: Failed to read currents: %s\n", modbus_strerror(errno));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
I1 = modbus_get_float_abcd(®[0]);
|
||||||
|
I2 = modbus_get_float_abcd(®[2]);
|
||||||
|
I3 = modbus_get_float_abcd(®[4]);
|
||||||
|
printf("ORNO: Currents: L1=%.2f A, L2=%.2f A, L3=%.2f A\n", I1, I2, I3);
|
||||||
|
|
||||||
|
/* Validate currents (0-100A is reasonable range for home use) */
|
||||||
|
if (is_valid_float(I1, 0.0, 100.0) &&
|
||||||
|
is_valid_float(I2, 0.0, 100.0) &&
|
||||||
|
is_valid_float(I3, 0.0, 100.0)) {
|
||||||
|
mqtt_send_I(I1, I2, I3);
|
||||||
|
influx_send_I(I1, I2, I3);
|
||||||
|
printf("ORNO: MQTT: Published currents\n");
|
||||||
|
} else {
|
||||||
|
printf("ORNO: MQTT: Skipping currents - invalid values\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usleep(100000); /* 100ms delay between queries */
|
||||||
|
|
||||||
|
memset((char *)reg, 0, sizeof(reg));
|
||||||
|
num = modbus_read_timed(ctx, 0x1c, 8, reg);
|
||||||
|
if (num != 8)
|
||||||
|
{ // number of read registers is not the one expected
|
||||||
|
printf("ORNO: %x \t\n", 0x1c);
|
||||||
|
printf("ORNO: Failed to read: %s\n", modbus_strerror(errno));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
P_Tot = modbus_get_float_abcd(®[0]);
|
||||||
|
P1 = modbus_get_float_abcd(®[2]);
|
||||||
|
P2 = modbus_get_float_abcd(®[4]);
|
||||||
|
P3 = modbus_get_float_abcd(®[6]);
|
||||||
|
printf("ORNO: Power: Total=%.3f W, L1=%.3f W, L2=%.3f W, L3=%.3f W\n", P_Tot, P1, P2, P3);
|
||||||
|
|
||||||
|
/* Validate power (-25000W to +25000W, allows negative for generation) */
|
||||||
|
if (is_valid_float(P_Tot, -25000.0, 25000.0) &&
|
||||||
|
is_valid_float(P1, -10000.0, 10000.0) &&
|
||||||
|
is_valid_float(P2, -10000.0, 10000.0) &&
|
||||||
|
is_valid_float(P3, -10000.0, 10000.0)) {
|
||||||
|
mqtt_send_P(P_Tot, P1, P2, P3);
|
||||||
|
influx_send_P(P_Tot, P1, P2, P3);
|
||||||
|
printf("ORNO: MQTT: Published power\n");
|
||||||
|
} else {
|
||||||
|
printf("ORNO: MQTT: Skipping power - invalid values\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usleep(100000); /* 100ms delay between queries */
|
||||||
|
|
||||||
|
memset((char *)reg, 0, sizeof(reg));
|
||||||
|
num = modbus_read_timed(ctx, 0x100, 8, reg);
|
||||||
|
if (num != 8)
|
||||||
|
{ // number of read registers is not the one expected
|
||||||
|
printf("ORNO: %x \t\n", 0x100);
|
||||||
|
printf("ORNO: Failed to read: %s\n", modbus_strerror(errno));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
W_Tot = modbus_get_float_abcd(®[0]);
|
||||||
|
W1 = modbus_get_float_abcd(®[2]);
|
||||||
|
W2 = modbus_get_float_abcd(®[4]);
|
||||||
|
W3 = modbus_get_float_abcd(®[6]);
|
||||||
|
printf("ORNO: Energy: Total=%.3f kWh, L1=%.3f kWh, L2=%.3f kWh, L3=%.3f kWh\n", W_Tot, W1, W2, W3);
|
||||||
|
|
||||||
|
/* Validate energy (0-1000000 kWh is reasonable range) */
|
||||||
|
if (is_valid_float(W_Tot, 0.0, 1000000.0) &&
|
||||||
|
is_valid_float(W1, 0.0, 1000000.0) &&
|
||||||
|
is_valid_float(W2, 0.0, 1000000.0) &&
|
||||||
|
is_valid_float(W3, 0.0, 1000000.0)) {
|
||||||
|
mqtt_send_W(W_Tot, W1, W2, W3);
|
||||||
|
influx_send_W(W_Tot, W1, W2, W3);
|
||||||
|
printf("ORNO: MQTT: Published energy\n");
|
||||||
|
} else {
|
||||||
|
printf("ORNO: MQTT: Skipping energy - invalid values\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usleep(10000); /* 10ms delay between queries */
|
||||||
|
|
||||||
|
// CZESTOTLIWOSC (Frequency)
|
||||||
|
memset((char *)reg, 0, sizeof(reg));
|
||||||
|
num = modbus_read_timed(ctx, 0x14, 2, reg); // ORNO
|
||||||
|
if (num != 2)
|
||||||
|
{
|
||||||
|
printf("ORNO: %x \t\n", 0x14);
|
||||||
|
printf("ORNO: Failed to read frequency: %s\n", modbus_strerror(errno));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Freq = modbus_get_float_abcd(®[0]);
|
||||||
|
printf("ORNO: Frequency: %.4f Hz\n", Freq);
|
||||||
|
|
||||||
|
/* Validate frequency (47-53 Hz is reasonable range for EU/US grid) */
|
||||||
|
if (is_valid_float(Freq, 47.0, 53.0)) {
|
||||||
|
mqtt_send_Hz(Freq);
|
||||||
|
influx_send_Hz(Freq);
|
||||||
|
printf("ORNO: MQTT: Published frequency\n");
|
||||||
|
} else {
|
||||||
|
printf("ORNO: MQTT: Skipping frequency - invalid value\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modbus_close(ctx);
|
||||||
|
modbus_free(ctx);
|
||||||
|
}
|
||||||
|
if (do_sun2k)
|
||||||
|
{
|
||||||
|
/* Delay between ORNO and SUN2K as configured */
|
||||||
|
if (do_orno) {
|
||||||
|
printf("\nSUN2K: Waiting %d ms before SUN2K...\n\n", DELAY_BETWEEN_DEVICES/1000);
|
||||||
|
usleep(DELAY_BETWEEN_DEVICES);
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t rawtime;
|
||||||
|
struct tm *timeinfo;
|
||||||
|
|
||||||
|
time(&rawtime);
|
||||||
|
timeinfo = localtime(&rawtime);
|
||||||
|
printf("SUN2K: === SUN2K Reading Started: %s", asctime(timeinfo));
|
||||||
|
//Create a new RTU context with proper serial parameters (in this example,
|
||||||
|
//device name /dev/ttyS0, baud rate 9600, no parity bit, 8 data bits, 1 stop bit)
|
||||||
|
modbus_t *ctx = modbus_new_rtu(USB_DEV, 9600, 'N', 8, 1);
|
||||||
|
if (!ctx)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "SUN2K: Failed to create the context: %s\n", modbus_strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
//Set the Modbus address of the remote slave (to 3)
|
||||||
|
modbus_set_slave(ctx, SUN2000_SLAVE);
|
||||||
|
|
||||||
|
if (modbus_connect(ctx) == -1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "SUN2K: Unable to connect: %s\n", modbus_strerror(errno));
|
||||||
|
modbus_free(ctx);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Configure timeouts for SUN2K from #define */
|
||||||
|
modbus_rtu_set_rts_delay(ctx, SUN2K_RTS_DELAY);
|
||||||
|
modbus_set_response_timeout(ctx, 0, 900000); /* 0.9s */
|
||||||
|
modbus_set_byte_timeout(ctx, 0, SUN2K_BYTE_TIMEOUT);
|
||||||
|
|
||||||
|
/* Display current configuration */
|
||||||
|
int rts_delay = modbus_rtu_get_rts_delay(ctx);
|
||||||
|
uint32_t response_timeout_sec, response_timeout_usec;
|
||||||
|
uint32_t byte_timeout_sec, byte_timeout_usec;
|
||||||
|
modbus_get_response_timeout(ctx, &response_timeout_sec, &response_timeout_usec);
|
||||||
|
modbus_get_byte_timeout(ctx, &byte_timeout_sec, &byte_timeout_usec);
|
||||||
|
printf("SUN2K: RTS Delay %u us\n", rts_delay);
|
||||||
|
printf("SUN2K: Response Timeout %u,%06u s\n", response_timeout_sec, response_timeout_usec);
|
||||||
|
printf("SUN2K: Byte Timeout %u,%06u s\n", byte_timeout_sec, byte_timeout_usec);
|
||||||
|
|
||||||
|
/* Flush serial buffer and wait for device to be ready */
|
||||||
|
modbus_flush(ctx);
|
||||||
|
usleep(100000); /* 100ms delay before first read */
|
||||||
|
|
||||||
|
/* Enable libmodbus debug to print raw frames */
|
||||||
|
/* libmodbus debug disabled for normal runs */
|
||||||
|
/* modbus_set_debug(ctx, TRUE); */
|
||||||
|
|
||||||
|
uint16_t reg[32]; // will store read registers values
|
||||||
|
int num = 0;
|
||||||
|
int proba = 0;
|
||||||
|
char str_buf[32];
|
||||||
|
|
||||||
|
for (int i = 0; i < sizeof(mbReg) / sizeof(t_mb_reg); i++)
|
||||||
|
{
|
||||||
|
memset(str_buf, 0, sizeof(str_buf));
|
||||||
|
proba = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
num = modbus_read_timed(ctx, mbReg[i].ireg, mbReg[i].num_reg, reg);
|
||||||
|
} while ((num != mbReg[i].num_reg) && (proba++ < 10));
|
||||||
|
|
||||||
|
printf("SUN2K: %10i\t", mbReg[i].ireg);
|
||||||
|
printf("%u\t", reg[0]);
|
||||||
|
if (mbReg[i].num_reg == 2)
|
||||||
|
printf("%u\t", reg[1]);
|
||||||
|
else
|
||||||
|
printf("\t");
|
||||||
|
if (num != mbReg[i].num_reg)
|
||||||
|
{ // number of read registers is not the one expected
|
||||||
|
printf("%20.20s \t", mbReg[i].reg_id);
|
||||||
|
printf("SUN2K: Failed to %i read: %s\n", proba, modbus_strerror(errno));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("SUN2K: %20.20s \t", mbReg[i].reg_id);
|
||||||
|
switch (mbReg[i].type)
|
||||||
|
{
|
||||||
|
case rgStr:
|
||||||
|
{
|
||||||
|
for (int j = 0; j < mbReg[i].num_reg; j++)
|
||||||
|
{
|
||||||
|
printf("%c%c", (reg[j] / 256), (reg[j] % 256));
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case rgData:
|
||||||
|
{
|
||||||
|
time_t td = reg[0] * 65536 + reg[1];
|
||||||
|
timeinfo = localtime(&td);
|
||||||
|
printf("%s", asctime(timeinfo));
|
||||||
|
sprintf(str_buf, "\"%s\"", asctime(timeinfo));
|
||||||
|
str_buf[strlen(str_buf) - 2] = '"';
|
||||||
|
str_buf[strlen(str_buf) - 1] = '\0';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case rgI32:
|
||||||
|
{
|
||||||
|
int dh = reg[0];
|
||||||
|
int dl = reg[1];
|
||||||
|
int d = (dh << 16) + dl;
|
||||||
|
if (mbReg[i].scale == 1)
|
||||||
|
{
|
||||||
|
printf("%i", d);
|
||||||
|
sprintf(str_buf, "%i", d);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double f = d;
|
||||||
|
f /= mbReg[i].scale;
|
||||||
|
printf("%lf", f);
|
||||||
|
sprintf(str_buf, "%lf", f);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case rgI16:
|
||||||
|
{
|
||||||
|
int d = reg[0];
|
||||||
|
if (mbReg[i].scale == 1)
|
||||||
|
{
|
||||||
|
printf("%i", d);
|
||||||
|
sprintf(str_buf, "%i", d);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double f = d;
|
||||||
|
f /= mbReg[i].scale;
|
||||||
|
printf("%lf", f);
|
||||||
|
sprintf(str_buf, "%lf", f);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case rgU32:
|
||||||
|
{
|
||||||
|
unsigned int dh = reg[0];
|
||||||
|
unsigned int dl = reg[1];
|
||||||
|
unsigned int d = (dh << 16) + dl;
|
||||||
|
|
||||||
|
if (mbReg[i].scale == 1)
|
||||||
|
{
|
||||||
|
printf("%i", d);
|
||||||
|
sprintf(str_buf, "%i", d);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double f = d;
|
||||||
|
f /= mbReg[i].scale;
|
||||||
|
printf("%lf", f);
|
||||||
|
sprintf(str_buf, "%lf", f);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case rgU16:
|
||||||
|
{
|
||||||
|
unsigned int d = reg[0];
|
||||||
|
if (mbReg[i].scale == 1)
|
||||||
|
{
|
||||||
|
printf("%i", d);
|
||||||
|
sprintf(str_buf, "%i", d);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double f = d;
|
||||||
|
f /= mbReg[i].scale;
|
||||||
|
printf("%lf", f);
|
||||||
|
sprintf(str_buf, "%lf", f);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
for (int j = 0; j < mbReg[i].num_reg; j++)
|
||||||
|
{
|
||||||
|
printf("%i:", reg[j]);
|
||||||
|
sprintf(str_buf, "%i", reg[j]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mbReg[i].type != rgData)
|
||||||
|
printf(" %s\n", mbReg[i].units);
|
||||||
|
}
|
||||||
|
mqtt_send_SUN2K(&mbReg[i], str_buf);
|
||||||
|
influx_send_SUN2K(&mbReg[i], str_buf);
|
||||||
|
usleep(10000); /* 10ms delay between queries */
|
||||||
|
}
|
||||||
|
modbus_close(ctx);
|
||||||
|
modbus_free(ctx);
|
||||||
|
}
|
||||||
|
if (READ_LOOP) {
|
||||||
|
if (time(NULL) < high_frequency_mode_end_time) {
|
||||||
|
usleep(10000); // 10ms
|
||||||
|
} else {
|
||||||
|
sleep(5); // 5s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (READ_LOOP);
|
||||||
|
//mosq_test();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
159
ornowe51x_copilot.py
Executable file
159
ornowe51x_copilot.py
Executable file
@@ -0,0 +1,159 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
# ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||
|
# Copyright (C) 2017 Piotr Murawski
|
||||||
|
# Niniejszy program jest wolnym oprogramowaniem; mozesz go
|
||||||
|
# rozprowadzac dalej i/lub modyfikowac na warunkach Powszechnej
|
||||||
|
# Licencji Publicznej GNU, wydanej przez Fundacje Wolnego
|
||||||
|
# Oprogramowania - wedlug wersji 3-ciej tej Licencji lub ktorejs
|
||||||
|
# z pozniejszych wersji.
|
||||||
|
# Niniejszy program rozpowszechniany jest z nadzieja iz bedzie on
|
||||||
|
# uzyteczny - jednak BEZ JAKIEJKOLWIEK GWARANCJI, nawet domyslnej
|
||||||
|
# gwarancji PRZYDATNOSCI HANDLOWEJ albo PRZYDATNOSCI DO OKRESLONYCH
|
||||||
|
# ZASTOSOWAN. W celu uzyskania blizszych informacji - Powszechna
|
||||||
|
# Licencja Publiczna GNU.
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
# Z pewnoscia wraz z niniejszym programem otrzymales tez egzemplarz
|
||||||
|
# Powszechnej Licencji Publicznej GNU (GNU General Public License);
|
||||||
|
# jesli nie - napisz do Free Software Foundation, Inc., 675 Mass Ave,
|
||||||
|
# Cambridge, MA 02139, USA.
|
||||||
|
# Powszechna Licencja GNU dostepna jest rowniez na stronie:
|
||||||
|
# http://www.gnu.org/licenses/licenses.html
|
||||||
|
# nieoficjalne polskie tlumaczenie na
|
||||||
|
# http://www.gnu.org.pl
|
||||||
|
# ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import energymeter
|
||||||
|
import math
|
||||||
|
import struct
|
||||||
|
|
||||||
|
|
||||||
|
import paho.mqtt.publish as publish
|
||||||
|
|
||||||
|
#publish.single("paho/test/single", "boo", hostname="mqtt.eclipse.org")
|
||||||
|
|
||||||
|
DEVICE_0="/dev/ttyUSB0"
|
||||||
|
ADDR=2
|
||||||
|
|
||||||
|
|
||||||
|
def mem2float(reg1, reg2):
|
||||||
|
# found on beloved "satckoverflow"
|
||||||
|
raw = struct.pack('>HH', reg1, reg2)
|
||||||
|
return struct.unpack('>f', raw)[0]
|
||||||
|
|
||||||
|
def float2mem(flo):
|
||||||
|
# found on beloved "satckoverflow"
|
||||||
|
raw = struct.pack('>f', flo)
|
||||||
|
regsy=struct.unpack('>HH', raw)
|
||||||
|
return [regsy[0], regsy[1]]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print("testing @ ", DEVICE_0)
|
||||||
|
for iter in range(50):
|
||||||
|
try:
|
||||||
|
print("\n--- Iteration %d ---" % (iter+1))
|
||||||
|
testEmeter=energymeter.testPort(DEVICE_0, ADDR)
|
||||||
|
#mbcli = createClient(portDevice, adres)
|
||||||
|
testEmeter = True
|
||||||
|
if testEmeter:
|
||||||
|
print("meter ok")
|
||||||
|
mbcli = energymeter.startClient(DEVICE_0, ADDR)
|
||||||
|
#print(regs)
|
||||||
|
|
||||||
|
regs=energymeter.readRegs(mbcli, 0xE,6) # ORNO
|
||||||
|
print()
|
||||||
|
print("voltage L1 ...", round(mem2float(regs[0], regs[1]), 1))
|
||||||
|
sU1 = round(mem2float(regs[0], regs[1]), 1)
|
||||||
|
publish.single("/energy/orno/U1", round(mem2float(regs[0], regs[1]),1), hostname="10.1.1.1")
|
||||||
|
print("voltage L2 ...", round(mem2float(regs[2], regs[3]), 1))
|
||||||
|
sU2 = round(mem2float(regs[2], regs[3]), 1)
|
||||||
|
publish.single("/energy/orno/U2", round(mem2float(regs[2], regs[3]),1), hostname="10.1.1.1")
|
||||||
|
print("voltage L3 ...", round(mem2float(regs[4], regs[5]), 1))
|
||||||
|
sU3 = round(mem2float(regs[4], regs[5]), 1)
|
||||||
|
publish.single("/energy/orno/U3", round(mem2float(regs[4], regs[5]),1), hostname="10.1.1.1")
|
||||||
|
sU = "{\"U1\":%f,\"U2\":%f,\"U3\":%f}" % (sU1, sU2, sU3)
|
||||||
|
print(sU)
|
||||||
|
publish.single("/energy/orno/U", sU, hostname="10.1.1.1")
|
||||||
|
|
||||||
|
regs=energymeter.readRegs(mbcli, 0x16,6)
|
||||||
|
print()
|
||||||
|
print("current L1 ...", round(mem2float(regs[0], regs[1]), 2))
|
||||||
|
sI1 = round(mem2float(regs[0], regs[1]), 2)
|
||||||
|
publish.single("/energy/orno/I1", round(mem2float(regs[0], regs[1]),2), hostname="10.1.1.1")
|
||||||
|
print("current L2 ...", round(mem2float(regs[2], regs[3]), 2))
|
||||||
|
sI2 = round(mem2float(regs[2], regs[3]), 2)
|
||||||
|
publish.single("/energy/orno/I2", round(mem2float(regs[2], regs[3]),2), hostname="10.1.1.1")
|
||||||
|
print("current L3 ...", round(mem2float(regs[4], regs[5]), 2))
|
||||||
|
sI3 = round(mem2float(regs[4], regs[5]), 2)
|
||||||
|
publish.single("/energy/orno/I3", round(mem2float(regs[4], regs[5]),2), hostname="10.1.1.1")
|
||||||
|
sI = "{\"I1\":%f,\"I2\":%f,\"I3\":%f}" % (sI1, sI2, sI3)
|
||||||
|
print(sI)
|
||||||
|
publish.single("/energy/orno/I", sI, hostname="10.1.1.1")
|
||||||
|
|
||||||
|
regs=energymeter.readRegs(mbcli, 0x1C,8)
|
||||||
|
print()
|
||||||
|
print("power total ..", round(mem2float(regs[0], regs[1]), 3))
|
||||||
|
sP_Tot = round(mem2float(regs[0], regs[1]), 3)
|
||||||
|
publish.single("/energy/orno/P_Tot", round(mem2float(regs[0], regs[1]),3), hostname="10.1.1.1")
|
||||||
|
print("power L1 ...", round(mem2float(regs[2], regs[3]), 3))
|
||||||
|
sP1 = round(mem2float(regs[2], regs[3]), 3)
|
||||||
|
publish.single("/energy/orno/P1", round(mem2float(regs[2], regs[3]),3), hostname="10.1.1.1")
|
||||||
|
print("power L2 ...", round(mem2float(regs[4], regs[5]), 3))
|
||||||
|
sP2 = round(mem2float(regs[4], regs[5]), 3)
|
||||||
|
publish.single("/energy/orno/P2", round(mem2float(regs[4], regs[5]),3), hostname="10.1.1.1")
|
||||||
|
print("power L3 ...", round(mem2float(regs[6], regs[7]), 3))
|
||||||
|
sP3 = round(mem2float(regs[6], regs[7]), 3)
|
||||||
|
publish.single("/energy/orno/P3", round(mem2float(regs[6], regs[7]),3), hostname="10.1.1.1")
|
||||||
|
sP = "{\"P_Tot\":%f,\"P1\":%f,\"P2\":%f,\"P3\":%f}" % (sP_Tot, sP1, sP2, sP3)
|
||||||
|
print(sP)
|
||||||
|
publish.single("/energy/orno/P", sP, hostname="10.1.1.1")
|
||||||
|
|
||||||
|
regs=energymeter.readRegs(mbcli, 0x100,8)
|
||||||
|
print()
|
||||||
|
print("energy total..", round(mem2float(regs[0], regs[1]), 3))
|
||||||
|
sW_Tot = round(mem2float(regs[0], regs[1]), 3)
|
||||||
|
publish.single("/energy/orno/W_Tot", round(mem2float(regs[0], regs[1]),3), hostname="10.1.1.1")
|
||||||
|
print("energy L1 ...", round(mem2float(regs[2], regs[3]), 3))
|
||||||
|
sW1 = round(mem2float(regs[2], regs[3]), 3)
|
||||||
|
publish.single("/energy/orno/W1", round(mem2float(regs[2], regs[3]),3), hostname="10.1.1.1")
|
||||||
|
print("energy L2 ...", round(mem2float(regs[4], regs[5]), 3))
|
||||||
|
sW2 = round(mem2float(regs[4], regs[5]), 3)
|
||||||
|
publish.single("/energy/orno/W2", round(mem2float(regs[4], regs[5]),3), hostname="10.1.1.1")
|
||||||
|
print("energy L3 ...", round(mem2float(regs[6], regs[7]), 3))
|
||||||
|
sW3 = round(mem2float(regs[6], regs[7]), 3)
|
||||||
|
publish.single("/energy/orno/W3", round(mem2float(regs[6], regs[7]), 3), hostname="10.1.1.1")
|
||||||
|
sW = "{\"W_Tot\":%f,\"W1\":%f,\"W2\":%f,\"W3\":%f}" % (sW_Tot, sW1, sW2, sW3)
|
||||||
|
print(sW)
|
||||||
|
publish.single("/energy/orno/W", sW, hostname="10.1.1.1")
|
||||||
|
regs=energymeter.readRegs(mbcli, 0x14,2)
|
||||||
|
print()
|
||||||
|
print("net frequency.", round(mem2float(regs[0], regs[1]), 4))
|
||||||
|
sFreq = round(mem2float(regs[0], regs[1]), 4)
|
||||||
|
publish.single("/energy/orno/Hz", round(mem2float(regs[0], regs[1]),4), hostname="10.1.1.1")
|
||||||
|
else:
|
||||||
|
print("ERROR: meter not found")
|
||||||
|
except Exception as e:
|
||||||
|
print('Iteration %d error:' % (iter+1), e)
|
||||||
|
finally:
|
||||||
|
import time
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
25
read-energy.service
Normal file
25
read-energy.service
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Energy Meter Reader (ORNO & SUN2K)
|
||||||
|
Documentation=file:///home/ms/projekty/python3/modebus/ornoserial3/CONFIG.md
|
||||||
|
After=network.target mosquitto.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
User=ms
|
||||||
|
Group=dialout
|
||||||
|
WorkingDirectory=/home/ms/projekty/python3/modebus/ornoserial3
|
||||||
|
ExecStart=/home/ms/projekty/python3/modebus/ornoserial3/modborno3
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
SyslogIdentifier=read-energy
|
||||||
|
|
||||||
|
# Restart on failure
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5s
|
||||||
|
|
||||||
|
# Security settings
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
16
read-energy.timer
Normal file
16
read-energy.timer
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Energy Meter Reader Timer (every 10s)
|
||||||
|
Documentation=file:///home/ms/projekty/python3/modebus/ornoserial3/CONFIG.md
|
||||||
|
Requires=read-energy.service
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
# Uruchom po starcie systemu po 30 sekundach
|
||||||
|
OnBootSec=30s
|
||||||
|
# Powtarzaj co 10 sekund
|
||||||
|
OnUnitActiveSec=10s
|
||||||
|
# Jeśli nie udało się uruchomić o czasie, uruchom jak najszybciej
|
||||||
|
Persistent=true
|
||||||
|
AccuracySec=1s
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
Reference in New Issue
Block a user