initial docker support on x86
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
voltronic-cli/bin/skymax.bak
|
||||
voltronic-cli/bin/skymax
|
||||
voltronic-cli/test/
|
||||
CMakeFiles
|
||||
CMakeCache.txt
|
||||
cmake_install.cmake
|
||||
Makefile
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
FROM debian:stretch
|
||||
|
||||
RUN apt update && apt install -y \
|
||||
curl \
|
||||
git \
|
||||
build-essential \
|
||||
cmake \
|
||||
jq \
|
||||
mosquitto-clients
|
||||
|
||||
ADD sources/ /opt/
|
||||
ADD config/ /etc/skymax/
|
||||
|
||||
RUN cd /opt/voltronic-cli && \
|
||||
mkdir bin && cmake . && make
|
||||
|
||||
WORKDIR /opt
|
||||
ENTRYPOINT ["/bin/bash", "/opt/voltronic-mqtt/entrypoint.sh"]
|
||||
@@ -0,0 +1,13 @@
|
||||
## A Docker based Home Assistant interface for Voltronic Solar Inverters
|
||||
|
||||
This project [was derived](https://github.com/leithhobson/skymax-demo-Original) from the 'skymax' [C based monitoring application](https://skyboo.net/2017/03/monitoring-voltronic-power-axpert-mex-inverter-under-linux/) designed to take the monitoring data from Voltronic, Axpert, Mppsolar PIP, Voltacon, Effekta, and other branded OEM Inverters and send it to a Home Assistant MQTT server for ingestion...
|
||||
|
||||
The program can also receive commands from Home Assistant (via MQTT) to change the state of the inverter remotely.
|
||||
|
||||
By remotely setting values via MQTT you can for example, change the power mode to '_solar only_' during the day, but then change back to '_grid mode charging_' for your AGM batteries in the evenings - But if it's raining (based on data from your weather station), Set the charge mode to `PCP02` _(Charge based on 'Solar and Utility')_...
|
||||
|
||||
The program is designed to be run in a Docker Container, and can be deployed on a lightweight SBC next to your Inverter (i.e. an Orange Pi Zero running Arabian), and read data via the RS232 or USB ports on the back of the Inverter.
|
||||
|
||||
----
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"server": "10.16.10.5",
|
||||
"port": "1883",
|
||||
"topic": "homeassistant",
|
||||
"devicename": "voltronic"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#This is the settings file, all comment lines should start with a hash mark.
|
||||
|
||||
# The device to read from...
|
||||
# Use: /dev/ttyS0 if you have a serial device or /dev/hidraw0 if you're connecting via USB.
|
||||
device=/dev/ttyS0
|
||||
|
||||
# How many times per hour is the program going to run...
|
||||
# This is used to calculate the PV & Load Watt Hours between runs...
|
||||
# If unsure, leave as default - it will run every minute...
|
||||
|
||||
# (120 = every 30 seconds)...
|
||||
run_interval=120
|
||||
|
||||
# This allos you to modify the amperage in case the inverter is giving an incorrect
|
||||
# reading compared to measurement tools. Normally this will remain '1'
|
||||
amperage_factor=1.0
|
||||
|
||||
# This allos you to modify the wattage in case the inverter is giving an incorrect
|
||||
# reading compared to measurement tools. Normally this will remain '1'
|
||||
watt_factor=1.01
|
||||
@@ -0,0 +1,26 @@
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
voltronic-mqtt:
|
||||
|
||||
build: .
|
||||
container_name: voltronic-mqtt
|
||||
|
||||
privileged: true
|
||||
restart: always
|
||||
|
||||
volumes:
|
||||
- ./config/:/etc/skymax/
|
||||
|
||||
devices:
|
||||
# - "/dev/mem:/dev/mem"
|
||||
|
||||
# USB Port Mapping
|
||||
- /dev/bus/usb:/dev/bus/usb:rwm
|
||||
- /dev/ttyUSB0:/dev/ttyUSB0:rwm
|
||||
- /dev/ttyUSB1:/dev/ttyUSB1:rwm
|
||||
|
||||
# Serial Port Mapping...
|
||||
- /dev/ttyS0:/dev/ttyS0
|
||||
- /dev/ttyS1:/dev/ttyS1
|
||||
- /dev/ttyS2:/dev/ttyS2
|
||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
|
||||
PROJECT("skymax")
|
||||
|
||||
set (CMAKE_CXX_FLAGS "-O2 --std=c++0x ${CMAKE_CXX_FLAGS}")
|
||||
|
||||
file(GLOB SOURCES *.cpp)
|
||||
ADD_EXECUTABLE(bin/skymax ${SOURCES})
|
||||
target_link_libraries(bin/skymax -lpthread)
|
||||
@@ -0,0 +1,340 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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 2 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.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -0,0 +1,54 @@
|
||||
--------------------------------------------------------------------------------------
|
||||
skymax
|
||||
--------------------------------------------------------------------------------------
|
||||
This project was forked from manio/skymax-demo on git since it allows me to query my
|
||||
own Axpert solar inverter. A company in Taiwan called Voltronic sells a hardware plat-
|
||||
form used in many different brands of inverter. My brand just happens to be one of
|
||||
those Voltronic clones so all the same commands apply.
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
compilation / running
|
||||
--------------------------------------------------------------------------------------
|
||||
(You will need cmake, make and gcc already installed. Use apt-get to get them)
|
||||
|
||||
Sample build/compilation procedure:
|
||||
$ git clone https://github.com/nrm21/skymax-demo
|
||||
$ cd skymax-demo
|
||||
$ mkdir out
|
||||
$ cd out
|
||||
$ cmake ..
|
||||
$ make
|
||||
|
||||
Then you need to get the real hidraw device name (probably hidraw0):
|
||||
$ dmesg | grep hidraw
|
||||
|
||||
and then run the program like this (assuming it is located in '/opt/skymax/out' dir):
|
||||
$ /opt/skymax/out/skymax
|
||||
|
||||
The program will run once, it will spit out values it receives from the inverter in
|
||||
JSON format and exits. It was intended to be run by telegraf using the exec plugin
|
||||
every 15 seconds, and that output data will be imported into an InfluxDB instance
|
||||
where it can be easily graphed by grafana (or some other "TIG or TICK stack"-like
|
||||
software suite).
|
||||
|
||||
The program can also be made to send raw commands to the inverter if you choose:
|
||||
$ /opt/skymax/out/skymax -r POP00 # set utility output mode
|
||||
$ /opt/skymax/out/skymax -r POP02 # set SBU output mode
|
||||
$ /opt/skymax/out/skymax -r PCP00 # set utility charging mode
|
||||
$ /opt/skymax/out/skymax -r PCP03 # set solar only charging mode
|
||||
etc...
|
||||
|
||||
These commands can of course be scripted and put into a cron job to run them at a
|
||||
certain time of day/week as well (say, every morning at 8am switch to solar charging
|
||||
mode only and SBU output mode).
|
||||
|
||||
See this protocol manual for more commands that can be sent:
|
||||
http://forums.aeva.asn.au/uploads/293/HS_MS_MSX_RS232_Protocol_20140822_after_current_upgrade.pdf
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
GNU License
|
||||
--------------------------------------------------------------------------------------
|
||||
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 2 of the License, or (at your option) any later version. See the file
|
||||
COPYING for more information.
|
||||
@@ -0,0 +1,34 @@
|
||||
// @author iain
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "inputparser.h"
|
||||
|
||||
// This class simply finds cmd line args and parses them for use in a program.
|
||||
// It is not posix compliant and wont work with args like: ./program -xf filename
|
||||
// You must place each arg after its own seperate dash like: ./program -x -f filename
|
||||
|
||||
InputParser::InputParser (int &argc, char **argv)
|
||||
{
|
||||
for (int i=1; i < argc; ++i)
|
||||
this->tokens.push_back(std::string(argv[i]));
|
||||
}
|
||||
const std::string& InputParser::getCmdOption(const std::string &option) const
|
||||
{
|
||||
std::vector<std::string>::const_iterator itr;
|
||||
itr = std::find(this->tokens.begin(), this->tokens.end(), option);
|
||||
if (itr != this->tokens.end() && ++itr != this->tokens.end())
|
||||
{
|
||||
return *itr;
|
||||
}
|
||||
static const std::string empty_string("");
|
||||
return empty_string;
|
||||
}
|
||||
bool InputParser::cmdOptionExists(const std::string &option) const
|
||||
{
|
||||
return std::find(this->tokens.begin(), this->tokens.end(), option)
|
||||
!= this->tokens.end();
|
||||
}
|
||||
|
||||
std::vector <std::string> tokens;
|
||||
@@ -0,0 +1,18 @@
|
||||
// inputparser.h
|
||||
// @author iain
|
||||
#ifndef INPUTPARSER_H
|
||||
#define INPUTPARSER_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
class InputParser
|
||||
{
|
||||
std::vector <std::string> tokens;
|
||||
|
||||
public:
|
||||
InputParser (int &argc, char **argv);
|
||||
const std::string& getCmdOption(const std::string &option) const;
|
||||
bool cmdOptionExists(const std::string &option) const;
|
||||
};
|
||||
|
||||
#endif // ___INPUTPARSER_H
|
||||
@@ -0,0 +1,285 @@
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
#include "main.h"
|
||||
#include "inputparser.h"
|
||||
#include "tools.h"
|
||||
|
||||
bool debugFlag = false;
|
||||
cSkymax *ups = NULL;
|
||||
atomic_bool ups_status_changed(false);
|
||||
atomic_bool ups_qmod_changed(false);
|
||||
atomic_bool ups_qpiri_changed(false);
|
||||
atomic_bool ups_qpigs_changed(false);
|
||||
atomic_bool ups_cmd_executed(false);
|
||||
|
||||
// ---------------------------------------
|
||||
// Global configs read from 'skymax.conf'
|
||||
|
||||
string devicename;
|
||||
int runinterval;
|
||||
float ampfactor;
|
||||
float wattfactor;
|
||||
|
||||
// ---------------------------------------
|
||||
|
||||
void attemptAddSetting(int *addTo, string addFrom)
|
||||
{
|
||||
try
|
||||
{
|
||||
*addTo = stof(addFrom);
|
||||
}
|
||||
catch (exception e)
|
||||
{
|
||||
cout << e.what() << '\n';
|
||||
cout << "There's probably a string in the settings file where an int should be.\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void attemptAddSetting(float *addTo, string addFrom)
|
||||
{
|
||||
try
|
||||
{
|
||||
*addTo = stof(addFrom);
|
||||
}
|
||||
catch (exception e)
|
||||
{
|
||||
cout << e.what() << '\n';
|
||||
cout << "There's probably a string in the settings file where a floating point should be.\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void getSettingsFile(string filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
string fileline, linepart1, linepart2;
|
||||
ifstream infile;
|
||||
infile.open(filename);
|
||||
while(!infile.eof())
|
||||
{
|
||||
getline(infile, fileline);
|
||||
size_t firstpos = fileline.find("#");
|
||||
if(firstpos != 0 && fileline.length() != 0) // Ignore lines starting with # (comment lines)
|
||||
{
|
||||
size_t delimiter = fileline.find("=");
|
||||
linepart1 = fileline.substr(0, delimiter);
|
||||
linepart2 = fileline.substr(delimiter+1, string::npos - delimiter);
|
||||
|
||||
if(linepart1 == "device")
|
||||
devicename = linepart2;
|
||||
else if(linepart1 == "run_interval")
|
||||
attemptAddSetting(&runinterval, linepart2);
|
||||
else if(linepart1 == "amperage_factor")
|
||||
attemptAddSetting(&factor, linepart2);
|
||||
else if(linepart1 == "watt_factor")
|
||||
attemptAddSetting(&wattfactor, linepart2);
|
||||
else if(linepart1 == "watt_factor")
|
||||
attemptAddSetting(&wattfactor, linepart2);
|
||||
|
||||
else
|
||||
continue;
|
||||
}
|
||||
}
|
||||
infile.close();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
cout << "Settings could not be read properly...\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
// Reply1
|
||||
float voltage_grid;
|
||||
float freq_grid;
|
||||
float voltage_out;
|
||||
float freq_out;
|
||||
int load_va;
|
||||
int load_watt;
|
||||
int load_percent;
|
||||
int voltage_bus;
|
||||
float voltage_batt;
|
||||
int batt_charge_current;
|
||||
int batt_capacity;
|
||||
int temp_heatsink;
|
||||
float pv_input_current;
|
||||
float pv_input_voltage;
|
||||
float pv_input_watts;
|
||||
float pv_input_watthour;
|
||||
float load_watthour = 0;
|
||||
float scc_voltage;
|
||||
int batt_discharge_current;
|
||||
char device_status[9];
|
||||
|
||||
// Reply2
|
||||
float grid_voltage_rating;
|
||||
float grid_current_rating;
|
||||
float out_voltage_rating;
|
||||
float out_freq_rating;
|
||||
float out_current_rating;
|
||||
int out_va_rating;
|
||||
int out_watt_rating;
|
||||
float batt_rating;
|
||||
float batt_recharge_voltage;
|
||||
float batt_under_voltage;
|
||||
float batt_bulk_voltage;
|
||||
float batt_float_voltage;
|
||||
int batt_type;
|
||||
int max_grid_charge_current;
|
||||
int max_charge_current;
|
||||
int in_voltage_range;
|
||||
int out_source_priority;
|
||||
int charger_source_priority;
|
||||
int machine_type;
|
||||
int topology;
|
||||
int out_mode;
|
||||
float batt_redischarge_voltage;
|
||||
|
||||
// Get command flag settings from the arguments (if any)
|
||||
InputParser cmdArgs(argc, argv);
|
||||
const string &rawcmd = cmdArgs.getCmdOption("-r");
|
||||
if(cmdArgs.cmdOptionExists("-h") || cmdArgs.cmdOptionExists("--help"))
|
||||
{
|
||||
return print_help();
|
||||
}
|
||||
if(cmdArgs.cmdOptionExists("-d"))
|
||||
{
|
||||
debugFlag = true;
|
||||
}
|
||||
lprintf("SKYMAX: Debug set");
|
||||
|
||||
// Get the rest of the settings from the conf file
|
||||
if( access( "./skymax.conf", F_OK ) != -1 ) { // file exists
|
||||
getSettingsFile("./skymax.conf");
|
||||
} else { // file doesn't exist
|
||||
getSettingsFile("/etc/skymax/skymax.conf");
|
||||
}
|
||||
|
||||
bool ups_status_changed(false);
|
||||
ups = new cSkymax(devicename);
|
||||
|
||||
if (!rawcmd.empty())
|
||||
{
|
||||
ups->ExecuteCmd(rawcmd);
|
||||
// We can piggyback on either GetStatus() function to return our result, it doesn't matter which
|
||||
printf("Reply: %s\n", ups->GetQpiriStatus()->c_str());
|
||||
}
|
||||
else // No command being sent so just run normally
|
||||
{
|
||||
ups->runMultiThread();
|
||||
|
||||
while (true)
|
||||
{
|
||||
lprintf("SKYMAX: Start loop");
|
||||
// If inverter mode changes print it to screen
|
||||
if (ups_status_changed)
|
||||
{
|
||||
int mode = ups->GetMode();
|
||||
if (mode)
|
||||
lprintf("SKYMAX: %d", mode);
|
||||
ups_status_changed = false;
|
||||
}
|
||||
|
||||
// Once we receive all queries print it to screen
|
||||
if (ups_qmod_changed && ups_qpiri_changed && ups_qpigs_changed)
|
||||
{
|
||||
ups_qmod_changed = false;
|
||||
ups_qpiri_changed = false;
|
||||
ups_qpigs_changed = false;
|
||||
|
||||
int mode = ups->GetMode();
|
||||
string *reply1 = ups->GetQpigsStatus();
|
||||
string *reply2 = ups->GetQpiriStatus();
|
||||
if (reply1 && reply2)
|
||||
{
|
||||
// Parse and display values
|
||||
sscanf(reply1->c_str(), "%f %f %f %f %d %d %d %d %f %d %d %d %f %f %f %d %s", &voltage_grid, &freq_grid, &voltage_out, &freq_out, &load_va, &load_watt, &load_percent, &voltage_bus, &voltage_batt, &batt_charge_current, &batt_capacity, &temp_heatsink, &pv_input_current, &pv_input_voltage, &scc_voltage, &batt_discharge_current, &device_status);
|
||||
sscanf(reply2->c_str(), "%f %f %f %f %f %d %d %f %f %f %f %f %d %d %d %d %d %d - %d %d %d %f", &grid_voltage_rating, &grid_current_rating, &out_voltage_rating, &out_freq_rating, &out_current_rating, &out_va_rating, &out_watt_rating, &batt_rating, &batt_recharge_voltage, &batt_under_voltage, &batt_bulk_voltage, &batt_float_voltage, &batt_type, &max_grid_charge_current, &max_charge_current, &in_voltage_range, &out_source_priority, &charger_source_priority, &machine_type, &topology, &out_mode, &batt_redischarge_voltage);
|
||||
|
||||
// There appears to be a discrepancy in actual DMM measured current vs what the meter is
|
||||
// telling me it's getting, so lets add a variable we can multiply/divide by to adjust if
|
||||
// needed. This should be set in the config so it can be changed without program recompile.
|
||||
if (debugFlag) {
|
||||
printf("SKYMAX: ampfactor from config is %.2f\n", ampfactor);
|
||||
printf("SKYMAX: wattfactor from config is %.2f\n", wattfactor);
|
||||
}
|
||||
pv_input_current = pv_input_current * ampfactor;
|
||||
// It appears on further inspection of the documentation, that the input current is actually
|
||||
// current that is going out to the battery at battery voltage (NOT at PV voltage). This
|
||||
// would explain the larger discrepancy we saw before.
|
||||
pv_input_watts = (scc_voltage * pv_input_current) * wattfactor;
|
||||
|
||||
// Calculate watt-hours generated per run interval period (given as program argument)
|
||||
pv_input_watthour = pv_input_watts / (3600 / runinterval);
|
||||
|
||||
// Only calculate load watt-hours if we are in battery mode (line mode doesn't count towards money savings)
|
||||
if (mode == 4)
|
||||
load_watthour = (float)load_watt / (3600 / runinterval);
|
||||
|
||||
// Print as JSON (output is expected to be use by telegraf to send to influxdb)
|
||||
printf("{\n");
|
||||
printf("\"Inverter_mode\":%d,\n", mode);
|
||||
printf("\"AC_grid_voltage\":%.1f,\n", voltage_grid);
|
||||
printf("\"AC_grid_frequency\":%.1f,\n", freq_grid);
|
||||
printf("\"AC_out_voltage\":%.1f,\n", voltage_out);
|
||||
printf("\"AC_out_frequency\":%.1f,\n", freq_out);
|
||||
printf("\"PV_in_voltage\":%.1f,\n", pv_input_voltage);
|
||||
printf("\"PV_in_current\":%.1f,\n", pv_input_current);
|
||||
printf("\"PV_in_watts\":%.1f,\n", pv_input_watts);
|
||||
printf("\"PV_in_watthour\":%.4f,\n", pv_input_watthour);
|
||||
printf("\"SCC_voltage\":%.4f,\n", scc_voltage);
|
||||
printf("\"Load_pct\":%d,\n", load_percent);
|
||||
printf("\"Load_watt\":%d,\n", load_watt);
|
||||
printf("\"Load_watthour\":%.4f,\n", load_watthour);
|
||||
printf("\"Load_va\":%d,\n", load_va);
|
||||
printf("\"Bus_voltage\":%d,\n", voltage_bus);
|
||||
printf("\"Heatsink_temperature\":%d,\n", temp_heatsink);
|
||||
printf("\"Battery_capacity\":%d,\n", batt_capacity);
|
||||
printf("\"Battery_voltage\":%.2f,\n", voltage_batt);
|
||||
printf("\"Battery_charge_current\":%d,\n", batt_charge_current);
|
||||
printf("\"Battery_discharge_current\":%d,\n", batt_discharge_current);
|
||||
printf("\"Load_status_on\":%c,\n", device_status[3]);
|
||||
printf("\"SCC_charge_on\":%c,\n", device_status[6]);
|
||||
printf("\"AC_charge_on\":%c,\n", device_status[7]);
|
||||
printf("\"Battery_recharge_voltage\":%.1f,\n", batt_recharge_voltage);
|
||||
printf("\"Battery_under_voltage\":%.1f,\n", batt_under_voltage);
|
||||
printf("\"Battery_bulk_voltage\":%.1f,\n", batt_bulk_voltage);
|
||||
printf("\"Battery_float_voltage\":%.1f,\n", batt_float_voltage);
|
||||
printf("\"Max_grid_charge_current\":%d,\n", max_grid_charge_current);
|
||||
printf("\"Max_charge_current\":%d,\n", max_charge_current);
|
||||
printf("\"Out_source_priority\":%d,\n", out_source_priority);
|
||||
printf("\"Charger_source_priority\":%d,\n", charger_source_priority);
|
||||
printf("\"Battery_redischarge_voltage\":%.1f\n", batt_redischarge_voltage);
|
||||
printf("}\n");
|
||||
|
||||
delete reply1;
|
||||
delete reply2;
|
||||
|
||||
// Do once and exit instead of loop endlessly
|
||||
lprintf("SKYMAX: All queries complete, exiting using goto");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
if (ups)
|
||||
delete ups;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef ___MAIN_H
|
||||
#define ___MAIN_H
|
||||
|
||||
#include <atomic>
|
||||
#include "skymax.h"
|
||||
|
||||
extern bool debugFlag;
|
||||
extern atomic_bool ups_status_changed;
|
||||
extern atomic_bool ups_qmod_changed;
|
||||
extern atomic_bool ups_qpiri_changed;
|
||||
extern atomic_bool ups_qpigs_changed;
|
||||
|
||||
#endif // ___MAIN_H
|
||||
@@ -0,0 +1,232 @@
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "skymax.h"
|
||||
#include "tools.h"
|
||||
#include "main.h"
|
||||
|
||||
cSkymax::cSkymax(std::string devicename)
|
||||
{
|
||||
device = devicename;
|
||||
status1[0] = 0;
|
||||
status2[0] = 0;
|
||||
mode = 0;
|
||||
}
|
||||
|
||||
string *cSkymax::GetQpigsStatus()
|
||||
{
|
||||
m.lock();
|
||||
string *result = new string(status1);
|
||||
m.unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
string *cSkymax::GetQpiriStatus()
|
||||
{
|
||||
m.lock();
|
||||
string *result = new string(status2);
|
||||
m.unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
void cSkymax::SetMode(char newmode)
|
||||
{
|
||||
m.lock();
|
||||
if (mode && newmode != mode)
|
||||
ups_status_changed = true;
|
||||
mode = newmode;
|
||||
m.unlock();
|
||||
}
|
||||
|
||||
int cSkymax::GetMode()
|
||||
{
|
||||
int result;
|
||||
m.lock();
|
||||
switch (mode)
|
||||
{
|
||||
case 'P': result = 1; break; // Power_On
|
||||
case 'S': result = 2; break; // Standby
|
||||
case 'L': result = 3; break; // Line
|
||||
case 'B': result = 4; break; // Battery
|
||||
case 'F': result = 5; break; // Fault
|
||||
case 'H': result = 6; break; // Power_Saving
|
||||
default: result = 0; break; // Unknown
|
||||
}
|
||||
m.unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool cSkymax::query(const char *cmd)
|
||||
{
|
||||
time_t started;
|
||||
int fd;
|
||||
int i = 0, n;
|
||||
|
||||
fd = open(this->device.data(), O_RDWR | O_NONBLOCK); // device is provided by program arg (usually /dev/hidraw0)
|
||||
if (fd == -1)
|
||||
{
|
||||
lprintf("Skymax: Unable to open device file (errno=%d %s)", errno, strerror(errno));
|
||||
sleep(10);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Generating CRC for a command
|
||||
uint16_t crc = cal_crc_half((uint8_t*)cmd, strlen(cmd));
|
||||
n = strlen(cmd);
|
||||
memcpy(&buf, cmd, n);
|
||||
lprintf("SKYMAX: Current CRC: %X %X", crc >> 8, crc & 0xff);
|
||||
buf[n++] = crc >> 8;
|
||||
buf[n++] = crc & 0xff;
|
||||
buf[n++] = 0x0d;
|
||||
|
||||
// Send a command
|
||||
write(fd, &buf, n);
|
||||
time(&started);
|
||||
|
||||
// Instead of using a fixed size for expected response length, lets find it
|
||||
// by searching for the first returned <cr> char instead.
|
||||
char *startbuf = 0;
|
||||
char *endbuf = 0;
|
||||
do
|
||||
{
|
||||
// According to protocol manual, it appears no query should ever exceed 150 byte size in response
|
||||
n = read(fd, (void*)buf+i, 120 - i);
|
||||
if (n < 0)
|
||||
{
|
||||
if (time(NULL) - started > 8) // Wait 8 secs before timeout
|
||||
{
|
||||
lprintf("SKYMAX: %s read timeout", cmd);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
usleep(10);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
i += n;
|
||||
|
||||
startbuf = (char *)&buf[0];
|
||||
endbuf = strchr(startbuf, '\r');
|
||||
//lprintf("SKYMAX: %s Current buffer: %s", cmd, startbuf);
|
||||
} while (endbuf == NULL); // Still haven't found end <cr> char as long as pointer is null
|
||||
close(fd);
|
||||
|
||||
int replysize = endbuf - startbuf + 1;
|
||||
lprintf("SKYMAX: Found <cr> at byte: %d", replysize);
|
||||
|
||||
if (buf[0]!='(' || buf[replysize-1]!=0x0d)
|
||||
{
|
||||
lprintf("SKYMAX: %s: incorrect start/stop bytes. Buffer: %s", cmd, buf);
|
||||
return false;
|
||||
}
|
||||
if (!(CheckCRC(buf, replysize)))
|
||||
{
|
||||
lprintf("SKYMAX: %s: CRC Failed! Reply size: %d Buffer: %s", cmd, replysize, buf);
|
||||
return false;
|
||||
}
|
||||
buf[replysize-3] = '\0'; // Null-terminating on first CRC byte
|
||||
lprintf("SKYMAX: %s: %d bytes read: %s", cmd, i, buf);
|
||||
|
||||
lprintf("SKYMAX: %s query finished", cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
void cSkymax::poll()
|
||||
{
|
||||
int n,j;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Reading mode
|
||||
if (!ups_qmod_changed)
|
||||
{
|
||||
if (query("QMOD"))
|
||||
{
|
||||
SetMode(buf[1]);
|
||||
ups_qmod_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Reading QPIGS status
|
||||
if (!ups_qpigs_changed)
|
||||
{
|
||||
if (query("QPIGS"))
|
||||
{
|
||||
m.lock();
|
||||
strcpy(status1, (const char*)buf+1);
|
||||
m.unlock();
|
||||
ups_qpigs_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Reading QPIRI status
|
||||
if (!ups_qpiri_changed)
|
||||
{
|
||||
if (query("QPIRI"))
|
||||
{
|
||||
m.lock();
|
||||
strcpy(status2, (const char*)buf+1);
|
||||
m.unlock();
|
||||
ups_qpiri_changed = true;
|
||||
}
|
||||
}
|
||||
sleep(5);
|
||||
}
|
||||
}
|
||||
|
||||
void cSkymax::ExecuteCmd(const string cmd)
|
||||
{
|
||||
// Sending any command raw
|
||||
if (query(cmd.data()))
|
||||
{
|
||||
m.lock();
|
||||
strcpy(status2, (const char*)buf+1);
|
||||
m.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t cSkymax::cal_crc_half(uint8_t *pin, uint8_t len)
|
||||
{
|
||||
uint16_t crc;
|
||||
|
||||
uint8_t da;
|
||||
uint8_t *ptr;
|
||||
uint8_t bCRCHign;
|
||||
uint8_t bCRCLow;
|
||||
|
||||
uint16_t crc_ta[16]=
|
||||
{
|
||||
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
|
||||
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef
|
||||
};
|
||||
ptr=pin;
|
||||
crc=0;
|
||||
|
||||
while(len--!=0)
|
||||
{
|
||||
da=((uint8_t)(crc>>8))>>4;
|
||||
crc<<=4;
|
||||
crc^=crc_ta[da^(*ptr>>4)];
|
||||
da=((uint8_t)(crc>>8))>>4;
|
||||
crc<<=4;
|
||||
crc^=crc_ta[da^(*ptr&0x0f)];
|
||||
ptr++;
|
||||
}
|
||||
bCRCLow = crc;
|
||||
bCRCHign= (uint8_t)(crc>>8);
|
||||
if(bCRCLow==0x28||bCRCLow==0x0d||bCRCLow==0x0a)
|
||||
bCRCLow++;
|
||||
if(bCRCHign==0x28||bCRCHign==0x0d||bCRCHign==0x0a)
|
||||
bCRCHign++;
|
||||
crc = ((uint16_t)bCRCHign)<<8;
|
||||
crc += bCRCLow;
|
||||
return(crc);
|
||||
}
|
||||
|
||||
bool cSkymax::CheckCRC(unsigned char *data, int len)
|
||||
{
|
||||
uint16_t crc = cal_crc_half(data, len-3);
|
||||
return data[len-3]==(crc>>8) && data[len-2]==(crc&0xff);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef ___SKYMAX_H
|
||||
#define ___SKYMAX_H
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class cSkymax
|
||||
{
|
||||
unsigned char buf[1024]; //internal work buffer
|
||||
char status1[1024];
|
||||
char status2[1024];
|
||||
char mode;
|
||||
std::string device;
|
||||
std::mutex m;
|
||||
void SetMode(char newmode);
|
||||
bool CheckCRC(unsigned char *buff, int len);
|
||||
bool query(const char *cmd);
|
||||
uint16_t cal_crc_half(uint8_t *pin, uint8_t len);
|
||||
|
||||
public:
|
||||
cSkymax(std::string devicename);
|
||||
void poll();
|
||||
void runMultiThread()
|
||||
{
|
||||
std::thread t1(&cSkymax::poll, this);
|
||||
t1.detach();
|
||||
}
|
||||
string *GetQpiriStatus();
|
||||
string *GetQpigsStatus();
|
||||
int GetMode();
|
||||
void ExecuteCmd(const std::string cmd);
|
||||
};
|
||||
|
||||
#endif // ___SKYMAX_H
|
||||
@@ -0,0 +1,71 @@
|
||||
#include <mutex>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/time.h>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include "main.h"
|
||||
#include "tools.h"
|
||||
|
||||
std::mutex log_mutex;
|
||||
|
||||
void lprintf(const char *format, ...)
|
||||
{
|
||||
// Only print if debug flag is set, else do nothing
|
||||
if (debugFlag) {
|
||||
va_list ap;
|
||||
char fmt[2048];
|
||||
|
||||
//actual time
|
||||
time_t rawtime;
|
||||
struct tm *timeinfo;
|
||||
time(&rawtime);
|
||||
timeinfo = localtime(&rawtime);
|
||||
char buf[256];
|
||||
strcpy(buf, asctime(timeinfo));
|
||||
buf[strlen(buf)-1] = 0;
|
||||
|
||||
//connect with args
|
||||
snprintf(fmt, sizeof(fmt), "%s %s\n", buf, format);
|
||||
|
||||
//put on screen:
|
||||
va_start(ap, format);
|
||||
vprintf(fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
//to the logfile:
|
||||
static FILE *log;
|
||||
log_mutex.lock();
|
||||
log = fopen(LOG_FILE, "a");
|
||||
va_start(ap, format);
|
||||
vfprintf(log, fmt, ap);
|
||||
va_end(ap);
|
||||
fclose(log);
|
||||
log_mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int print_help()
|
||||
{
|
||||
printf("USAGE: skymax [-r <raw command>] | [-h | --help]\n\n");
|
||||
printf("RAW COMMAND EXAMPLES (see protocol manual for complete list):\n");
|
||||
printf("Set output source priority POP00 (Utility first)\n");
|
||||
printf(" POP01 (Solar first)\n");
|
||||
printf(" POP02 (SBU)\n");
|
||||
printf("Set charger priority PCP00 (Utility first)\n");
|
||||
printf(" PCP01 (Solar first)\n");
|
||||
printf(" PCP02 (Solar and utility)\n");
|
||||
printf(" PCP03 (Solar only)\n");
|
||||
printf("Set other commands PEa / PDa (Enable/disable buzzer)\n");
|
||||
printf(" PEb / PDb (Enable/disable overload bypass)\n");
|
||||
printf(" PEj / PDj (Enable/disable power saving)\n");
|
||||
printf(" PEu / PDu (Enable/disable overload restart)\n");
|
||||
printf(" PEx / PDx (Enable/disable backlight)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef ___TOOLS_H
|
||||
#define ___TOOLS_H
|
||||
|
||||
#define LOG_FILE "/dev/null"
|
||||
|
||||
void lprintf(const char *format, ...);
|
||||
int print_help();
|
||||
|
||||
#endif // ___TOOLS_H
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
export TERM=xterm
|
||||
|
||||
# Init the mqtt server for the first time...
|
||||
bash /opt/voltronic-mqtt/mqtt-init.sh
|
||||
|
||||
# Run the MQTT Subscriber process in the background (so that way we can change the configuration on the inverter from home assistant)
|
||||
/opt/voltronic-mqtt/mqtt-subscriber.sh &
|
||||
|
||||
# execute exactly ever minute...
|
||||
watch -n 30 /opt/voltronic-mqtt/mqtt-push.sh # > /dev/null 2>&1
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Simple script to register the MQTT topics when the container starts for the first time...
|
||||
|
||||
MQTT_SERVER=`cat /etc/skymax/mqtt.json | jq '.server' -r`
|
||||
MQTT_PORT=`cat /etc/skymax/mqtt.json | jq '.port' -r`
|
||||
MQTT_TOPIC=`cat /etc/skymax/mqtt.json | jq '.topic' -r`
|
||||
MQTT_DEVICENAME=`cat /etc/skymax/mqtt.json | jq '.devicename' -r`
|
||||
|
||||
registerTopic () {
|
||||
mosquitto_pub \
|
||||
-h $MQTT_SERVER \
|
||||
-p $MQTT_PORT \
|
||||
-t "$MQTT_TOPIC/sensor/"$MQTT_DEVICENAME"_$1/config" \
|
||||
-m "{
|
||||
\"name\": \""$MQTT_DEVICENAME"_$1\",
|
||||
\"unit_of_measurement\": \"$2\",
|
||||
\"state_topic\": \"$MQTT_TOPIC/sensor/"$MQTT_DEVICENAME"_$1\",
|
||||
\"icon\": \"mdi:$3\"
|
||||
}"
|
||||
}
|
||||
|
||||
registerInverterRawCMD () {
|
||||
mosquitto_pub \
|
||||
-h $MQTT_SERVER \
|
||||
-p $MQTT_PORT \
|
||||
-t "$MQTT_TOPIC/sensor/$MQTT_DEVICENAME/config" \
|
||||
-m "{
|
||||
\"name\": \""$MQTT_DEVICENAME"\",
|
||||
\"state_topic\": \"$MQTT_TOPIC/sensor/$MQTT_DEVICENAME\"
|
||||
}"
|
||||
}
|
||||
|
||||
registerTopic "Inverter_mode" "" "mdi-solar-power" # 1 = Power_On, 2 = Standby, 3 = Line, 4 = Battery, 5 = Fault, 6 = Power_Saving, 7 = Unknown
|
||||
registerTopic "AC_grid_voltage" "V" "mdi-power-plug"
|
||||
registerTopic "AC_grid_frequency" "Hz" "mdi-current-ac"
|
||||
registerTopic "AC_out_voltage" "V" "mdi-power-plug"
|
||||
registerTopic "AC_out_frequency" "Hz" "mdi-current-ac"
|
||||
registerTopic "PV_in_voltage" "V" "mdi-solar-panel-large"
|
||||
registerTopic "PV_in_current" "A" "mdi-solar-panel-large"
|
||||
registerTopic "PV_in_watts" "W" "mdi-solar-panel-large"
|
||||
registerTopic "PV_in_watthour" "Wh" "mdi-solar-panel-large"
|
||||
registerTopic "SCC_voltage" "V" "mdi-current-dc"
|
||||
registerTopic "Load_pct" "%" "mdi-brightness-percent"
|
||||
registerTopic "Load_watt" "W" "mdi-chart-bell-curve"
|
||||
registerTopic "Load_watthour" "Wh" "mdi-chart-bell-curve"
|
||||
registerTopic "Load_va" "VA" "mdi-chart-bell-curve"
|
||||
registerTopic "Bus_voltage" "V" "mdi-details"
|
||||
registerTopic "Heatsink_temperature" "" "mdi-details"
|
||||
registerTopic "Battery_capacity" "%" "mdi-battery-outline"
|
||||
registerTopic "Battery_voltage" "V" "mdi-battery-outline"
|
||||
registerTopic "Battery_charge_current" "A" "mdi-current-dc"
|
||||
registerTopic "Battery_discharge_current" "A" "mdi-current-dc"
|
||||
registerTopic "Load_status_on" "" "mdi-power"
|
||||
registerTopic "SCC_charge_on" "" "mdi-power"
|
||||
registerTopic "AC_charge_on" "" "mdi-power"
|
||||
registerTopic "Battery_recharge_voltage" "V" "mdi-current-dc"
|
||||
registerTopic "Battery_under_voltage" "V" "mdi-current-dc"
|
||||
registerTopic "Battery_bulk_voltage" "V" "mdi-current-dc"
|
||||
registerTopic "Battery_float_voltage" "V" "mdi-current-dc"
|
||||
registerTopic "Max_grid_charge_current" "A" "mdi-current-ac"
|
||||
registerTopic "Max_charge_current" "A" "mdi-current-ac"
|
||||
registerTopic "Out_source_priority" "" "mdi-grid"
|
||||
registerTopic "Charger_source_priority" "" "mdi-solar-power"
|
||||
registerTopic "Battery_redischarge_voltage" "V" "mdi-battery-negative"
|
||||
|
||||
# Add in a separate topic so we can send raw commands from assistant back to the inverter via MQTT (such as changing power modes etc)...
|
||||
registerInverterRawCMD
|
||||
Executable
+120
@@ -0,0 +1,120 @@
|
||||
#!/bin/bash
|
||||
|
||||
pushMQTTData () {
|
||||
|
||||
MQTT_SERVER=`cat /etc/skymax/mqtt.json | jq '.server' -r`
|
||||
MQTT_PORT=`cat /etc/skymax/mqtt.json | jq '.port' -r`
|
||||
MQTT_TOPIC=`cat /etc/skymax/mqtt.json | jq '.topic' -r`
|
||||
MQTT_DEVICENAME=`cat /etc/skymax/mqtt.json | jq '.devicename' -r`
|
||||
|
||||
mosquitto_pub \
|
||||
-h $MQTT_SERVER \
|
||||
-p $MQTT_PORT \
|
||||
-t "$MQTT_TOPIC/sensor/"$MQTT_DEVICENAME"_$1" \
|
||||
-m "$2"
|
||||
}
|
||||
|
||||
INVERTER_DATA=`timeout 10 /opt/voltronic-cli/bin/skymax`
|
||||
|
||||
#####################################################################################
|
||||
|
||||
Inverter_mode=`echo $INVERTER_DATA | jq '.Inverter_mode' -r`
|
||||
[ ! -z "$Inverter_mode" ] && pushMQTTData "Inverter_mode" "$Inverter_mode"
|
||||
|
||||
AC_grid_voltage=`echo $INVERTER_DATA | jq '.AC_grid_voltage' -r`
|
||||
[ ! -z "$AC_grid_voltage" ] && pushMQTTData "AC_grid_voltage" "$AC_grid_voltage"
|
||||
|
||||
AC_grid_frequency=`echo $INVERTER_DATA | jq '.AC_grid_frequency' -r`
|
||||
[ ! -z "$AC_grid_frequency" ] && pushMQTTData "AC_grid_frequency" "$AC_grid_frequency"
|
||||
|
||||
AC_out_voltage=`echo $INVERTER_DATA | jq '.AC_out_voltage' -r`
|
||||
[ ! -z "$AC_out_voltage" ] && pushMQTTData "AC_out_voltage" "$AC_out_voltage"
|
||||
|
||||
AC_out_frequency=`echo $INVERTER_DATA | jq '.AC_out_frequency' -r`
|
||||
[ ! -z "$AC_out_frequency" ] && pushMQTTData "AC_out_frequency" "$AC_out_frequency"
|
||||
|
||||
PV_in_voltage=`echo $INVERTER_DATA | jq '.PV_in_voltage' -r`
|
||||
[ ! -z "$PV_in_voltage" ] && pushMQTTData "PV_in_voltage" "$PV_in_voltage"
|
||||
|
||||
PV_in_current=`echo $INVERTER_DATA | jq '.PV_in_current' -r`
|
||||
[ ! -z "$PV_in_current" ] && pushMQTTData "PV_in_current" "$PV_in_current"
|
||||
|
||||
PV_in_watts=`echo $INVERTER_DATA | jq '.PV_in_watts' -r`
|
||||
[ ! -z "$PV_in_watts" ] && pushMQTTData "PV_in_watts" "$PV_in_watts"
|
||||
|
||||
PV_in_watthour=`echo $INVERTER_DATA | jq '.PV_in_watthour' -r`
|
||||
[ ! -z "$PV_in_watthour" ] && pushMQTTData "PV_in_watthour" "$PV_in_watthour"
|
||||
|
||||
SCC_voltage=`echo $INVERTER_DATA | jq '.SCC_voltage' -r`
|
||||
[ ! -z "$SCC_voltage" ] && pushMQTTData "SCC_voltage" "$SCC_voltage"
|
||||
|
||||
Load_pct=`echo $INVERTER_DATA | jq '.Load_pct' -r`
|
||||
[ ! -z "$Load_pct" ] && pushMQTTData "Load_pct" "$Load_pct"
|
||||
|
||||
Load_watt=`echo $INVERTER_DATA | jq '.Load_watt' -r`
|
||||
[ ! -z "$Load_watt" ] && pushMQTTData "Load_watt" "$Load_watt"
|
||||
|
||||
Load_watthour=`echo $INVERTER_DATA | jq '.Load_watthour' -r`
|
||||
[ ! -z "$Load_watthour" ] && pushMQTTData "Load_watthour" "$Load_watthour"
|
||||
|
||||
Load_va=`echo $INVERTER_DATA | jq '.Load_va' -r`
|
||||
[ ! -z "$Load_va" ] && pushMQTTData "Load_va" "$Load_va"
|
||||
|
||||
Bus_voltage=`echo $INVERTER_DATA | jq '.Bus_voltage' -r`
|
||||
[ ! -z "$Bus_voltage" ] && pushMQTTData "Bus_voltage" "$Bus_voltage"
|
||||
|
||||
Heatsink_temperature=`echo $INVERTER_DATA | jq '.Heatsink_temperature' -r`
|
||||
[ ! -z "$Heatsink_temperature" ] && pushMQTTData "Heatsink_temperature" "$Heatsink_temperature"
|
||||
|
||||
Battery_capacity=`echo $INVERTER_DATA | jq '.Battery_capacity' -r`
|
||||
[ ! -z "$Battery_capacity" ] && pushMQTTData "Battery_capacity" "$Battery_capacity"
|
||||
|
||||
Battery_voltage=`echo $INVERTER_DATA | jq '.Battery_voltage' -r`
|
||||
[ ! -z "$Battery_voltage" ] && pushMQTTData "Battery_voltage" "$Battery_voltage"
|
||||
|
||||
Battery_charge_current=`echo $INVERTER_DATA | jq '.Battery_charge_current' -r`
|
||||
[ ! -z "$Battery_charge_current" ] && pushMQTTData "Battery_charge_current" "$Battery_charge_current"
|
||||
|
||||
Battery_discharge_current=`echo $INVERTER_DATA | jq '.Battery_discharge_current' -r`
|
||||
[ ! -z "$Battery_discharge_current" ] && pushMQTTData "Battery_discharge_current" "$Battery_discharge_current"
|
||||
|
||||
Load_status_on=`echo $INVERTER_DATA | jq '.Load_status_on' -r`
|
||||
[ ! -z "$Load_status_on" ] && pushMQTTData "Load_status_on" "$Load_status_on"
|
||||
|
||||
SCC_charge_on=`echo $INVERTER_DATA | jq '.SCC_charge_on' -r`
|
||||
[ ! -z "$SCC_charge_on" ] && pushMQTTData "SCC_charge_on" "$SCC_charge_on"
|
||||
|
||||
AC_charge_on=`echo $INVERTER_DATA | jq '.AC_charge_on' -r`
|
||||
[ ! -z "$AC_charge_on" ] && pushMQTTData "AC_charge_on" "$AC_charge_on"
|
||||
|
||||
Battery_recharge_voltage=`echo $INVERTER_DATA | jq '.Battery_recharge_voltage' -r`
|
||||
[ ! -z "$Battery_recharge_voltage" ] && pushMQTTData "Battery_recharge_voltage" "$Battery_recharge_voltage"
|
||||
|
||||
Battery_under_voltage=`echo $INVERTER_DATA | jq '.Battery_under_voltage' -r`
|
||||
[ ! -z "$Battery_under_voltage" ] && pushMQTTData "Battery_under_voltage" "$Battery_under_voltage"
|
||||
|
||||
Battery_under_voltage=`echo $INVERTER_DATA | jq '.Battery_under_voltage' -r`
|
||||
[ ! -z "$Battery_under_voltage" ] && pushMQTTData "Battery_under_voltage" "$Battery_under_voltage"
|
||||
|
||||
Battery_bulk_voltage=`echo $INVERTER_DATA | jq '.Battery_bulk_voltage' -r`
|
||||
[ ! -z "$Battery_bulk_voltage" ] && pushMQTTData "Battery_bulk_voltage" "$Battery_bulk_voltage"
|
||||
|
||||
Battery_float_voltage=`echo $INVERTER_DATA | jq '.Battery_float_voltage' -r`
|
||||
[ ! -z "$Battery_float_voltage" ] && pushMQTTData "Battery_float_voltage" "$Battery_float_voltage"
|
||||
|
||||
Max_grid_charge_current=`echo $INVERTER_DATA | jq '.Max_grid_charge_current' -r`
|
||||
[ ! -z "$Max_grid_charge_current" ] && pushMQTTData "Max_grid_charge_current" "$Max_grid_charge_current"
|
||||
|
||||
Max_charge_current=`echo $INVERTER_DATA | jq '.Max_charge_current' -r`
|
||||
[ ! -z "$Max_charge_current" ] && pushMQTTData "Max_charge_current" "$Max_charge_current"
|
||||
|
||||
Out_source_priority=`echo $INVERTER_DATA | jq '.Out_source_priority' -r`
|
||||
[ ! -z "$Out_source_priority" ] && pushMQTTData "Out_source_priority" "$Out_source_priority"
|
||||
|
||||
Charger_source_priority=`echo $INVERTER_DATA | jq '.Charger_source_priority' -r`
|
||||
[ ! -z "$Charger_source_priority" ] && pushMQTTData "Charger_source_priority" "$Charger_source_priority"
|
||||
|
||||
Battery_redischarge_voltage=`echo $INVERTER_DATA | jq '.Battery_redischarge_voltage' -r`
|
||||
[ ! -z "$Battery_redischarge_voltage" ] && pushMQTTData "Battery_redischarge_voltage" "$Battery_redischarge_voltage"
|
||||
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
MQTT_SERVER=`cat /etc/skymax/mqtt.json | jq '.server' -r`
|
||||
MQTT_PORT=`cat /etc/skymax/mqtt.json | jq '.port' -r`
|
||||
MQTT_TOPIC=`cat /etc/skymax/mqtt.json | jq '.topic' -r`
|
||||
MQTT_DEVICENAME=`cat /etc/skymax/mqtt.json | jq '.devicename' -r`
|
||||
|
||||
while read rawcmd;
|
||||
do
|
||||
|
||||
echo "Incoming request send: [$rawcmd] to inverter."
|
||||
/opt/voltronic-cli/bin/skymax -r $rawcmd;
|
||||
|
||||
done < <(mosquitto_sub -h $MQTT_SERVER -p $MQTT_PORT -t "$MQTT_TOPIC/sensor/$MQTT_DEVICENAME" -q 1)
|
||||
Reference in New Issue
Block a user