diff --git a/COPYING b/COPYING new file mode 120000 index 0000000..2eac441 --- /dev/null +++ b/COPYING @@ -0,0 +1 @@ +LICENSE.GPL3 \ No newline at end of file diff --git a/COPYING.LESSER b/COPYING.LESSER new file mode 120000 index 0000000..dc71033 --- /dev/null +++ b/COPYING.LESSER @@ -0,0 +1 @@ +LICENSE.LGPL3 \ No newline at end of file diff --git a/LICENSE.GPL3 b/LICENSE.GPL3 new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE.GPL3 @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. 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 +them 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 prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. 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. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + 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 +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program 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, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU 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. But first, please read +. diff --git a/LICENSE.LGPL3 b/LICENSE.LGPL3 new file mode 100644 index 0000000..0a04128 --- /dev/null +++ b/LICENSE.LGPL3 @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser 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 +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/eastron/eastron.pro b/eastron/eastron.pro new file mode 100644 index 0000000..314b37c --- /dev/null +++ b/eastron/eastron.pro @@ -0,0 +1,14 @@ +include(../plugins.pri) + +# Generate modbus connections +MODBUS_CONNECTIONS += sdm630-registers.json +MODBUS_CONNECTIONS += sdm72-registers.json +MODBUS_CONNECTIONS += sdm120-registers.json +#MODBUS_TOOLS_CONFIG += VERBOSE +include(../modbus.pri) + +HEADERS += \ + integrationplugineastron.h + +SOURCES += \ + integrationplugineastron.cpp diff --git a/eastron/integrationplugineastron.cpp b/eastron/integrationplugineastron.cpp new file mode 100644 index 0000000..80aefa3 --- /dev/null +++ b/eastron/integrationplugineastron.cpp @@ -0,0 +1,708 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright (C) 2013 - 2024, nymea GmbH +* Copyright (C) 2025, ETM-Schurig SARL +* +* This file is part of nymea-plugins-modbus. +* +* nymea-plugins-modbus 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. +* +* nymea-plugins-modbus 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 nymea-plugins-modbus. If not, see . +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "integrationplugineastron.h" +#include "plugininfo.h" + +IntegrationPluginEastron::IntegrationPluginEastron() +{ +} + +void IntegrationPluginEastron::init() +{ + connect(hardwareManager()->modbusRtuResource(), &ModbusRtuHardwareResource::modbusRtuMasterRemoved, + this, [=](const QUuid &modbusUuid) { + qCDebug(dcEastron()) << "Modbus RTU master removed:" << modbusUuid.toString(); + + foreach (Thing *thing, myThings()) { + ThingClassId classId = thing->thingClassId(); + + if (classId == sdm630ThingClassId || classId == sdm630ConsumerThingClassId || classId == sdm630ProducerThingClassId) { + if (thing->paramValue(sdm630ThingModbusMasterUuidParamTypeId) == modbusUuid) { + thing->setStateValue(sdm630ConnectedStateTypeId, false); + delete m_sdm630Connections.take(thing); + } + } else if (classId == sdm630ConsumerThingClassId) { + if (thing->paramValue(sdm630ConsumerThingModbusMasterUuidParamTypeId) == modbusUuid) { + thing->setStateValue(sdm630ConsumerConnectedStateTypeId, false); + delete m_sdm630Connections.take(thing); + } + } else if (classId == sdm630ProducerThingClassId) { + if (thing->paramValue(sdm630ProducerThingModbusMasterUuidParamTypeId) == modbusUuid) { + thing->setStateValue(sdm630ProducerConnectedStateTypeId, false); + delete m_sdm630Connections.take(thing); + } + } else if (classId == sdm72ThingClassId) { + if (thing->paramValue(sdm72ThingModbusMasterUuidParamTypeId) == modbusUuid) { + thing->setStateValue(sdm72ConnectedStateTypeId, false); + delete m_sdm72Connections.take(thing); + } + } else if (classId == sdm72ConsumerThingClassId) { + if (thing->paramValue(sdm72ConsumerThingModbusMasterUuidParamTypeId) == modbusUuid) { + thing->setStateValue(sdm72ConsumerConnectedStateTypeId, false); + delete m_sdm72Connections.take(thing); + } + } else if (classId == sdm72ProducerThingClassId) { + if (thing->paramValue(sdm72ProducerThingModbusMasterUuidParamTypeId) == modbusUuid) { + thing->setStateValue(sdm72ProducerConnectedStateTypeId, false); + delete m_sdm72Connections.take(thing); + } + } else if (classId == sdm120ThingClassId) { + if (thing->paramValue(sdm120ThingModbusMasterUuidParamTypeId) == modbusUuid) { + thing->setStateValue(sdm120ConnectedStateTypeId, false); + delete m_sdm120Connections.take(thing); + } + } else if (classId == sdm120ConsumerThingClassId) { + if (thing->paramValue(sdm120ConsumerThingModbusMasterUuidParamTypeId) == modbusUuid) { + thing->setStateValue(sdm120ConsumerConnectedStateTypeId, false); + delete m_sdm120Connections.take(thing); + } + } else if (classId == sdm120ProducerThingClassId) { + if (thing->paramValue(sdm120ProducerThingModbusMasterUuidParamTypeId) == modbusUuid) { + thing->setStateValue(sdm120ProducerConnectedStateTypeId, false); + delete m_sdm120Connections.take(thing); + } + } else if (classId == sdm220ThingClassId) { + if (thing->paramValue(sdm220ThingModbusMasterUuidParamTypeId) == modbusUuid) { + thing->setStateValue(sdm220ConnectedStateTypeId, false); + delete m_sdm120Connections.take(thing); + } + } else if (classId == sdm220ConsumerThingClassId) { + if (thing->paramValue(sdm220ConsumerThingModbusMasterUuidParamTypeId) == modbusUuid) { + thing->setStateValue(sdm220ConsumerConnectedStateTypeId, false); + delete m_sdm120Connections.take(thing); + } + } else if (classId == sdm220ProducerThingClassId) { + if (thing->paramValue(sdm220ProducerThingModbusMasterUuidParamTypeId) == modbusUuid) { + thing->setStateValue(sdm220ProducerConnectedStateTypeId, false); + delete m_sdm120Connections.take(thing); + } + } else if (classId == sdm230ThingClassId) { + if (thing->paramValue(sdm230ThingModbusMasterUuidParamTypeId) == modbusUuid) { + thing->setStateValue(sdm230ConnectedStateTypeId, false); + delete m_sdm120Connections.take(thing); + } + } else if (classId == sdm230ConsumerThingClassId) { + if (thing->paramValue(sdm230ConsumerThingModbusMasterUuidParamTypeId) == modbusUuid) { + thing->setStateValue(sdm230ConsumerConnectedStateTypeId, false); + delete m_sdm120Connections.take(thing); + } + } else if (classId == sdm230ProducerThingClassId) { + if (thing->paramValue(sdm230ProducerThingModbusMasterUuidParamTypeId) == modbusUuid) { + thing->setStateValue(sdm230ProducerConnectedStateTypeId, false); + delete m_sdm120Connections.take(thing); + } + } + } + }); +} + +void IntegrationPluginEastron::discoverThings(ThingDiscoveryInfo *info) +{ + ThingClassId classId = info->thingClassId(); + QString modelName; + ParamTypeId discoverySlaveParamTypeId; + ParamTypeId thingSlaveParamTypeId; + ParamTypeId thingMasterUuidParamTypeId; + + if (classId == sdm630ThingClassId) { + modelName = "SDM630"; + discoverySlaveParamTypeId = sdm630DiscoverySlaveAddressParamTypeId; + thingSlaveParamTypeId = sdm630ThingSlaveAddressParamTypeId; + thingMasterUuidParamTypeId = sdm630ThingModbusMasterUuidParamTypeId; + } else if (classId == sdm630ConsumerThingClassId) { + modelName = "SDM630"; + discoverySlaveParamTypeId = sdm630ConsumerDiscoverySlaveAddressParamTypeId; + thingSlaveParamTypeId = sdm630ConsumerThingSlaveAddressParamTypeId; + thingMasterUuidParamTypeId = sdm630ConsumerThingModbusMasterUuidParamTypeId; + } else if (classId == sdm630ProducerThingClassId) { + modelName = "SDM630"; + discoverySlaveParamTypeId = sdm630ProducerDiscoverySlaveAddressParamTypeId; + thingSlaveParamTypeId = sdm630ProducerThingSlaveAddressParamTypeId; + thingMasterUuidParamTypeId = sdm630ProducerThingModbusMasterUuidParamTypeId; + } else if (classId == sdm72ThingClassId) { + modelName = "SDM72"; + discoverySlaveParamTypeId = sdm72DiscoverySlaveAddressParamTypeId; + thingSlaveParamTypeId = sdm72ThingSlaveAddressParamTypeId; + thingMasterUuidParamTypeId = sdm72ThingModbusMasterUuidParamTypeId; + } else if (classId == sdm72ConsumerThingClassId) { + modelName = "SDM72"; + discoverySlaveParamTypeId = sdm72ConsumerDiscoverySlaveAddressParamTypeId; + thingSlaveParamTypeId = sdm72ConsumerThingSlaveAddressParamTypeId; + thingMasterUuidParamTypeId = sdm72ConsumerThingModbusMasterUuidParamTypeId; + } else if (classId == sdm72ProducerThingClassId) { + modelName = "SDM72"; + discoverySlaveParamTypeId = sdm72ProducerDiscoverySlaveAddressParamTypeId; + thingSlaveParamTypeId = sdm72ProducerThingSlaveAddressParamTypeId; + thingMasterUuidParamTypeId = sdm72ProducerThingModbusMasterUuidParamTypeId; + } else if (classId == sdm120ThingClassId) { + modelName = "SDM120"; + discoverySlaveParamTypeId = sdm120DiscoverySlaveAddressParamTypeId; + thingSlaveParamTypeId = sdm120ThingSlaveAddressParamTypeId; + thingMasterUuidParamTypeId = sdm120ThingModbusMasterUuidParamTypeId; + } else if (classId == sdm120ConsumerThingClassId) { + modelName = "SDM120"; + discoverySlaveParamTypeId = sdm120ConsumerDiscoverySlaveAddressParamTypeId; + thingSlaveParamTypeId = sdm120ConsumerThingSlaveAddressParamTypeId; + thingMasterUuidParamTypeId = sdm120ConsumerThingModbusMasterUuidParamTypeId; + } else if (classId == sdm120ProducerThingClassId) { + modelName = "SDM120"; + discoverySlaveParamTypeId = sdm120ProducerDiscoverySlaveAddressParamTypeId; + thingSlaveParamTypeId = sdm120ProducerThingSlaveAddressParamTypeId; + thingMasterUuidParamTypeId = sdm120ProducerThingModbusMasterUuidParamTypeId; + } else if (classId == sdm220ThingClassId) { + modelName = "SDM220"; + discoverySlaveParamTypeId = sdm220DiscoverySlaveAddressParamTypeId; + thingSlaveParamTypeId = sdm220ThingSlaveAddressParamTypeId; + thingMasterUuidParamTypeId = sdm220ThingModbusMasterUuidParamTypeId; + } else if (classId == sdm220ConsumerThingClassId) { + modelName = "SDM220"; + discoverySlaveParamTypeId = sdm220ConsumerDiscoverySlaveAddressParamTypeId; + thingSlaveParamTypeId = sdm220ConsumerThingSlaveAddressParamTypeId; + thingMasterUuidParamTypeId = sdm220ConsumerThingModbusMasterUuidParamTypeId; + } else if (classId == sdm220ProducerThingClassId) { + modelName = "SDM220"; + discoverySlaveParamTypeId = sdm220ProducerDiscoverySlaveAddressParamTypeId; + thingSlaveParamTypeId = sdm220ProducerThingSlaveAddressParamTypeId; + thingMasterUuidParamTypeId = sdm220ProducerThingModbusMasterUuidParamTypeId; + } else if (classId == sdm230ThingClassId) { + modelName = "SDM230"; + discoverySlaveParamTypeId = sdm230DiscoverySlaveAddressParamTypeId; + thingSlaveParamTypeId = sdm230ThingSlaveAddressParamTypeId; + thingMasterUuidParamTypeId = sdm230ThingModbusMasterUuidParamTypeId; + } else if (classId == sdm230ConsumerThingClassId) { + modelName = "SDM230"; + discoverySlaveParamTypeId = sdm230ConsumerDiscoverySlaveAddressParamTypeId; + thingSlaveParamTypeId = sdm230ConsumerThingSlaveAddressParamTypeId; + thingMasterUuidParamTypeId = sdm230ConsumerThingModbusMasterUuidParamTypeId; + } else if (classId == sdm230ProducerThingClassId) { + modelName = "SDM230"; + discoverySlaveParamTypeId = sdm230ProducerDiscoverySlaveAddressParamTypeId; + thingSlaveParamTypeId = sdm230ProducerThingSlaveAddressParamTypeId; + thingMasterUuidParamTypeId = sdm230ProducerThingModbusMasterUuidParamTypeId; + } else { + info->finish(Thing::ThingErrorThingClassNotFound); + return; + } + + discoverRtuDevices(info, modelName, discoverySlaveParamTypeId, thingSlaveParamTypeId, thingMasterUuidParamTypeId); +} + +void IntegrationPluginEastron::discoverRtuDevices(ThingDiscoveryInfo *info, const QString &modelName, + const ParamTypeId &discoverySlaveParamTypeId, + const ParamTypeId &thingSlaveParamTypeId, + const ParamTypeId &thingMasterUuidParamTypeId) +{ + if (hardwareManager()->modbusRtuResource()->modbusRtuMasters().isEmpty()) { + info->finish(Thing::ThingErrorHardwareNotAvailable, + QT_TR_NOOP("No Modbus RTU interface available. Please set up the Modbus RTU interface first.")); + return; + } + + uint slaveAddress = info->params().paramValue(discoverySlaveParamTypeId).toUInt(); + if (slaveAddress == 0 || slaveAddress > 254) { + info->finish(Thing::ThingErrorInvalidParameter, + QT_TR_NOOP("The Modbus slave address must be a value between 1 and 254.")); + return; + } + + foreach (ModbusRtuMaster *modbusMaster, hardwareManager()->modbusRtuResource()->modbusRtuMasters()) { + qCDebug(dcEastron()) << "Found RTU master" << modbusMaster << "connected:" << modbusMaster->connected(); + if (!modbusMaster->connected()) + continue; + + ThingDescriptor descriptor(info->thingClassId(), modelName, + QString::number(slaveAddress) + " " + modbusMaster->serialPort()); + ParamList params; + params << Param(thingSlaveParamTypeId, slaveAddress); + params << Param(thingMasterUuidParamTypeId, modbusMaster->modbusUuid()); + descriptor.setParams(params); + info->addThingDescriptor(descriptor); + } + + info->finish(Thing::ThingErrorNoError); +} + +void IntegrationPluginEastron::setupThing(ThingSetupInfo *info) +{ + ThingClassId classId = info->thing()->thingClassId(); + + if (classId == sdm630ThingClassId || classId == sdm630ConsumerThingClassId || classId == sdm630ProducerThingClassId) { + setupSdm630(info); + } else if (classId == sdm72ThingClassId || classId == sdm72ConsumerThingClassId || classId == sdm72ProducerThingClassId) { + setupSdm72(info); + } else if (classId == sdm120ThingClassId || classId == sdm120ConsumerThingClassId || classId == sdm120ProducerThingClassId + || classId == sdm220ThingClassId || classId == sdm220ConsumerThingClassId || classId == sdm220ProducerThingClassId + || classId == sdm230ThingClassId || classId == sdm230ConsumerThingClassId || classId == sdm230ProducerThingClassId) { + setupSdm120(info); + } else { + info->finish(Thing::ThingErrorThingClassNotFound); + } +} + +void IntegrationPluginEastron::setupSdm630(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + ThingClassId classId = thing->thingClassId(); + + ParamTypeId slaveParamTypeId; + ParamTypeId masterUuidParamTypeId; + StateTypeId connectedStateTypeId; + + if (classId == sdm630ThingClassId) { + slaveParamTypeId = sdm630ThingSlaveAddressParamTypeId; + masterUuidParamTypeId = sdm630ThingModbusMasterUuidParamTypeId; + connectedStateTypeId = sdm630ConnectedStateTypeId; + } else if (classId == sdm630ConsumerThingClassId) { + slaveParamTypeId = sdm630ConsumerThingSlaveAddressParamTypeId; + masterUuidParamTypeId = sdm630ConsumerThingModbusMasterUuidParamTypeId; + connectedStateTypeId = sdm630ConsumerConnectedStateTypeId; + } else { + slaveParamTypeId = sdm630ProducerThingSlaveAddressParamTypeId; + masterUuidParamTypeId = sdm630ProducerThingModbusMasterUuidParamTypeId; + connectedStateTypeId = sdm630ProducerConnectedStateTypeId; + } + + uint address = thing->paramValue(slaveParamTypeId).toUInt(); + if (address == 0 || address > 254) { + qCWarning(dcEastron()) << "Setup failed, invalid slave address" << address; + info->finish(Thing::ThingErrorSetupFailed, + QT_TR_NOOP("The Modbus address not valid. It must be a value between 1 and 254.")); + return; + } + + QUuid uuid = thing->paramValue(masterUuidParamTypeId).toUuid(); + if (!hardwareManager()->modbusRtuResource()->hasModbusRtuMaster(uuid)) { + qCWarning(dcEastron()) << "Setup failed, Modbus RTU master not available"; + info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("The Modbus RTU interface not available.")); + return; + } + + if (m_sdm630Connections.contains(thing)) + m_sdm630Connections.take(thing)->deleteLater(); + + Sdm630ModbusRtuConnection *connection = new Sdm630ModbusRtuConnection( + hardwareManager()->modbusRtuResource()->getModbusRtuMaster(uuid), address, this); + + connect(connection, &Sdm630ModbusRtuConnection::reachableChanged, this, [=](bool reachable) { + thing->setStateValue(connectedStateTypeId, reachable); + }); + + if (classId == sdm630ThingClassId) { + connect(connection, &Sdm630ModbusRtuConnection::voltagePhaseAChanged, this, [=](float v) { + thing->setStateValue(sdm630VoltagePhaseAStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::voltagePhaseBChanged, this, [=](float v) { + thing->setStateValue(sdm630VoltagePhaseBStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::voltagePhaseCChanged, this, [=](float v) { + thing->setStateValue(sdm630VoltagePhaseCStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::currentPhaseAChanged, this, [=](float v) { + thing->setStateValue(sdm630CurrentPhaseAStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::currentPhaseBChanged, this, [=](float v) { + thing->setStateValue(sdm630CurrentPhaseBStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::currentPhaseCChanged, this, [=](float v) { + thing->setStateValue(sdm630CurrentPhaseCStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::totalCurrentPowerChanged, this, [=](float v) { + thing->setStateValue(sdm630CurrentPowerStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::powerPhaseAChanged, this, [=](float v) { + thing->setStateValue(sdm630CurrentPowerPhaseAStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::powerPhaseBChanged, this, [=](float v) { + thing->setStateValue(sdm630CurrentPowerPhaseBStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::powerPhaseCChanged, this, [=](float v) { + thing->setStateValue(sdm630CurrentPowerPhaseCStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::frequencyChanged, this, [=](float v) { + thing->setStateValue(sdm630FrequencyStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::totalEnergyConsumedChanged, this, [=](float v) { + thing->setStateValue(sdm630TotalEnergyConsumedStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::totalEnergyProducedChanged, this, [=](float v) { + thing->setStateValue(sdm630TotalEnergyProducedStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::energyConsumedPhaseAChanged, this, [=](float v) { + thing->setStateValue(sdm630EnergyConsumedPhaseAStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::energyConsumedPhaseBChanged, this, [=](float v) { + thing->setStateValue(sdm630EnergyConsumedPhaseBStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::energyConsumedPhaseCChanged, this, [=](float v) { + thing->setStateValue(sdm630EnergyConsumedPhaseCStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::energyProducedPhaseAChanged, this, [=](float v) { + thing->setStateValue(sdm630EnergyProducedPhaseAStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::energyProducedPhaseBChanged, this, [=](float v) { + thing->setStateValue(sdm630EnergyProducedPhaseBStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::energyProducedPhaseCChanged, this, [=](float v) { + thing->setStateValue(sdm630EnergyProducedPhaseCStateTypeId, v); + }); + + } else if (classId == sdm630ConsumerThingClassId) { + connect(connection, &Sdm630ModbusRtuConnection::totalCurrentPowerChanged, this, [=](float v) { + thing->setStateValue(sdm630ConsumerCurrentPowerStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::totalEnergyConsumedChanged, this, [=](float v) { + thing->setStateValue(sdm630ConsumerTotalEnergyConsumedStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::frequencyChanged, this, [=](float v) { + thing->setStateValue(sdm630ConsumerFrequencyStateTypeId, v); + }); + + } else { // sdm630Producer + connect(connection, &Sdm630ModbusRtuConnection::totalCurrentPowerChanged, this, [=](float v) { + thing->setStateValue(sdm630ProducerCurrentPowerStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::totalEnergyProducedChanged, this, [=](float v) { + thing->setStateValue(sdm630ProducerTotalEnergyProducedStateTypeId, v); + }); + connect(connection, &Sdm630ModbusRtuConnection::frequencyChanged, this, [=](float v) { + thing->setStateValue(sdm630ProducerFrequencyStateTypeId, v); + }); + } + + m_sdm630Connections.insert(thing, connection); + info->finish(Thing::ThingErrorNoError); +} + +void IntegrationPluginEastron::setupSdm72(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + ThingClassId classId = thing->thingClassId(); + + ParamTypeId slaveParamTypeId; + ParamTypeId masterUuidParamTypeId; + StateTypeId connectedStateTypeId; + + if (classId == sdm72ThingClassId) { + slaveParamTypeId = sdm72ThingSlaveAddressParamTypeId; + masterUuidParamTypeId = sdm72ThingModbusMasterUuidParamTypeId; + connectedStateTypeId = sdm72ConnectedStateTypeId; + } else if (classId == sdm72ConsumerThingClassId) { + slaveParamTypeId = sdm72ConsumerThingSlaveAddressParamTypeId; + masterUuidParamTypeId = sdm72ConsumerThingModbusMasterUuidParamTypeId; + connectedStateTypeId = sdm72ConsumerConnectedStateTypeId; + } else { + slaveParamTypeId = sdm72ProducerThingSlaveAddressParamTypeId; + masterUuidParamTypeId = sdm72ProducerThingModbusMasterUuidParamTypeId; + connectedStateTypeId = sdm72ProducerConnectedStateTypeId; + } + + uint address = thing->paramValue(slaveParamTypeId).toUInt(); + if (address == 0 || address > 254) { + qCWarning(dcEastron()) << "Setup failed, invalid slave address" << address; + info->finish(Thing::ThingErrorSetupFailed, + QT_TR_NOOP("The Modbus address not valid. It must be a value between 1 and 254.")); + return; + } + + QUuid uuid = thing->paramValue(masterUuidParamTypeId).toUuid(); + if (!hardwareManager()->modbusRtuResource()->hasModbusRtuMaster(uuid)) { + qCWarning(dcEastron()) << "Setup failed, Modbus RTU master not available"; + info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("The Modbus RTU interface not available.")); + return; + } + + if (m_sdm72Connections.contains(thing)) + m_sdm72Connections.take(thing)->deleteLater(); + + Sdm72ModbusRtuConnection *connection = new Sdm72ModbusRtuConnection( + hardwareManager()->modbusRtuResource()->getModbusRtuMaster(uuid), address, this); + + connect(connection, &Sdm72ModbusRtuConnection::reachableChanged, this, [=](bool reachable) { + thing->setStateValue(connectedStateTypeId, reachable); + }); + + if (classId == sdm72ThingClassId) { + connect(connection, &Sdm72ModbusRtuConnection::voltagePhaseAChanged, this, [=](float v) { + thing->setStateValue(sdm72VoltagePhaseAStateTypeId, v); + }); + connect(connection, &Sdm72ModbusRtuConnection::voltagePhaseBChanged, this, [=](float v) { + thing->setStateValue(sdm72VoltagePhaseBStateTypeId, v); + }); + connect(connection, &Sdm72ModbusRtuConnection::voltagePhaseCChanged, this, [=](float v) { + thing->setStateValue(sdm72VoltagePhaseCStateTypeId, v); + }); + connect(connection, &Sdm72ModbusRtuConnection::currentPhaseAChanged, this, [=](float v) { + thing->setStateValue(sdm72CurrentPhaseAStateTypeId, v); + }); + connect(connection, &Sdm72ModbusRtuConnection::currentPhaseBChanged, this, [=](float v) { + thing->setStateValue(sdm72CurrentPhaseBStateTypeId, v); + }); + connect(connection, &Sdm72ModbusRtuConnection::currentPhaseCChanged, this, [=](float v) { + thing->setStateValue(sdm72CurrentPhaseCStateTypeId, v); + }); + connect(connection, &Sdm72ModbusRtuConnection::totalCurrentPowerChanged, this, [=](float v) { + thing->setStateValue(sdm72CurrentPowerStateTypeId, v); + }); + connect(connection, &Sdm72ModbusRtuConnection::powerPhaseAChanged, this, [=](float v) { + thing->setStateValue(sdm72CurrentPowerPhaseAStateTypeId, v); + }); + connect(connection, &Sdm72ModbusRtuConnection::powerPhaseBChanged, this, [=](float v) { + thing->setStateValue(sdm72CurrentPowerPhaseBStateTypeId, v); + }); + connect(connection, &Sdm72ModbusRtuConnection::powerPhaseCChanged, this, [=](float v) { + thing->setStateValue(sdm72CurrentPowerPhaseCStateTypeId, v); + }); + connect(connection, &Sdm72ModbusRtuConnection::frequencyChanged, this, [=](float v) { + thing->setStateValue(sdm72FrequencyStateTypeId, v); + }); + connect(connection, &Sdm72ModbusRtuConnection::totalEnergyConsumedChanged, this, [=](float v) { + thing->setStateValue(sdm72TotalEnergyConsumedStateTypeId, v); + }); + connect(connection, &Sdm72ModbusRtuConnection::totalEnergyProducedChanged, this, [=](float v) { + thing->setStateValue(sdm72TotalEnergyProducedStateTypeId, v); + }); + + } else if (classId == sdm72ConsumerThingClassId) { + connect(connection, &Sdm72ModbusRtuConnection::totalCurrentPowerChanged, this, [=](float v) { + thing->setStateValue(sdm72ConsumerCurrentPowerStateTypeId, v); + }); + connect(connection, &Sdm72ModbusRtuConnection::totalEnergyConsumedChanged, this, [=](float v) { + thing->setStateValue(sdm72ConsumerTotalEnergyConsumedStateTypeId, v); + }); + connect(connection, &Sdm72ModbusRtuConnection::frequencyChanged, this, [=](float v) { + thing->setStateValue(sdm72ConsumerFrequencyStateTypeId, v); + }); + + } else { // sdm72Producer + connect(connection, &Sdm72ModbusRtuConnection::totalCurrentPowerChanged, this, [=](float v) { + thing->setStateValue(sdm72ProducerCurrentPowerStateTypeId, v); + }); + connect(connection, &Sdm72ModbusRtuConnection::totalEnergyProducedChanged, this, [=](float v) { + thing->setStateValue(sdm72ProducerTotalEnergyProducedStateTypeId, v); + }); + connect(connection, &Sdm72ModbusRtuConnection::frequencyChanged, this, [=](float v) { + thing->setStateValue(sdm72ProducerFrequencyStateTypeId, v); + }); + } + + m_sdm72Connections.insert(thing, connection); + info->finish(Thing::ThingErrorNoError); +} + +void IntegrationPluginEastron::setupSdm120(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + ThingClassId classId = thing->thingClassId(); + + ParamTypeId slaveParamTypeId; + ParamTypeId masterUuidParamTypeId; + StateTypeId connectedStateTypeId; + StateTypeId voltageStateTypeId; + StateTypeId currentStateTypeId; + StateTypeId powerStateTypeId; + StateTypeId frequencyStateTypeId; + StateTypeId totalEnergyConsumedStateTypeId; + StateTypeId totalEnergyProducedStateTypeId; + bool isEnergyMeter = false; + + if (classId == sdm120ThingClassId) { + slaveParamTypeId = sdm120ThingSlaveAddressParamTypeId; + masterUuidParamTypeId = sdm120ThingModbusMasterUuidParamTypeId; + connectedStateTypeId = sdm120ConnectedStateTypeId; + voltageStateTypeId = sdm120VoltagePhaseAStateTypeId; + currentStateTypeId = sdm120CurrentPhaseAStateTypeId; + powerStateTypeId = sdm120CurrentPowerStateTypeId; + frequencyStateTypeId = sdm120FrequencyStateTypeId; + totalEnergyConsumedStateTypeId = sdm120TotalEnergyConsumedStateTypeId; + totalEnergyProducedStateTypeId = sdm120TotalEnergyProducedStateTypeId; + isEnergyMeter = true; + } else if (classId == sdm120ConsumerThingClassId) { + slaveParamTypeId = sdm120ConsumerThingSlaveAddressParamTypeId; + masterUuidParamTypeId = sdm120ConsumerThingModbusMasterUuidParamTypeId; + connectedStateTypeId = sdm120ConsumerConnectedStateTypeId; + powerStateTypeId = sdm120ConsumerCurrentPowerStateTypeId; + frequencyStateTypeId = sdm120ConsumerFrequencyStateTypeId; + totalEnergyConsumedStateTypeId = sdm120ConsumerTotalEnergyConsumedStateTypeId; + } else if (classId == sdm120ProducerThingClassId) { + slaveParamTypeId = sdm120ProducerThingSlaveAddressParamTypeId; + masterUuidParamTypeId = sdm120ProducerThingModbusMasterUuidParamTypeId; + connectedStateTypeId = sdm120ProducerConnectedStateTypeId; + powerStateTypeId = sdm120ProducerCurrentPowerStateTypeId; + frequencyStateTypeId = sdm120ProducerFrequencyStateTypeId; + totalEnergyProducedStateTypeId = sdm120ProducerTotalEnergyProducedStateTypeId; + } else if (classId == sdm220ThingClassId) { + slaveParamTypeId = sdm220ThingSlaveAddressParamTypeId; + masterUuidParamTypeId = sdm220ThingModbusMasterUuidParamTypeId; + connectedStateTypeId = sdm220ConnectedStateTypeId; + voltageStateTypeId = sdm220VoltagePhaseAStateTypeId; + currentStateTypeId = sdm220CurrentPhaseAStateTypeId; + powerStateTypeId = sdm220CurrentPowerStateTypeId; + frequencyStateTypeId = sdm220FrequencyStateTypeId; + totalEnergyConsumedStateTypeId = sdm220TotalEnergyConsumedStateTypeId; + totalEnergyProducedStateTypeId = sdm220TotalEnergyProducedStateTypeId; + isEnergyMeter = true; + } else if (classId == sdm220ConsumerThingClassId) { + slaveParamTypeId = sdm220ConsumerThingSlaveAddressParamTypeId; + masterUuidParamTypeId = sdm220ConsumerThingModbusMasterUuidParamTypeId; + connectedStateTypeId = sdm220ConsumerConnectedStateTypeId; + powerStateTypeId = sdm220ConsumerCurrentPowerStateTypeId; + frequencyStateTypeId = sdm220ConsumerFrequencyStateTypeId; + totalEnergyConsumedStateTypeId = sdm220ConsumerTotalEnergyConsumedStateTypeId; + } else if (classId == sdm220ProducerThingClassId) { + slaveParamTypeId = sdm220ProducerThingSlaveAddressParamTypeId; + masterUuidParamTypeId = sdm220ProducerThingModbusMasterUuidParamTypeId; + connectedStateTypeId = sdm220ProducerConnectedStateTypeId; + powerStateTypeId = sdm220ProducerCurrentPowerStateTypeId; + frequencyStateTypeId = sdm220ProducerFrequencyStateTypeId; + totalEnergyProducedStateTypeId = sdm220ProducerTotalEnergyProducedStateTypeId; + } else if (classId == sdm230ThingClassId) { + slaveParamTypeId = sdm230ThingSlaveAddressParamTypeId; + masterUuidParamTypeId = sdm230ThingModbusMasterUuidParamTypeId; + connectedStateTypeId = sdm230ConnectedStateTypeId; + voltageStateTypeId = sdm230VoltagePhaseAStateTypeId; + currentStateTypeId = sdm230CurrentPhaseAStateTypeId; + powerStateTypeId = sdm230CurrentPowerStateTypeId; + frequencyStateTypeId = sdm230FrequencyStateTypeId; + totalEnergyConsumedStateTypeId = sdm230TotalEnergyConsumedStateTypeId; + totalEnergyProducedStateTypeId = sdm230TotalEnergyProducedStateTypeId; + isEnergyMeter = true; + } else if (classId == sdm230ConsumerThingClassId) { + slaveParamTypeId = sdm230ConsumerThingSlaveAddressParamTypeId; + masterUuidParamTypeId = sdm230ConsumerThingModbusMasterUuidParamTypeId; + connectedStateTypeId = sdm230ConsumerConnectedStateTypeId; + powerStateTypeId = sdm230ConsumerCurrentPowerStateTypeId; + frequencyStateTypeId = sdm230ConsumerFrequencyStateTypeId; + totalEnergyConsumedStateTypeId = sdm230ConsumerTotalEnergyConsumedStateTypeId; + } else { // sdm230Producer + slaveParamTypeId = sdm230ProducerThingSlaveAddressParamTypeId; + masterUuidParamTypeId = sdm230ProducerThingModbusMasterUuidParamTypeId; + connectedStateTypeId = sdm230ProducerConnectedStateTypeId; + powerStateTypeId = sdm230ProducerCurrentPowerStateTypeId; + frequencyStateTypeId = sdm230ProducerFrequencyStateTypeId; + totalEnergyProducedStateTypeId = sdm230ProducerTotalEnergyProducedStateTypeId; + } + + uint address = thing->paramValue(slaveParamTypeId).toUInt(); + if (address == 0 || address > 254) { + qCWarning(dcEastron()) << "Setup failed, invalid slave address" << address; + info->finish(Thing::ThingErrorSetupFailed, + QT_TR_NOOP("The Modbus address not valid. It must be a value between 1 and 254.")); + return; + } + + QUuid uuid = thing->paramValue(masterUuidParamTypeId).toUuid(); + if (!hardwareManager()->modbusRtuResource()->hasModbusRtuMaster(uuid)) { + qCWarning(dcEastron()) << "Setup failed, Modbus RTU master not available"; + info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("The Modbus RTU interface not available.")); + return; + } + + if (m_sdm120Connections.contains(thing)) + m_sdm120Connections.take(thing)->deleteLater(); + + Sdm120ModbusRtuConnection *connection = new Sdm120ModbusRtuConnection( + hardwareManager()->modbusRtuResource()->getModbusRtuMaster(uuid), address, this); + + connect(connection, &Sdm120ModbusRtuConnection::reachableChanged, this, [=](bool reachable) { + thing->setStateValue(connectedStateTypeId, reachable); + }); + + connect(connection, &Sdm120ModbusRtuConnection::activePowerChanged, this, [=](float v) { + thing->setStateValue(powerStateTypeId, v); + }); + connect(connection, &Sdm120ModbusRtuConnection::frequencyChanged, this, [=](float v) { + thing->setStateValue(frequencyStateTypeId, v); + }); + + if (isEnergyMeter) { + connect(connection, &Sdm120ModbusRtuConnection::voltageChanged, this, [=](float v) { + thing->setStateValue(voltageStateTypeId, v); + }); + connect(connection, &Sdm120ModbusRtuConnection::currentChanged, this, [=](float v) { + thing->setStateValue(currentStateTypeId, v); + }); + connect(connection, &Sdm120ModbusRtuConnection::totalEnergyConsumedChanged, this, [=](float v) { + thing->setStateValue(totalEnergyConsumedStateTypeId, v); + }); + connect(connection, &Sdm120ModbusRtuConnection::totalEnergyProducedChanged, this, [=](float v) { + thing->setStateValue(totalEnergyProducedStateTypeId, v); + }); + } else { + // Consumer or producer: connect only the relevant energy direction + bool isProducer = (classId == sdm120ProducerThingClassId + || classId == sdm220ProducerThingClassId + || classId == sdm230ProducerThingClassId); + if (isProducer) { + connect(connection, &Sdm120ModbusRtuConnection::totalEnergyProducedChanged, this, [=](float v) { + thing->setStateValue(totalEnergyProducedStateTypeId, v); + }); + } else { + connect(connection, &Sdm120ModbusRtuConnection::totalEnergyConsumedChanged, this, [=](float v) { + thing->setStateValue(totalEnergyConsumedStateTypeId, v); + }); + } + } + + m_sdm120Connections.insert(thing, connection); + info->finish(Thing::ThingErrorNoError); +} + +void IntegrationPluginEastron::postSetupThing(Thing *thing) +{ + qCDebug(dcEastron()) << "Post setup thing" << thing->name(); + if (!m_refreshTimer) { + m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(2); + connect(m_refreshTimer, &PluginTimer::timeout, this, [this] { + foreach (Thing *thing, myThings()) { + if (m_sdm630Connections.contains(thing)) + m_sdm630Connections.value(thing)->update(); + else if (m_sdm72Connections.contains(thing)) + m_sdm72Connections.value(thing)->update(); + else if (m_sdm120Connections.contains(thing)) + m_sdm120Connections.value(thing)->update(); + } + }); + qCDebug(dcEastron()) << "Refresh timer started"; + m_refreshTimer->start(); + } +} + +void IntegrationPluginEastron::thingRemoved(Thing *thing) +{ + qCDebug(dcEastron()) << "Thing removed" << thing->name(); + + if (m_sdm630Connections.contains(thing)) + m_sdm630Connections.take(thing)->deleteLater(); + else if (m_sdm72Connections.contains(thing)) + m_sdm72Connections.take(thing)->deleteLater(); + else if (m_sdm120Connections.contains(thing)) + m_sdm120Connections.take(thing)->deleteLater(); + + if (myThings().isEmpty() && m_refreshTimer) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer); + m_refreshTimer = nullptr; + qCDebug(dcEastron()) << "Refresh timer stopped"; + } +} diff --git a/eastron/integrationplugineastron.h b/eastron/integrationplugineastron.h new file mode 100644 index 0000000..a5332b1 --- /dev/null +++ b/eastron/integrationplugineastron.h @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright (C) 2013 - 2024, nymea GmbH +* Copyright (C) 2025, ETM-Schurig SARL +* +* This file is part of nymea-plugins-modbus. +* +* nymea-plugins-modbus 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. +* +* nymea-plugins-modbus 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 nymea-plugins-modbus. If not, see . +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef INTEGRATIONPLUGINEASTRON_H +#define INTEGRATIONPLUGINEASTRON_H + +#include +#include +#include + +#include "sdm630modbusrtuconnection.h" +#include "sdm72modbusrtuconnection.h" +#include "sdm120modbusrtuconnection.h" + +#include "extern-plugininfo.h" + +#include +#include + +class IntegrationPluginEastron: public IntegrationPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationplugineastron.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginEastron(); + void init() override; + void discoverThings(ThingDiscoveryInfo *info) override; + void setupThing(ThingSetupInfo *info) override; + void postSetupThing(Thing *thing) override; + void thingRemoved(Thing *thing) override; + +private: + PluginTimer *m_refreshTimer = nullptr; + + // SDM630 group: sdm630, sdm630Consumer, sdm630Producer + QHash m_sdm630Connections; + // SDM72 group: sdm72, sdm72Consumer, sdm72Producer + QHash m_sdm72Connections; + // SDM120 group: sdm120/Consumer/Producer, sdm220/Consumer/Producer, sdm230/Consumer/Producer + QHash m_sdm120Connections; + + void discoverRtuDevices(ThingDiscoveryInfo *info, const QString &modelName, + const ParamTypeId &discoverySlaveParamTypeId, + const ParamTypeId &thingSlaveParamTypeId, + const ParamTypeId &thingMasterUuidParamTypeId); + + void setupSdm630(ThingSetupInfo *info); + void setupSdm72(ThingSetupInfo *info); + void setupSdm120(ThingSetupInfo *info); +}; + +#endif // INTEGRATIONPLUGINEASTRON_H diff --git a/eastron/integrationplugineastron.json b/eastron/integrationplugineastron.json new file mode 100644 index 0000000..2727fc5 --- /dev/null +++ b/eastron/integrationplugineastron.json @@ -0,0 +1,1411 @@ +{ + "name": "eastron", + "displayName": "Eastron", + "id": "2078c2fc-c4cd-46bb-95ab-e9a55ef0a281", + "paramTypes": [], + "vendors": [ + { + "name": "eastron", + "displayName": "Eastron", + "id": "33529c0d-67e6-441e-b0bb-6e76e9d4bc1c", + "thingClasses": [ + + { + "name": "sdm630", + "displayName": "SDM630 — Energy Meter", + "id": "0535818b-bd91-4cb8-8ec7-a870c7b12115", + "createMethods": ["discovery"], + "interfaces": ["energymeter", "connectable"], + "discoveryParamTypes": [ + { + "id": "b3ff747c-84a5-4e40-9c45-dd075e767cc3", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "d05fc16a-2fb1-4184-8cd8-2ee05427af13", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "0b1b47fb-2f05-49da-bf55-58d928f02909", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "e7cd3427-e7d9-4859-8f51-7c685e362eec", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "e3196bf6-2933-4166-8e36-518ed456c88b", + "name": "voltagePhaseA", + "displayName": "Voltage phase A", + "displayNameEvent": "Voltage phase A changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "866f9eca-5518-4e0b-8329-fcdef9ff2174", + "name": "voltagePhaseB", + "displayName": "Voltage phase B", + "displayNameEvent": "Voltage phase B changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "3c8e085b-ede9-410b-9e45-84b067616582", + "name": "voltagePhaseC", + "displayName": "Voltage phase C", + "displayNameEvent": "Voltage phase C changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "682e4ed8-a6a4-4303-ab23-6d936bc9ea9b", + "name": "currentPhaseA", + "displayName": "Current phase A", + "displayNameEvent": "Current phase A changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "5b333cb5-2e62-4615-b75c-f931ecdd7ece", + "name": "currentPhaseB", + "displayName": "Current phase B", + "displayNameEvent": "Current phase B changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "e6b7cc83-4fd4-444c-b4e4-098d2e75d06e", + "name": "currentPhaseC", + "displayName": "Current phase C", + "displayNameEvent": "Current phase C changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "a1d63410-a603-4897-b951-15677c773f9d", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "53725aef-3d6e-4c7a-a54a-759f7b15ed7e", + "name": "currentPowerPhaseA", + "displayName": "Current power phase A", + "displayNameEvent": "Current power phase A changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "f739de45-9237-40cc-93d7-283cb7dcb863", + "name": "currentPowerPhaseB", + "displayName": "Current power phase B", + "displayNameEvent": "Current power phase B changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "9629c405-36de-4168-a06b-c6bce48142eb", + "name": "currentPowerPhaseC", + "displayName": "Current power phase C", + "displayNameEvent": "Current power phase C changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "b3724c5a-deda-4186-b364-1f2d65193a80", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0 + }, + { + "id": "49f6adcd-1994-4686-8294-4afc7a4dcd86", + "name": "totalEnergyConsumed", + "displayName": "Total energy consumed", + "displayNameEvent": "Total energy consumed changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "f257effd-6c49-4604-b4c1-5689eae312bc", + "name": "totalEnergyProduced", + "displayName": "Total energy produced", + "displayNameEvent": "Total energy produced changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "df2fbaf0-7e36-4790-b614-6f3d4d990a6c", + "name": "energyConsumedPhaseA", + "displayName": "Energy consumed phase A", + "displayNameEvent": "Energy consumed phase A changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "ef1471f3-37b5-4d4d-9229-6fbb43b73ca3", + "name": "energyConsumedPhaseB", + "displayName": "Energy consumed phase B", + "displayNameEvent": "Energy consumed phase B changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "c04dab09-766d-4868-b284-6edaeb92c498", + "name": "energyConsumedPhaseC", + "displayName": "Energy consumed phase C", + "displayNameEvent": "Energy consumed phase C changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "d0eef54c-6b95-4d8f-be4b-32f7ce983d62", + "name": "energyProducedPhaseA", + "displayName": "Energy produced phase A", + "displayNameEvent": "Energy produced phase A changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "63b65768-3970-4072-b0c2-e2c143b46bef", + "name": "energyProducedPhaseB", + "displayName": "Energy produced phase B", + "displayNameEvent": "Energy produced phase B changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "86d0b64c-488c-4957-b72c-25a7577c41a9", + "name": "energyProducedPhaseC", + "displayName": "Energy produced phase C", + "displayNameEvent": "Energy produced phase C changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + } + ] + }, + + { + "name": "sdm630Consumer", + "displayName": "SDM630 — Consumer Meter", + "id": "54105387-1d86-409d-abeb-248f78a476b7", + "createMethods": ["discovery"], + "interfaces": ["smartmeterconsumer", "connectable"], + "discoveryParamTypes": [ + { + "id": "f1766468-db1c-4cc6-939d-85ff33841659", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "6c2e5662-8c92-4934-88f7-f8a06066a49c", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "58b5d865-a2ef-491d-9c9f-89436ca87011", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "f4decb05-e9de-456f-9b82-f2e86ed41aa9", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "e28e2222-3109-4074-8131-2f5c097e72b0", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "8a204eed-d8ea-49af-85dc-73cd47cd05dd", + "name": "totalEnergyConsumed", + "displayName": "Total energy consumed", + "displayNameEvent": "Total energy consumed changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "1a66d49e-3da6-4489-bcd7-fe6f384881a0", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0 + } + ] + }, + + { + "name": "sdm630Producer", + "displayName": "SDM630 — Producer Meter", + "id": "da785267-1dbe-409a-8892-bb4b3ab68728", + "createMethods": ["discovery"], + "interfaces": ["smartmeterproducer", "connectable"], + "discoveryParamTypes": [ + { + "id": "856a0582-c52d-4c37-8173-e0616b529c22", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "c7833946-1e97-4c51-9501-8ff7ca8a0493", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "be7cd84c-3694-4321-9285-853d5ba24e4e", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "935491c9-77dd-4278-a9cb-3788612eab3b", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "74feee50-3469-4365-98d7-c6f5eefe1736", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "4d4247a1-425b-4bee-9b93-1f92447e7779", + "name": "totalEnergyProduced", + "displayName": "Total energy produced", + "displayNameEvent": "Total energy produced changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "15dd9561-27ad-4a00-a7bc-860ffa17b04f", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0 + } + ] + }, + + { + "name": "sdm72", + "displayName": "SDM72 — Energy Meter", + "id": "d058ef0a-0b0d-4248-a39e-21e9397081db", + "createMethods": ["discovery"], + "interfaces": ["energymeter", "connectable"], + "discoveryParamTypes": [ + { + "id": "87b58e0c-3827-4a05-a44c-28f168c65ee3", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "78b2ab1f-19aa-4461-a284-f3b19d99cbe4", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "63405213-b6c3-4b00-ac55-8fba70edb133", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "73328327-a4f3-4214-9af8-751788730a1f", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "c02cdb3c-9069-4fce-9470-277fabc89302", + "name": "voltagePhaseA", + "displayName": "Voltage phase A", + "displayNameEvent": "Voltage phase A changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "53804251-d8fa-4237-8879-2066f31e5e15", + "name": "voltagePhaseB", + "displayName": "Voltage phase B", + "displayNameEvent": "Voltage phase B changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "1301a94a-12e2-4591-8685-4b710c054645", + "name": "voltagePhaseC", + "displayName": "Voltage phase C", + "displayNameEvent": "Voltage phase C changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "ba023b21-3704-4e9a-bad0-ce68415782c9", + "name": "currentPhaseA", + "displayName": "Current phase A", + "displayNameEvent": "Current phase A changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "3eda9880-70cc-4ace-8279-0022de13dcc5", + "name": "currentPhaseB", + "displayName": "Current phase B", + "displayNameEvent": "Current phase B changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "4baeb06f-422a-442a-9d54-b389f555eace", + "name": "currentPhaseC", + "displayName": "Current phase C", + "displayNameEvent": "Current phase C changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "a32b70cf-fd55-4204-971d-865a944c7800", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "15a1d6a7-5d46-4bc9-a83e-db85ddc0b739", + "name": "currentPowerPhaseA", + "displayName": "Current power phase A", + "displayNameEvent": "Current power phase A changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "3b63f4cf-097b-486b-8cf2-97d97ed67478", + "name": "currentPowerPhaseB", + "displayName": "Current power phase B", + "displayNameEvent": "Current power phase B changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "60b42caf-75f6-4f0a-8b33-d59c8a169f37", + "name": "currentPowerPhaseC", + "displayName": "Current power phase C", + "displayNameEvent": "Current power phase C changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "8036f567-2532-4258-8d76-ec2c82bd29f4", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0 + }, + { + "id": "c6f0c8d6-4bec-445b-843e-baf2f3908d58", + "name": "totalEnergyConsumed", + "displayName": "Total energy consumed", + "displayNameEvent": "Total energy consumed changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "482757a4-8b8b-4d41-99d9-5ced48459c63", + "name": "totalEnergyProduced", + "displayName": "Total energy produced", + "displayNameEvent": "Total energy produced changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + } + ] + }, + + { + "name": "sdm72Consumer", + "displayName": "SDM72 — Consumer Meter", + "id": "e67c2b0f-3905-454f-890a-9cce0800b703", + "createMethods": ["discovery"], + "interfaces": ["smartmeterconsumer", "connectable"], + "discoveryParamTypes": [ + { + "id": "7956c051-4e01-4123-abc6-23d7bf1ed05f", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "d7795c92-d00e-49bc-9005-49e1bc520e25", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "4cab7e0c-37bd-44cc-a866-219172535323", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "166471bb-136c-467a-a689-39912be1b7ce", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "3f8dcfbf-9c35-40ed-928f-c8d0859e47f6", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "aa358341-9409-4a99-ab80-4a7196d446c3", + "name": "totalEnergyConsumed", + "displayName": "Total energy consumed", + "displayNameEvent": "Total energy consumed changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "981e0554-9411-4dd0-8af9-56a54e8a4104", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0 + } + ] + }, + + { + "name": "sdm72Producer", + "displayName": "SDM72 — Producer Meter", + "id": "4fe3a9cc-3d37-45eb-989a-63f84fab119e", + "createMethods": ["discovery"], + "interfaces": ["smartmeterproducer", "connectable"], + "discoveryParamTypes": [ + { + "id": "1f900a6d-b835-45e6-b236-67362b697e9b", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "8a60ffcd-32d9-4b2d-a525-f3cd22e126bd", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "026481d9-d2de-4082-b5ea-73905afa911e", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "406d6b71-b7d8-481e-93f5-535bbe5dab35", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "a1446da3-3f2d-41a4-a0a8-ebf96bd27b67", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "6a1b0f7c-4829-48dc-85a5-d8bd640bda33", + "name": "totalEnergyProduced", + "displayName": "Total energy produced", + "displayNameEvent": "Total energy produced changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "bacb8a91-7881-4b41-97ae-dc960c420e4c", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0 + } + ] + }, + + { + "name": "sdm120", + "displayName": "SDM120 — Energy Meter", + "id": "46cfdfab-6e93-4568-9ff0-5760909a2a4c", + "createMethods": ["discovery"], + "interfaces": ["energymeter", "connectable"], + "discoveryParamTypes": [ + { + "id": "bf10687e-da5e-4c00-932c-f07b069734f7", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "907e6c11-d334-4a56-a87b-ec3e04ea4904", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "f5c93fbf-325f-4a5e-955d-9b7883c71032", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "956de306-01bb-4f1a-847c-d15e617335a6", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "35f88f7f-a8a0-4090-968c-832905aed291", + "name": "voltagePhaseA", + "displayName": "Voltage", + "displayNameEvent": "Voltage changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "094d3441-1718-4170-a9bd-bb157d554afc", + "name": "currentPhaseA", + "displayName": "Current", + "displayNameEvent": "Current changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "10ea7d4e-e1a4-4999-aca2-b0183bc36fe4", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "4965f4b1-8351-4932-9c3c-776a6e32091b", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0 + }, + { + "id": "03e259b6-ecca-4ee1-bca6-8f34accd0d28", + "name": "totalEnergyConsumed", + "displayName": "Total energy consumed", + "displayNameEvent": "Total energy consumed changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "f5348729-8146-4fc1-8d79-fd797e0b4951", + "name": "totalEnergyProduced", + "displayName": "Total energy produced", + "displayNameEvent": "Total energy produced changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + } + ] + }, + + { + "name": "sdm120Consumer", + "displayName": "SDM120 — Consumer Meter", + "id": "e519e207-c6af-4479-af02-af36d6dd290e", + "createMethods": ["discovery"], + "interfaces": ["smartmeterconsumer", "connectable"], + "discoveryParamTypes": [ + { + "id": "bc0d2184-0d26-4e61-aded-7eecad2ddf11", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "37555aec-b740-4a17-8b49-4c0538330b86", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "7bbd3600-1e72-4dcb-a597-3415d32ea2db", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "112240d5-f6fc-4d67-874e-537bda2fe65c", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "8fc409b7-6e78-4ff5-8910-9b5ad0f59bf0", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "03c78adf-2d57-40a6-835b-492aab9bb021", + "name": "totalEnergyConsumed", + "displayName": "Total energy consumed", + "displayNameEvent": "Total energy consumed changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "f9b356aa-2468-430b-864d-01ac943dfdc9", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0 + } + ] + }, + + { + "name": "sdm120Producer", + "displayName": "SDM120 — Producer Meter", + "id": "162b883b-3010-478d-a373-ee9446a19d15", + "createMethods": ["discovery"], + "interfaces": ["smartmeterproducer", "connectable"], + "discoveryParamTypes": [ + { + "id": "28b1ec59-2f1c-4ee9-afd7-60f8b2540b71", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "a93dd24c-50b5-4a74-8929-f54029778dab", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "c358a56b-c7ed-4cf8-b74a-b2c95d2ec978", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "2b00f43c-ad66-4ae0-9981-1a43dfd68fde", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "5c801cb8-b891-45d9-978e-be8ae3a9a6ca", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "04d99792-1c4d-48fb-8ad6-370689ce387c", + "name": "totalEnergyProduced", + "displayName": "Total energy produced", + "displayNameEvent": "Total energy produced changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "c26381c5-0523-4d13-b818-e4e2144e629a", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0 + } + ] + }, + + { + "name": "sdm220", + "displayName": "SDM220 — Energy Meter", + "id": "3cf3b42b-cc14-4f5b-a291-f8682d7f5c9a", + "createMethods": ["discovery"], + "interfaces": ["energymeter", "connectable"], + "discoveryParamTypes": [ + { + "id": "56293d68-2e00-4def-bef0-d6b0f7289144", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "cc6aa752-86da-4155-8430-9eb79fd10d09", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "16cb45f5-7dde-4602-90cd-bbe2d9d58a3c", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "6ff17c3f-9d56-4146-a985-16676732d78b", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "eb047b88-6869-4d32-b9c3-a3654d3ca64b", + "name": "voltagePhaseA", + "displayName": "Voltage", + "displayNameEvent": "Voltage changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "f47d9db8-23b3-4b51-bfde-3ff34c1dedf9", + "name": "currentPhaseA", + "displayName": "Current", + "displayNameEvent": "Current changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "0c057463-d4a7-4deb-af18-ff7f90a11262", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "bc82a8a3-2b04-417c-afac-e3f5494d9985", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0 + }, + { + "id": "c9716b87-8e1d-4cd0-a526-dd1c220cce1b", + "name": "totalEnergyConsumed", + "displayName": "Total energy consumed", + "displayNameEvent": "Total energy consumed changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "7f6b35c5-a244-475f-838d-c77aac7b76a1", + "name": "totalEnergyProduced", + "displayName": "Total energy produced", + "displayNameEvent": "Total energy produced changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + } + ] + }, + + { + "name": "sdm220Consumer", + "displayName": "SDM220 — Consumer Meter", + "id": "252578a4-513b-46de-b10f-0bd6efb891aa", + "createMethods": ["discovery"], + "interfaces": ["smartmeterconsumer", "connectable"], + "discoveryParamTypes": [ + { + "id": "089bd1af-5196-4d7f-9682-16aa2d988f66", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "7919c29b-22ae-49f0-8c4e-81546e7af2e6", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "5066a96f-54c4-4165-85dc-b8cae4e6bfb6", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "6574af2b-7bdc-42a9-90f8-9c6381c692fc", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "4ec350aa-763b-4500-909b-53bd81e0f3cc", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "3de4a1f3-0c5f-454a-8c42-a4eb674fd1ca", + "name": "totalEnergyConsumed", + "displayName": "Total energy consumed", + "displayNameEvent": "Total energy consumed changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "d610133b-6a5f-4011-84fc-9cdb438312b9", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0 + } + ] + }, + + { + "name": "sdm220Producer", + "displayName": "SDM220 — Producer Meter", + "id": "2885a917-c281-4a03-bf98-e5d596eb0288", + "createMethods": ["discovery"], + "interfaces": ["smartmeterproducer", "connectable"], + "discoveryParamTypes": [ + { + "id": "dc485bee-2e90-4743-9b73-fbb8b79e1030", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "b7343b02-f937-4fda-8aec-539c989b99c5", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "27c7c43a-087f-49c8-bcfa-0a0988f3fe0f", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "4af34e28-c44a-44da-ba1b-a93b7956a2cc", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "f99063ae-731b-471f-99ba-aa4ca7945a03", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "e5a06165-2bbb-4b6f-8f5f-8e13f6756501", + "name": "totalEnergyProduced", + "displayName": "Total energy produced", + "displayNameEvent": "Total energy produced changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "1bef1573-8d0f-4ba8-82c9-afdf1437eed4", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0 + } + ] + }, + + { + "name": "sdm230", + "displayName": "SDM230 — Energy Meter", + "id": "078d9b32-4004-4ad9-a758-aef99c136858", + "createMethods": ["discovery"], + "interfaces": ["energymeter", "connectable"], + "discoveryParamTypes": [ + { + "id": "39b6cfe3-4ff0-4be1-8d68-f5c34c6c721b", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "56e3270f-5760-46ce-9e15-c5107b287af3", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "768872db-3f33-4d81-abc6-d1fa6d9e2c43", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "2fc9bc20-6f0a-46b3-b1d7-c1837f8a2728", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "3dcdf851-4d47-472e-871b-f69083b10d62", + "name": "voltagePhaseA", + "displayName": "Voltage", + "displayNameEvent": "Voltage changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "6890caf9-6d16-439d-8848-cefb32a16529", + "name": "currentPhaseA", + "displayName": "Current", + "displayNameEvent": "Current changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "2fb590d5-3b12-4b36-8566-5f9b9b42a669", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "4344b618-51fe-4c04-9e88-0b133c7e4a95", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0 + }, + { + "id": "41ef34e3-904e-4282-a203-7d3def3b592b", + "name": "totalEnergyConsumed", + "displayName": "Total energy consumed", + "displayNameEvent": "Total energy consumed changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "e382146d-1734-482a-9fea-ad6158da1268", + "name": "totalEnergyProduced", + "displayName": "Total energy produced", + "displayNameEvent": "Total energy produced changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + } + ] + }, + + { + "name": "sdm230Consumer", + "displayName": "SDM230 — Consumer Meter", + "id": "f98df834-6428-4173-a7d1-b91681274bd1", + "createMethods": ["discovery"], + "interfaces": ["smartmeterconsumer", "connectable"], + "discoveryParamTypes": [ + { + "id": "ae22d4de-d3f0-4a77-a0bd-8f7070f878fe", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "2fe5a67e-3d9d-4c22-b091-7249df0a1d3a", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "d0c76f54-2439-4d05-b5bf-14a27f28737b", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "d5016371-ef67-4242-8c74-a69e3e985630", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "9ab1167c-f890-47ee-aad2-ab4cae775ae2", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "fdfe8ef0-1f29-4cdd-88d2-165c4adae795", + "name": "totalEnergyConsumed", + "displayName": "Total energy consumed", + "displayNameEvent": "Total energy consumed changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "09877c12-ae5b-413e-b37b-ef104e76b66a", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0 + } + ] + }, + + { + "name": "sdm230Producer", + "displayName": "SDM230 — Producer Meter", + "id": "3d0c210b-2da9-4dcd-ae3f-aa4318262db3", + "createMethods": ["discovery"], + "interfaces": ["smartmeterproducer", "connectable"], + "discoveryParamTypes": [ + { + "id": "ed1d95cf-0e9f-4529-b514-80b3a57529ac", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "f35d05e5-f968-4a33-87c6-6ce5840f158a", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "402ed4c9-4560-4a74-9c6f-a0b29ea23a32", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "a964b0df-d4d2-4242-903b-ba654c800591", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "ae771224-30b7-478b-8b14-d12e0c2d1f70", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "a1f48d01-db15-4883-9a62-9520199771a8", + "name": "totalEnergyProduced", + "displayName": "Total energy produced", + "displayNameEvent": "Total energy produced changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "d7a9a378-a313-48c5-9360-7ffc8c0a0172", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0 + } + ] + } + + ] + } + ] +} diff --git a/eastron/meta.json b/eastron/meta.json new file mode 100644 index 0000000..1b4af0c --- /dev/null +++ b/eastron/meta.json @@ -0,0 +1,13 @@ +{ + "title": "Eastron", + "tagline": "Connect Eastron SDM energy meters via Modbus RTU.", + "icon": "eastron.jpg", + "stability": "consumer", + "offline": true, + "technologies": [ + "modbus" + ], + "categories": [ + "energy" + ] +} diff --git a/eastron/sdm120-registers.json b/eastron/sdm120-registers.json new file mode 100644 index 0000000..3464f86 --- /dev/null +++ b/eastron/sdm120-registers.json @@ -0,0 +1,89 @@ +{ + "className": "Sdm120", + "protocol": "RTU", + "endianness": "BigEndian", + "errorLimitUntilNotReachable": 15, + "checkReachableRegister": "activePower", + "registers": [ + { + "id": "voltage", + "address": 0, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Voltage", + "unit": "V", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "current", + "address": 6, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Current", + "unit": "A", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "activePower", + "address": 12, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Active power", + "unit": "W", + "defaultValue": "0", + "access": "RO" + } + ], + "blocks": [ + { + "id": "frequencyAndTotalEnergy", + "readSchedule": "update", + "registers": [ + { + "id": "frequency", + "address": 70, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Frequency", + "unit": "Hz", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "totalEnergyConsumed", + "address": 72, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Total import active energy", + "unit": "kWh", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "totalEnergyProduced", + "address": 74, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Total export active energy", + "unit": "kWh", + "defaultValue": "0", + "access": "RO" + } + ] + } + ] +} diff --git a/eastron/sdm630-registers.json b/eastron/sdm630-registers.json new file mode 100644 index 0000000..10f6656 --- /dev/null +++ b/eastron/sdm630-registers.json @@ -0,0 +1,263 @@ +{ + "className": "Sdm630", + "protocol": "RTU", + "endianness": "BigEndian", + "errorLimitUntilNotReachable": 15, + "checkReachableRegister": "totalCurrentPower", + "registers": [ + { + "id": "totalCurrentPower", + "address": 52, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Total system power", + "unit": "W", + "defaultValue": "0", + "access": "RO" + } + ], + "blocks": [ + { + "id": "phaseVoltageAndCurrent", + "readSchedule": "update", + "registers": [ + { + "id": "voltagePhaseA", + "address": 0, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Voltage phase L1", + "unit": "V", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "voltagePhaseB", + "address": 2, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Voltage phase L2", + "unit": "V", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "voltagePhaseC", + "address": 4, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Voltage phase L3", + "unit": "V", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "currentPhaseA", + "address": 6, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Current phase L1", + "unit": "A", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "currentPhaseB", + "address": 8, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Current phase L2", + "unit": "A", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "currentPhaseC", + "address": 10, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Current phase L3", + "unit": "A", + "defaultValue": "0", + "access": "RO" + } + ] + }, + { + "id": "phasePower", + "readSchedule": "update", + "registers": [ + { + "id": "powerPhaseA", + "address": 12, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Power phase L1", + "unit": "W", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "powerPhaseB", + "address": 14, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Power phase L2", + "unit": "W", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "powerPhaseC", + "address": 16, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Power phase L3", + "unit": "W", + "defaultValue": "0", + "access": "RO" + } + ] + }, + { + "id": "frequencyAndTotalEnergy", + "readSchedule": "update", + "registers": [ + { + "id": "frequency", + "address": 70, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Frequency", + "unit": "Hz", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "totalEnergyConsumed", + "address": 72, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Total import active energy", + "unit": "kWh", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "totalEnergyProduced", + "address": 74, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Total export active energy", + "unit": "kWh", + "defaultValue": "0", + "access": "RO" + } + ] + }, + { + "id": "phaseEnergy", + "readSchedule": "update", + "registers": [ + { + "id": "energyProducedPhaseA", + "address": 346, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Export active energy phase L1", + "unit": "kWh", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "energyProducedPhaseB", + "address": 348, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Export active energy phase L2", + "unit": "kWh", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "energyProducedPhaseC", + "address": 350, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Export active energy phase L3", + "unit": "kWh", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "energyConsumedPhaseA", + "address": 352, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Import active energy phase L1", + "unit": "kWh", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "energyConsumedPhaseB", + "address": 354, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Import active energy phase L2", + "unit": "kWh", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "energyConsumedPhaseC", + "address": 356, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Import active energy phase L3", + "unit": "kWh", + "defaultValue": "0", + "access": "RO" + } + ] + } + ] +} diff --git a/eastron/sdm72-registers.json b/eastron/sdm72-registers.json new file mode 100644 index 0000000..346a709 --- /dev/null +++ b/eastron/sdm72-registers.json @@ -0,0 +1,185 @@ +{ + "className": "Sdm72", + "protocol": "RTU", + "endianness": "BigEndian", + "errorLimitUntilNotReachable": 15, + "checkReachableRegister": "totalCurrentPower", + "registers": [ + { + "id": "totalCurrentPower", + "address": 52, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Total system power", + "unit": "W", + "defaultValue": "0", + "access": "RO" + } + ], + "blocks": [ + { + "id": "phaseVoltageAndCurrent", + "readSchedule": "update", + "registers": [ + { + "id": "voltagePhaseA", + "address": 0, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Voltage phase L1", + "unit": "V", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "voltagePhaseB", + "address": 2, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Voltage phase L2", + "unit": "V", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "voltagePhaseC", + "address": 4, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Voltage phase L3", + "unit": "V", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "currentPhaseA", + "address": 6, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Current phase L1", + "unit": "A", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "currentPhaseB", + "address": 8, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Current phase L2", + "unit": "A", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "currentPhaseC", + "address": 10, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Current phase L3", + "unit": "A", + "defaultValue": "0", + "access": "RO" + } + ] + }, + { + "id": "phasePower", + "readSchedule": "update", + "registers": [ + { + "id": "powerPhaseA", + "address": 12, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Power phase L1", + "unit": "W", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "powerPhaseB", + "address": 14, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Power phase L2", + "unit": "W", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "powerPhaseC", + "address": 16, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Power phase L3", + "unit": "W", + "defaultValue": "0", + "access": "RO" + } + ] + }, + { + "id": "frequencyAndTotalEnergy", + "readSchedule": "update", + "registers": [ + { + "id": "frequency", + "address": 70, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Frequency", + "unit": "Hz", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "totalEnergyConsumed", + "address": 72, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Total import active energy", + "unit": "kWh", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "totalEnergyProduced", + "address": 74, + "size": 2, + "type": "float", + "registerType": "inputRegister", + "readSchedule": "update", + "description": "Total export active energy", + "unit": "kWh", + "defaultValue": "0", + "access": "RO" + } + ] + } + ] +} diff --git a/modbus.pri b/modbus.pri new file mode 100644 index 0000000..840bcb4 --- /dev/null +++ b/modbus.pri @@ -0,0 +1,12 @@ +QT += network serialport serialbus + +top_srcdir=$$PWD +top_builddir=$$shadowed($$PWD) + +INCLUDEPATH += $$top_srcdir/libnymea-modbus +LIBS += -L$$top_builddir/libnymea-modbus/ -lnymea-modbus + +OTHER_FILES += $${MODBUS_CONNECTIONS} + +include(libnymea-modbus/modbus-tool.pri) + diff --git a/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro new file mode 100644 index 0000000..23b985e --- /dev/null +++ b/nymea-plugins-modbus.pro @@ -0,0 +1,70 @@ +TEMPLATE = subdirs + +# Note: In the loop at the end of this file the plugin +# dependency on the libs will be defined +SUBDIRS += nymea-modbus-cli libnymea-modbus libnymea-sunspec + +PLUGIN_DIRS = \ + eastron-all-models \ + waveshare-relay-d8 \ + + +message(============================================) +message("Qt version:" $$[QT_VERSION]) + +!greaterThan(QT_MAJOR_VERSION, 5) { + # We disable unipi for + PLUGIN_DIRS += \ + unipi +} + +gcc { + COMPILER_VERSION = $$system($$QMAKE_CXX " -dumpversion") + COMPILER_MAJOR_VERSION = $$str_member($$COMPILER_VERSION) + greaterThan(COMPILER_MAJOR_VERSION, 7): QMAKE_CXXFLAGS += -Wno-deprecated-copy +} + +plugininfo.depends = FORCE +for (entry, PLUGIN_DIRS):plugininfo.commands += test -d $${entry} || mkdir -p $${entry}; cd $${entry} && qmake -o Makefile $$PWD/$${entry}/$${entry}.pro && cd ..; +for (entry, PLUGIN_DIRS):plugininfo.commands += make -C $${entry} plugininfo.h; +QMAKE_EXTRA_TARGETS += plugininfo + +# Translations: +# make lupdate to update .ts files +lupdate.depends = FORCE plugininfo +for (entry, PLUGIN_DIRS):lupdate.commands += make -C $${entry} lupdate; +QMAKE_EXTRA_TARGETS += lupdate + +# make lrelease to build .qm from .ts +lrelease.depends = FORCE +for (entry, PLUGIN_DIRS):lrelease.commands += lrelease $$files($$PWD/$${entry}/translations/*.ts, true); +for (entry, PLUGIN_DIRS):lrelease.commands += rsync -a $$PWD/$${entry}/translations/*.qm $$OUT_PWD/translations/; +QMAKE_EXTRA_TARGETS += lrelease + +# For Qt-Creator's code model: Add CPATH to INCLUDEPATH explicitly +INCLUDEPATH += $$(CPATH) + +message("Usage: qmake [srcdir] [WITH_PLUGINS=\"...\"] [WITHOUT_PLUGINS=\"...\"]") + +isEmpty(WITH_PLUGINS) { + PLUGINS = $${PLUGIN_DIRS} +} else { + PLUGINS = $${WITH_PLUGINS} +} +PLUGINS-=$${WITHOUT_PLUGINS} + +message("Building plugins:") +for(plugin, PLUGINS) { + exists($${plugin}) { + SUBDIRS*= $${plugin} + message("- $${plugin}") + # Make sure the libs will be built before the plugins + equals(plugin, "sunspec") { + $${plugin}.depends += libnymea-sunspec + } else { + $${plugin}.depends += libnymea-modbus + } + } else { + error("Invalid plugin \"$${plugin}\".") + } +} diff --git a/plugins.pri b/plugins.pri new file mode 100644 index 0000000..f180f53 --- /dev/null +++ b/plugins.pri @@ -0,0 +1,17 @@ +isEmpty(PLUGIN_PRI) { + exists($$[QT_INSTALL_PREFIX]/include/nymea/plugin.pri) { + include($$[QT_INSTALL_PREFIX]/include/nymea/plugin.pri) + } else { + message("plugin.pri not found. Either install libnymea1-dev or use the PLUGIN_PRI argument to point to it.") + message("For building this project without nymea installed system-wide, you will want to export those variables in addition:") + message("PKG_CONFIG_PATH=/path/to/build-nymea/libnymea/pkgconfig/") + message("CPATH=/path/to/nymea/libnymea/") + message("LIBRARY_PATH=/path/to/build-nymea/libnymea/") + message("PATH=/path/to/build-nymea/tools/nymea-plugininfocompiler:$PATH") + message("LD_LIBRARY_PATH=/path/to/build-nymea/libnymea/") + error("plugin.pri not found. Cannot continue") + } +} else { +# message("Using $$PLUGIN_PRI") + include($$PLUGIN_PRI) +} diff --git a/sunspec.pri b/sunspec.pri new file mode 100644 index 0000000..d39682d --- /dev/null +++ b/sunspec.pri @@ -0,0 +1,7 @@ +QT *= network serialbus + +top_srcdir=$$PWD +top_builddir=$$shadowed($$PWD) + +INCLUDEPATH += $$top_srcdir/libnymea-sunspec +LIBS += -L$$top_builddir/libnymea-sunspec/ -lnymea-sunspec1 diff --git a/waveshare-relay-d8/integrationpluginwaveshare-relay-d8.json b/waveshare-relay-d8/integrationpluginwaveshare-relay-d8.json new file mode 100644 index 0000000..95d506e --- /dev/null +++ b/waveshare-relay-d8/integrationpluginwaveshare-relay-d8.json @@ -0,0 +1,223 @@ +{ + "name": "WaveshareRelayD8", + "displayName": "Waveshare Relay D8", + "id": "028e7851-a6cc-4480-b174-b94ca638cf56", + "vendors": [ + { + "name": "waveshare", + "displayName": "Waveshare", + "id": "1b021659-70ae-4dbf-b837-f96db1ede396", + "thingClasses": [ + { + "name": "waveshareRelayD8", + "displayName": "Waveshare Relay D8", + "id": "8e8e5643-7dcf-4438-af9b-3d003e0fa0f5", + "createMethods": ["discovery"], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "b4a727f1-ca3d-4674-864f-f625debf57e4", + "name": "rtuMaster", + "displayName": "Modbus RTU master UUID", + "type": "QString", + "defaultValue": "" + }, + { + "id": "ab68c9cd-4785-4b12-8752-b50e9fc60520", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "int", + "defaultValue": 1, + "minValue": 1, + "maxValue": 247 + } + ], + "stateTypes": [ + { + "id": "a43070ac-9784-4923-bc7f-16c206b822e1", + "name": "connected", + "displayName": "Connected", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "29fcfe96-989d-494d-9717-ce08f992e281", + "name": "relay1State", + "displayName": "Relay 1", + "type": "bool", + "defaultValue": false + }, + { + "id": "ba8e4946-6402-459b-bb92-e72b74294627", + "name": "relay2State", + "displayName": "Relay 2", + "type": "bool", + "defaultValue": false + }, + { + "id": "00904d40-eb47-4165-8776-ee6452bd2cb5", + "name": "relay3State", + "displayName": "Relay 3", + "type": "bool", + "defaultValue": false + }, + { + "id": "111db305-23f5-49e2-8999-ae29cacb13cf", + "name": "relay4State", + "displayName": "Relay 4", + "type": "bool", + "defaultValue": false + }, + { + "id": "83a781a8-c4be-406f-9da3-8c64185a945d", + "name": "relay5State", + "displayName": "Relay 5", + "type": "bool", + "defaultValue": false + }, + { + "id": "526a35bf-9529-40f9-bd50-9865ba7e9776", + "name": "relay6State", + "displayName": "Relay 6", + "type": "bool", + "defaultValue": false + }, + { + "id": "0624aea8-805c-4504-8781-2e64f5d88cfa", + "name": "relay7State", + "displayName": "Relay 7", + "type": "bool", + "defaultValue": false + }, + { + "id": "01c3d958-0de1-4c43-a30c-799fc74244ac", + "name": "relay8State", + "displayName": "Relay 8", + "type": "bool", + "defaultValue": false + }, + { + "id": "d380da7f-00c9-4458-ba4c-ed0d10060a24", + "name": "input1State", + "displayName": "Input 1", + "type": "bool", + "defaultValue": false + }, + { + "id": "1e990bb6-912a-4805-b03a-e41d95ee2494", + "name": "input2State", + "displayName": "Input 2", + "type": "bool", + "defaultValue": false + }, + { + "id": "bb460c57-2690-4ddd-8d0c-efcedc74cc53", + "name": "input3State", + "displayName": "Input 3", + "type": "bool", + "defaultValue": false + }, + { + "id": "0285f664-a46b-478f-b27d-4fd2b171883e", + "name": "input4State", + "displayName": "Input 4", + "type": "bool", + "defaultValue": false + }, + { + "id": "d09da37c-ccb9-4583-a42a-1d5b4e074751", + "name": "input5State", + "displayName": "Input 5", + "type": "bool", + "defaultValue": false + }, + { + "id": "45aca74d-12ed-4fce-a332-22535d91cf87", + "name": "input6State", + "displayName": "Input 6", + "type": "bool", + "defaultValue": false + }, + { + "id": "ef593976-820b-4eb2-9156-8c14e46e9dfe", + "name": "input7State", + "displayName": "Input 7", + "type": "bool", + "defaultValue": false + }, + { + "id": "6c46fb18-2f34-49c0-b22c-0eb561a9c7b0", + "name": "input8State", + "displayName": "Input 8", + "type": "bool", + "defaultValue": false + } + ], + "actionTypes": [ + { + "id": "19526ec5-991d-4b6f-855a-d2b61da05caa", + "name": "setRelay", + "displayName": "Set relay", + "paramTypes": [ + { + "id": "f7b2830a-182c-4de5-b689-9ff599add6ac", + "name": "channel", + "displayName": "Channel (1-8)", + "type": "int", + "defaultValue": 1, + "minValue": 1, + "maxValue": 8 + }, + { + "id": "dee4196d-586a-4735-8c3e-12e01f849787", + "name": "state", + "displayName": "State", + "type": "bool", + "defaultValue": false + } + ] + }, + { + "id": "0c0e9c8f-bf97-4f34-bd31-3252aade9168", + "name": "setAllRelays", + "displayName": "Set all relays", + "paramTypes": [ + { + "id": "791becf8-78aa-4c84-96df-2689d87970c8", + "name": "state", + "displayName": "State", + "type": "bool", + "defaultValue": false + } + ] + } + ], + "eventTypes": [ + { + "id": "63212751-793d-4147-89d0-47d71669c672", + "name": "inputChanged", + "displayName": "Input changed", + "paramTypes": [ + { + "id": "42779413-d416-4211-a1fb-76bcbc90a5cd", + "name": "channel", + "displayName": "Channel (1-8)", + "type": "int", + "defaultValue": 1 + }, + { + "id": "d3f66c2f-ed95-46ae-be06-ce151c1793d3", + "name": "state", + "displayName": "State", + "type": "bool", + "defaultValue": false + } + ] + } + ] + } + ] + } + ] +} diff --git a/waveshare-relay-d8/integrationpluginwavesharerelayed8.cpp b/waveshare-relay-d8/integrationpluginwavesharerelayed8.cpp new file mode 100644 index 0000000..d57a77c --- /dev/null +++ b/waveshare-relay-d8/integrationpluginwavesharerelayed8.cpp @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright (C) 2025, ETM-Schurig SARL +* +* This file is part of nymea-plugins-modbus. +* +* nymea-plugins-modbus 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. +* +* nymea-plugins-modbus 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 nymea-plugins-modbus. If not, see . +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "integrationpluginwavesharerelayed8.h" +#include "plugininfo.h" +#include "hardwaremanager.h" +#include "hardware/modbus/modbusrtuhardwareresource.h" + +IntegrationPluginWaveshareRelayD8::IntegrationPluginWaveshareRelayD8() +{ +} + +void IntegrationPluginWaveshareRelayD8::discoverThings(ThingDiscoveryInfo *info) +{ + foreach (ModbusRtuMaster *modbusMaster, hardwareManager()->modbusRtuResource()->modbusRtuMasters()) { + qCDebug(dcWaveshareRelayD8()) << "Found RTU master resource" << modbusMaster; + if (modbusMaster->connected() + && modbusMaster->baudrate() == 9600 + && modbusMaster->dataBits() == QSerialPort::Data8 + && modbusMaster->stopBits() == QSerialPort::OneStop + && modbusMaster->parity() == QSerialPort::NoParity) { + ParamList parameters; + ThingDescriptor thingDescriptor(waveshareRelayD8ThingClassId, + "Waveshare Relay D8", + modbusMaster->serialPort()); + parameters.append(Param(waveshareRelayD8ThingRtuMasterParamTypeId, + modbusMaster->modbusUuid().toString())); + thingDescriptor.setParams(parameters); + info->addThingDescriptor(thingDescriptor); + } else { + qCDebug(dcWaveshareRelayD8()) << "Skipping RTU master (not connected or wrong settings):" << modbusMaster; + } + } + + QString displayMessage; + if (info->thingDescriptors().isEmpty()) { + displayMessage = QT_TR_NOOP("Please configure a Modbus RTU master with 9600 baud, 8 data bits, 1 stop bit and no parity."); + } + + info->finish(Thing::ThingErrorNoError, displayMessage); +} + +void IntegrationPluginWaveshareRelayD8::setupThing(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + qCDebug(dcWaveshareRelayD8()) << "Setup" << thing << thing->params(); + + if (m_connections.contains(thing)) { + qCDebug(dcWaveshareRelayD8()) << "Reconfiguring existing thing" << thing->name(); + m_connections.take(thing)->deleteLater(); + } + + QUuid rtuMasterUuid = thing->paramValue(waveshareRelayD8ThingRtuMasterParamTypeId).toUuid(); + ModbusRtuMaster *master = hardwareManager()->modbusRtuResource()->getModbusRtuMaster(rtuMasterUuid); + if (!master) { + info->finish(Thing::ThingErrorHardwareNotAvailable, + QT_TR_NOOP("The Modbus RTU master is not available.")); + return; + } + + quint16 slaveAddress = static_cast( + thing->paramValue(waveshareRelayD8ThingSlaveAddressParamTypeId).toUInt()); + + WaveshareRelayD8ModbusRtuConnection *connection = + new WaveshareRelayD8ModbusRtuConnection(master, slaveAddress, this); + connect(info, &ThingSetupInfo::aborted, connection, + &WaveshareRelayD8ModbusRtuConnection::deleteLater); + + // Reachability + connect(connection, &WaveshareRelayD8ModbusRtuConnection::reachableChanged, + thing, [connection, thing](bool reachable) { + qCDebug(dcWaveshareRelayD8()) << thing->name() << "reachable changed:" << reachable; + if (reachable) { + connection->initialize(); + } else { + thing->setStateValue(waveshareRelayD8ConnectedStateTypeId, false); + } + }); + + // Initialization + connect(connection, &WaveshareRelayD8ModbusRtuConnection::initializationFinished, + info, [=](bool success) { + qCDebug(dcWaveshareRelayD8()) << "Initialization finished:" << success; + if (success) { + m_connections.insert(thing, connection); + info->finish(Thing::ThingErrorNoError); + } else { + delete connection; + info->finish(Thing::ThingErrorHardwareNotAvailable); + } + }); + + connect(connection, &WaveshareRelayD8ModbusRtuConnection::initializationFinished, + thing, [=](bool success) { + if (success) { + thing->setStateValue(waveshareRelayD8ConnectedStateTypeId, true); + } + }); + + // Relay state changes (bulk read FC01 → individual signals) + connect(connection, &WaveshareRelayD8ModbusRtuConnection::relay1Changed, + thing, [thing](quint16 value) { + thing->setStateValue(waveshareRelayD8Relay1StateStateTypeId, value != 0); + }); + connect(connection, &WaveshareRelayD8ModbusRtuConnection::relay2Changed, + thing, [thing](quint16 value) { + thing->setStateValue(waveshareRelayD8Relay2StateStateTypeId, value != 0); + }); + connect(connection, &WaveshareRelayD8ModbusRtuConnection::relay3Changed, + thing, [thing](quint16 value) { + thing->setStateValue(waveshareRelayD8Relay3StateStateTypeId, value != 0); + }); + connect(connection, &WaveshareRelayD8ModbusRtuConnection::relay4Changed, + thing, [thing](quint16 value) { + thing->setStateValue(waveshareRelayD8Relay4StateStateTypeId, value != 0); + }); + connect(connection, &WaveshareRelayD8ModbusRtuConnection::relay5Changed, + thing, [thing](quint16 value) { + thing->setStateValue(waveshareRelayD8Relay5StateStateTypeId, value != 0); + }); + connect(connection, &WaveshareRelayD8ModbusRtuConnection::relay6Changed, + thing, [thing](quint16 value) { + thing->setStateValue(waveshareRelayD8Relay6StateStateTypeId, value != 0); + }); + connect(connection, &WaveshareRelayD8ModbusRtuConnection::relay7Changed, + thing, [thing](quint16 value) { + thing->setStateValue(waveshareRelayD8Relay7StateStateTypeId, value != 0); + }); + connect(connection, &WaveshareRelayD8ModbusRtuConnection::relay8Changed, + thing, [thing](quint16 value) { + thing->setStateValue(waveshareRelayD8Relay8StateStateTypeId, value != 0); + }); + + // Opto input state changes (bulk read FC02 → individual signals + event) + connect(connection, &WaveshareRelayD8ModbusRtuConnection::input1Changed, + thing, [this, thing](quint16 value) { + bool state = value != 0; + thing->setStateValue(waveshareRelayD8Input1StateStateTypeId, state); + emitInputChangedEvent(thing, 1, state); + }); + connect(connection, &WaveshareRelayD8ModbusRtuConnection::input2Changed, + thing, [this, thing](quint16 value) { + bool state = value != 0; + thing->setStateValue(waveshareRelayD8Input2StateStateTypeId, state); + emitInputChangedEvent(thing, 2, state); + }); + connect(connection, &WaveshareRelayD8ModbusRtuConnection::input3Changed, + thing, [this, thing](quint16 value) { + bool state = value != 0; + thing->setStateValue(waveshareRelayD8Input3StateStateTypeId, state); + emitInputChangedEvent(thing, 3, state); + }); + connect(connection, &WaveshareRelayD8ModbusRtuConnection::input4Changed, + thing, [this, thing](quint16 value) { + bool state = value != 0; + thing->setStateValue(waveshareRelayD8Input4StateStateTypeId, state); + emitInputChangedEvent(thing, 4, state); + }); + connect(connection, &WaveshareRelayD8ModbusRtuConnection::input5Changed, + thing, [this, thing](quint16 value) { + bool state = value != 0; + thing->setStateValue(waveshareRelayD8Input5StateStateTypeId, state); + emitInputChangedEvent(thing, 5, state); + }); + connect(connection, &WaveshareRelayD8ModbusRtuConnection::input6Changed, + thing, [this, thing](quint16 value) { + bool state = value != 0; + thing->setStateValue(waveshareRelayD8Input6StateStateTypeId, state); + emitInputChangedEvent(thing, 6, state); + }); + connect(connection, &WaveshareRelayD8ModbusRtuConnection::input7Changed, + thing, [this, thing](quint16 value) { + bool state = value != 0; + thing->setStateValue(waveshareRelayD8Input7StateStateTypeId, state); + emitInputChangedEvent(thing, 7, state); + }); + connect(connection, &WaveshareRelayD8ModbusRtuConnection::input8Changed, + thing, [this, thing](quint16 value) { + bool state = value != 0; + thing->setStateValue(waveshareRelayD8Input8StateStateTypeId, state); + emitInputChangedEvent(thing, 8, state); + }); + + connection->update(); +} + +void IntegrationPluginWaveshareRelayD8::postSetupThing(Thing *thing) +{ + Q_UNUSED(thing) + if (!m_pluginTimer) { + qCDebug(dcWaveshareRelayD8()) << "Starting plugin timer (5s)..."; + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(5); + connect(m_pluginTimer, &PluginTimer::timeout, this, [this] { + foreach (WaveshareRelayD8ModbusRtuConnection *connection, m_connections) { + if (connection->reachable()) { + connection->update(); + } + } + }); + m_pluginTimer->start(); + } +} + +void IntegrationPluginWaveshareRelayD8::thingRemoved(Thing *thing) +{ + WaveshareRelayD8ModbusRtuConnection *connection = m_connections.take(thing); + delete connection; + + if (myThings().isEmpty() && m_pluginTimer) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); + m_pluginTimer = nullptr; + } +} + +void IntegrationPluginWaveshareRelayD8::executeAction(ThingActionInfo *info) +{ + Thing *thing = info->thing(); + Action action = info->action(); + WaveshareRelayD8ModbusRtuConnection *connection = m_connections.value(thing); + + if (!connection || !connection->reachable()) { + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + + if (action.actionTypeId() == waveshareRelayD8SetRelayActionTypeId) { + int channel = action.paramValue(waveshareRelayD8SetRelayActionChannelParamTypeId).toInt(); + quint16 value = action.paramValue(waveshareRelayD8SetRelayActionStateParamTypeId).toBool() ? 1 : 0; + + ModbusRtuReply *reply = nullptr; + switch (channel) { + case 1: reply = connection->setRelay1(value); break; + case 2: reply = connection->setRelay2(value); break; + case 3: reply = connection->setRelay3(value); break; + case 4: reply = connection->setRelay4(value); break; + case 5: reply = connection->setRelay5(value); break; + case 6: reply = connection->setRelay6(value); break; + case 7: reply = connection->setRelay7(value); break; + case 8: reply = connection->setRelay8(value); break; + default: + qCWarning(dcWaveshareRelayD8()) << "Invalid channel:" << channel; + info->finish(Thing::ThingErrorInvalidParameter); + return; + } + + if (!reply) { + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + + connect(reply, &ModbusRtuReply::finished, info, [reply, info]() { + if (reply->error() == ModbusRtuReply::NoError) { + info->finish(Thing::ThingErrorNoError); + } else { + qCWarning(dcWaveshareRelayD8()) << "setRelay write error:" << reply->errorString(); + info->finish(Thing::ThingErrorHardwareFailure); + } + }); + + } else if (action.actionTypeId() == waveshareRelayD8SetAllRelaysActionTypeId) { + quint16 value = action.paramValue(waveshareRelayD8SetAllRelaysActionStateParamTypeId).toBool() ? 1 : 0; + QVector values(8, value); + + // FC0F: write multiple coils at once (addresses 0-7) + ModbusRtuReply *reply = connection->modbusRtuMaster()->writeCoils( + connection->slaveId(), 0, values); + + if (!reply) { + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + + connect(reply, &ModbusRtuReply::finished, info, [reply, info]() { + if (reply->error() == ModbusRtuReply::NoError) { + info->finish(Thing::ThingErrorNoError); + } else { + qCWarning(dcWaveshareRelayD8()) << "setAllRelays write error:" << reply->errorString(); + info->finish(Thing::ThingErrorHardwareFailure); + } + }); + + } else { + info->finish(Thing::ThingErrorActionTypeNotFound); + } +} + +void IntegrationPluginWaveshareRelayD8::emitInputChangedEvent(Thing *thing, int channel, bool state) +{ + Event event; + event.setEventTypeId(waveshareRelayD8InputChangedEventTypeId); + event.setThingId(thing->id()); + ParamList params; + params << Param(waveshareRelayD8InputChangedEventChannelParamTypeId, channel); + params << Param(waveshareRelayD8InputChangedEventStateParamTypeId, state); + event.setParams(params); + emit emitEvent(event); +} diff --git a/waveshare-relay-d8/integrationpluginwavesharerelayed8.h b/waveshare-relay-d8/integrationpluginwavesharerelayed8.h new file mode 100644 index 0000000..50fdebe --- /dev/null +++ b/waveshare-relay-d8/integrationpluginwavesharerelayed8.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright (C) 2025, ETM-Schurig SARL +* +* This file is part of nymea-plugins-modbus. +* +* nymea-plugins-modbus 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. +* +* nymea-plugins-modbus 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 nymea-plugins-modbus. If not, see . +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef INTEGRATIONPLUGINWAVESHARERELAYED8_H +#define INTEGRATIONPLUGINWAVESHARERELAYED8_H + +#include +#include + +#include "extern-plugininfo.h" +#include "autogenerated/wavesharerelayd8modbusrtuconnection.h" + +class IntegrationPluginWaveshareRelayD8 : public IntegrationPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginwaveshare-relay-d8.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginWaveshareRelayD8(); + + void discoverThings(ThingDiscoveryInfo *info) override; + void setupThing(ThingSetupInfo *info) override; + void postSetupThing(Thing *thing) override; + void thingRemoved(Thing *thing) override; + void executeAction(ThingActionInfo *info) override; + +private: + PluginTimer *m_pluginTimer = nullptr; + QHash m_connections; + + void emitInputChangedEvent(Thing *thing, int channel, bool state); +}; + +#endif // INTEGRATIONPLUGINWAVESHARERELAYED8_H diff --git a/waveshare-relay-d8/meta.json b/waveshare-relay-d8/meta.json new file mode 100644 index 0000000..d5b6077 --- /dev/null +++ b/waveshare-relay-d8/meta.json @@ -0,0 +1,13 @@ +{ + "title": "Waveshare Relay D8", + "tagline": "8-channel Modbus RTU relay module with opto-isolated inputs", + "icon": "waveshare.png", + "stability": "experimental", + "offline": true, + "technologies": [ + "modbus" + ], + "categories": [ + "actor" + ] +} diff --git a/waveshare-relay-d8/waveshare-relay-d8-registers.json b/waveshare-relay-d8/waveshare-relay-d8-registers.json new file mode 100644 index 0000000..f8e938f --- /dev/null +++ b/waveshare-relay-d8/waveshare-relay-d8-registers.json @@ -0,0 +1,182 @@ +{ + "className": "WaveshareRelayD8", + "protocol": "RTU", + "endianness": "BigEndian", + "errorLimitUntilNotReachable": 10, + "checkReachableRegister": "relay1", + "registers": [], + "blocks": [ + { + "id": "relayStates", + "readSchedule": "update", + "registers": [ + { + "id": "relay1", + "address": 0, + "size": 1, + "type": "uint16", + "registerType": "coils", + "description": "Relay 1", + "defaultValue": "0", + "access": "RW" + }, + { + "id": "relay2", + "address": 1, + "size": 1, + "type": "uint16", + "registerType": "coils", + "description": "Relay 2", + "defaultValue": "0", + "access": "RW" + }, + { + "id": "relay3", + "address": 2, + "size": 1, + "type": "uint16", + "registerType": "coils", + "description": "Relay 3", + "defaultValue": "0", + "access": "RW" + }, + { + "id": "relay4", + "address": 3, + "size": 1, + "type": "uint16", + "registerType": "coils", + "description": "Relay 4", + "defaultValue": "0", + "access": "RW" + }, + { + "id": "relay5", + "address": 4, + "size": 1, + "type": "uint16", + "registerType": "coils", + "description": "Relay 5", + "defaultValue": "0", + "access": "RW" + }, + { + "id": "relay6", + "address": 5, + "size": 1, + "type": "uint16", + "registerType": "coils", + "description": "Relay 6", + "defaultValue": "0", + "access": "RW" + }, + { + "id": "relay7", + "address": 6, + "size": 1, + "type": "uint16", + "registerType": "coils", + "description": "Relay 7", + "defaultValue": "0", + "access": "RW" + }, + { + "id": "relay8", + "address": 7, + "size": 1, + "type": "uint16", + "registerType": "coils", + "description": "Relay 8", + "defaultValue": "0", + "access": "RW" + } + ] + }, + { + "id": "inputStates", + "readSchedule": "update", + "registers": [ + { + "id": "input1", + "address": 0, + "size": 1, + "type": "uint16", + "registerType": "discreteInputs", + "description": "Opto input 1", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "input2", + "address": 1, + "size": 1, + "type": "uint16", + "registerType": "discreteInputs", + "description": "Opto input 2", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "input3", + "address": 2, + "size": 1, + "type": "uint16", + "registerType": "discreteInputs", + "description": "Opto input 3", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "input4", + "address": 3, + "size": 1, + "type": "uint16", + "registerType": "discreteInputs", + "description": "Opto input 4", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "input5", + "address": 4, + "size": 1, + "type": "uint16", + "registerType": "discreteInputs", + "description": "Opto input 5", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "input6", + "address": 5, + "size": 1, + "type": "uint16", + "registerType": "discreteInputs", + "description": "Opto input 6", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "input7", + "address": 6, + "size": 1, + "type": "uint16", + "registerType": "discreteInputs", + "description": "Opto input 7", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "input8", + "address": 7, + "size": 1, + "type": "uint16", + "registerType": "discreteInputs", + "description": "Opto input 8", + "defaultValue": "0", + "access": "RO" + } + ] + } + ] +} diff --git a/waveshare-relay-d8/waveshare-relay-d8.pro b/waveshare-relay-d8/waveshare-relay-d8.pro new file mode 100644 index 0000000..eca5922 --- /dev/null +++ b/waveshare-relay-d8/waveshare-relay-d8.pro @@ -0,0 +1,13 @@ +include(../plugins.pri) + +# Generate modbus connection +MODBUS_CONNECTIONS += waveshare-relay-d8-registers.json +MODBUS_TOOLS_CONFIG += VERBOSE + +include(../modbus.pri) + +HEADERS += \ + integrationpluginwavesharerelayed8.h + +SOURCES += \ + integrationpluginwavesharerelayed8.cpp