From 22203798c8088a36e4381570961c94512020a107 Mon Sep 17 00:00:00 2001 From: Miguel Torres Date: Thu, 4 Nov 2021 13:44:23 -0700 Subject: [PATCH] first --- .gitignore | 4 + LICENSE | 515 +++++++++++ Makefile | 21 + README.md | 9 + app-service-metadata/.gitignore | 35 + app-service-metadata/Dockerfile | 27 + app-service-metadata/Makefile | 44 + app-service-metadata/VERSION | 1 + app-service-metadata/cmd/main.go | 224 +++++ .../cmd/res/configuration.toml | 108 +++ app-service-metadata/go.mod | 10 + .../internal/transforms/mqtt.go | 240 +++++ app-service-metadata/snapcraft.yaml | 20 + app-service-metadata/version.go | 10 + device-generic-mqtt/.dockerignore | 4 + device-generic-mqtt/.gitignore | 34 + device-generic-mqtt/Dockerfile | 35 + device-generic-mqtt/LICENSE | 201 ++++ device-generic-mqtt/Makefile | 51 ++ device-generic-mqtt/README.md | 67 ++ device-generic-mqtt/VERSION | 1 + device-generic-mqtt/bin/edgex-launch.sh | 30 + device-generic-mqtt/cmd/main.go | 22 + .../cmd/res/Generic-MQTT-Device.yaml | 77 ++ .../cmd/res/configuration.toml | 87 ++ device-generic-mqtt/go.mod | 13 + device-generic-mqtt/internal/driver/config.go | 101 ++ .../internal/driver/config_test.go | 94 ++ device-generic-mqtt/internal/driver/driver.go | 486 ++++++++++ .../internal/driver/driver_test.go | 595 ++++++++++++ .../internal/driver/incominglistener.go | 153 ++++ .../internal/driver/protocolpropertykey.go | 40 + .../internal/driver/readingchecker.go | 105 +++ .../internal/driver/responselistener.go | 70 ++ device-generic-mqtt/mock/device.go | 189 ++++ device-generic-mqtt/snap/hooks/configure | 4 + device-generic-mqtt/snap/hooks/install | 24 + device-generic-mqtt/snap/hooks/pre-refresh | 6 + device-generic-mqtt/snap/local/.gitkeep | 0 device-generic-mqtt/snap/snapcraft.yaml | 93 ++ device-generic-mqtt/version.go | 10 + device-generic-rest/.dockerignore | 17 + device-generic-rest/.gitignore | 29 + device-generic-rest/Dockerfile | 50 + device-generic-rest/LICENSE | 201 ++++ device-generic-rest/Makefile | 35 + device-generic-rest/README.md | 138 +++ device-generic-rest/VERSION | 1 + device-generic-rest/cmd/main.go | 32 + .../cmd/res/Generic-REST-Device.yaml | 75 ++ .../cmd/res/configuration.toml | 66 ++ device-generic-rest/driver/restdriver.go | 90 ++ device-generic-rest/driver/resthandler.go | 384 ++++++++ device-generic-rest/go.mod | 10 + device-generic-rest/version.go | 20 + scripts/arm64/basicdemo/.env | 21 + scripts/arm64/basicdemo/docker-compose.yml | 795 ++++++++++++++++ scripts/arm64/basicdemo/getSecurityToken.sh | 3 + scripts/arm64/basicdemo/startEdgeAIR.sh | 87 ++ scripts/arm64/basicdemo/startEdgex.sh | 88 ++ scripts/arm64/basicdemo/stopEdgex.sh | 3 + scripts/build_air_edgex_component.sh | 18 + scripts/build_installer.sh | 11 + scripts/delete_local_image.sh | 9 + scripts/linux/basicdemo/.env | 26 + scripts/linux/basicdemo/docker-compose.yml | 861 ++++++++++++++++++ scripts/linux/basicdemo/getSecurityToken.sh | 3 + scripts/linux/basicdemo/start.cmd | 86 ++ scripts/linux/basicdemo/start.sh | 140 +++ scripts/linux/basicdemo/startEdgeAIR.sh | 64 ++ scripts/linux/basicdemo/startEdgex.sh | 92 ++ scripts/linux/basicdemo/start_minikube.cmd | 4 + scripts/linux/basicdemo/start_minikube.sh | 5 + scripts/linux/basicdemo/stop.cmd | 3 + scripts/linux/basicdemo/stop.sh | 3 + scripts/linux/basicdemo/stopEdgex.sh | 3 + scripts/push_image.sh | 9 + scripts/tools.sh | 53 ++ 78 files changed, 7395 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 app-service-metadata/.gitignore create mode 100644 app-service-metadata/Dockerfile create mode 100644 app-service-metadata/Makefile create mode 100644 app-service-metadata/VERSION create mode 100644 app-service-metadata/cmd/main.go create mode 100644 app-service-metadata/cmd/res/configuration.toml create mode 100644 app-service-metadata/go.mod create mode 100644 app-service-metadata/internal/transforms/mqtt.go create mode 100644 app-service-metadata/snapcraft.yaml create mode 100644 app-service-metadata/version.go create mode 100644 device-generic-mqtt/.dockerignore create mode 100644 device-generic-mqtt/.gitignore create mode 100644 device-generic-mqtt/Dockerfile create mode 100644 device-generic-mqtt/LICENSE create mode 100644 device-generic-mqtt/Makefile create mode 100644 device-generic-mqtt/README.md create mode 100644 device-generic-mqtt/VERSION create mode 100644 device-generic-mqtt/bin/edgex-launch.sh create mode 100644 device-generic-mqtt/cmd/main.go create mode 100644 device-generic-mqtt/cmd/res/Generic-MQTT-Device.yaml create mode 100644 device-generic-mqtt/cmd/res/configuration.toml create mode 100644 device-generic-mqtt/go.mod create mode 100644 device-generic-mqtt/internal/driver/config.go create mode 100644 device-generic-mqtt/internal/driver/config_test.go create mode 100644 device-generic-mqtt/internal/driver/driver.go create mode 100644 device-generic-mqtt/internal/driver/driver_test.go create mode 100644 device-generic-mqtt/internal/driver/incominglistener.go create mode 100644 device-generic-mqtt/internal/driver/protocolpropertykey.go create mode 100644 device-generic-mqtt/internal/driver/readingchecker.go create mode 100644 device-generic-mqtt/internal/driver/responselistener.go create mode 100644 device-generic-mqtt/mock/device.go create mode 100644 device-generic-mqtt/snap/hooks/configure create mode 100644 device-generic-mqtt/snap/hooks/install create mode 100644 device-generic-mqtt/snap/hooks/pre-refresh create mode 100644 device-generic-mqtt/snap/local/.gitkeep create mode 100644 device-generic-mqtt/snap/snapcraft.yaml create mode 100644 device-generic-mqtt/version.go create mode 100644 device-generic-rest/.dockerignore create mode 100644 device-generic-rest/.gitignore create mode 100644 device-generic-rest/Dockerfile create mode 100644 device-generic-rest/LICENSE create mode 100644 device-generic-rest/Makefile create mode 100644 device-generic-rest/README.md create mode 100644 device-generic-rest/VERSION create mode 100644 device-generic-rest/cmd/main.go create mode 100644 device-generic-rest/cmd/res/Generic-REST-Device.yaml create mode 100644 device-generic-rest/cmd/res/configuration.toml create mode 100644 device-generic-rest/driver/restdriver.go create mode 100644 device-generic-rest/driver/resthandler.go create mode 100644 device-generic-rest/go.mod create mode 100644 device-generic-rest/version.go create mode 100644 scripts/arm64/basicdemo/.env create mode 100644 scripts/arm64/basicdemo/docker-compose.yml create mode 100755 scripts/arm64/basicdemo/getSecurityToken.sh create mode 100755 scripts/arm64/basicdemo/startEdgeAIR.sh create mode 100755 scripts/arm64/basicdemo/startEdgex.sh create mode 100755 scripts/arm64/basicdemo/stopEdgex.sh create mode 100755 scripts/build_air_edgex_component.sh create mode 100755 scripts/build_installer.sh create mode 100755 scripts/delete_local_image.sh create mode 100644 scripts/linux/basicdemo/.env create mode 100644 scripts/linux/basicdemo/docker-compose.yml create mode 100755 scripts/linux/basicdemo/getSecurityToken.sh create mode 100644 scripts/linux/basicdemo/start.cmd create mode 100755 scripts/linux/basicdemo/start.sh create mode 100755 scripts/linux/basicdemo/startEdgeAIR.sh create mode 100755 scripts/linux/basicdemo/startEdgex.sh create mode 100644 scripts/linux/basicdemo/start_minikube.cmd create mode 100755 scripts/linux/basicdemo/start_minikube.sh create mode 100644 scripts/linux/basicdemo/stop.cmd create mode 100755 scripts/linux/basicdemo/stop.sh create mode 100755 scripts/linux/basicdemo/stopEdgex.sh create mode 100755 scripts/push_image.sh create mode 100755 scripts/tools.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fbb86fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.target +.DS_Store +dist + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0788200 --- /dev/null +++ b/LICENSE @@ -0,0 +1,515 @@ +Copyright (c) 2021 TIBCO Software Inc. All Rights Reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of TIBCO Software Inc. nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT OWNER AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Addenda: +* +spf13-cast: v1.3.0 + +The MIT License (MIT) + +Copyright (c) 2014 Steve Francia + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +* +paho.mqtt.golang: 1.2.0 + +This product includes software licensed under the Eclipse Public License (EPL), v.2.0. The source code for such software component licensed under the EPL v.2.0 is available upon request to TIBCO at support@tibco.com. + +Except as expressly set forth in the EPL v.2.0, the component is provided on an "as is" basis, without warranties or conditions of any kind, either express or implied including, without limitation, any warranties or conditions of title, non-infringement, merchantability or fitness for a particular purpose. + +Except as expressly set forth in the EPL v.2.0, neither TIBCO nor any contributors shall have any liability for any direct, indirect, incidental, special, exemplary, or consequential damages (including without limitation lost profits), however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use or distribution of the component or the exercise of any rights granted under the EPL v.2.0, even if advised of the possibility of such damages. + +Any provisions under which TIBCO makes the component available which differ from the EPL v.2.0 are offered by TIBCO alone and not by any other party. + +* +gorilla-mux: v1.8.0 + +Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +* +Zone.js: 0.11.4 + +opyright (c) 2010-2021 Google LLC. https://angular.io/license + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +* +edgexfoundry/core-domain: 0.6.0 + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +* +edgexfoundry/core-command: 0.2.1 + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..83dda4f --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +SHELL := /bin/bash +SCRIPTS_PATH := scripts + +.PHONY: build-installer +build-installer: + @$(SCRIPTS_PATH)/build_installer.sh + +.PHONY: build-push-delete-air-edgex-component +build-push-delete-air-edgex-component: build-air-edgex-component push-image delete-local-image + +.PHONY: build-air-edgex-component +build-air-edgex-component: + @$(SCRIPTS_PATH)/build_air_edgex_component.sh ${IMAGE_NAME} ${IMAGE_TAG} ${IMAGE_URL} ${EDGEX_COMPONENT_NAME} ${TARGET_NAME} + +.PHONY: push-image +push-image: + @$(SCRIPTS_PATH)/push_image.sh ${IMAGE_NAME} ${IMAGE_TAG} ${IMAGE_URL} + +.PHONY: delete-local-image +delete-local-image: + @$(SCRIPTS_PATH)/delete_local_image.sh ${IMAGE_NAME} ${IMAGE_TAG} ${IMAGE_URL} diff --git a/README.md b/README.md new file mode 100644 index 0000000..aeb2711 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Project Air + +Project Air is an IoT platform for the registration of devices, process of IoT produced data and definition, deployment and configuration of Application pipelines. + +For more information please visit [Project Air](https://tibcosoftware.github.io/labs-air/) docs page. + +## Introduction + +These components are device, services and other [edgex](https://www.edgexfoundry.org/) specific add-ons for project Air. diff --git a/app-service-metadata/.gitignore b/app-service-metadata/.gitignore new file mode 100644 index 0000000..59c3824 --- /dev/null +++ b/app-service-metadata/.gitignore @@ -0,0 +1,35 @@ + +.vscode +**/debug +**/debug.test +*.txt +*.html + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +cmd/app-service-metadata + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +*.log + +.idea/ +vendor/ +go.sum + +# snap files +*.snap +*.assert +prime/ +stage/ +parts/ +squashfs-root/ +coverage.out \ No newline at end of file diff --git a/app-service-metadata/Dockerfile b/app-service-metadata/Dockerfile new file mode 100644 index 0000000..8eb6d4b --- /dev/null +++ b/app-service-metadata/Dockerfile @@ -0,0 +1,27 @@ +FROM golang:1.15-alpine AS builder + +# add git for go modules +RUN apk update;\ + apk add --no-cache make git gcc libc-dev libsodium-dev zeromq-dev; + +WORKDIR /app-service-metadata + +COPY go.mod . + +RUN go mod download + +COPY . . + +RUN apk info -a zeromq-dev + +RUN make build + +# Next image - Copy built Go binary into new workspace +FROM alpine + + +RUN apk --no-cache add zeromq +COPY --from=builder /app-service-metadata/cmd/res /res +COPY --from=builder /app-service-metadata/cmd/app-service-metadata /app-service-metadata + +CMD [ "/app-service-metadata" ,"--registry","--confdir=/res"] \ No newline at end of file diff --git a/app-service-metadata/Makefile b/app-service-metadata/Makefile new file mode 100644 index 0000000..b21bfe8 --- /dev/null +++ b/app-service-metadata/Makefile @@ -0,0 +1,44 @@ +.PHONY: build test clean docker + +GO=CGO_ENABLED=1 GO111MODULE=on go + +MICROSERVICES=cmd/app-service-metadata +.PHONY: $(MICROSERVICES) + +VERSION=$(shell cat ./VERSION) +GOFLAGS=-ldflags "-X github.com/TIBCOSoftware/labs-air-infra/edgexfoundry/app-service-metadata.Version=$(VERSION)" +GIT_SHA=$(shell git rev-parse HEAD) + +build: $(MICROSERVICES) + $(GO) install -tags=safe + +$(MICROSERVICES): + $(GO) build $(GOFLAGS) -o $@ ./cmd + +docker: + docker build --no-cache \ + --label "git_sha=$(GIT_SHA)" \ + -t tibcosoftware/labs-air-edgex-app-service-metadata:$(VERSION) \ + . + +dockerarm64: + docker build --no-cache \ + --label "git_sha=$(GIT_SHA)" \ + -t tibcosoftware/labs-air-edgex-app-service-metadata-arm64:$(VERSION) \ + . + + +dockerbuildxarm64: + docker buildx build \ + --platform linux/arm64 \ + --label "git_sha=$(GIT_SHA)" \ + -t tibcosoftware/docker-app-service-metadata-arm64:$(VERSION) --push \ + . + +test: + $(GO) vet ./... + gofmt -l . + $(GO) test -coverprofile=coverage.out ./... + +clean: + rm -f $(MICROSERVICES) diff --git a/app-service-metadata/VERSION b/app-service-metadata/VERSION new file mode 100644 index 0000000..f0bb29e --- /dev/null +++ b/app-service-metadata/VERSION @@ -0,0 +1 @@ +1.3.0 diff --git a/app-service-metadata/cmd/main.go b/app-service-metadata/cmd/main.go new file mode 100644 index 0000000..3af42f9 --- /dev/null +++ b/app-service-metadata/cmd/main.go @@ -0,0 +1,224 @@ +package main + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "os" + "strconv" + "time" + + inttransforms "github.com/TIBCOSoftware/labs-air/edgexfoundry/app-service-metadata/internal/transforms" + "github.com/edgexfoundry/app-functions-sdk-go/appcontext" + "github.com/edgexfoundry/app-functions-sdk-go/appsdk" + "github.com/edgexfoundry/go-mod-core-contracts/clients" + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" + "github.com/edgexfoundry/go-mod-core-contracts/clients/metadata" + "github.com/edgexfoundry/go-mod-core-contracts/clients/urlclient/local" + "github.com/edgexfoundry/go-mod-core-contracts/models" + "github.com/google/uuid" +) + +const ( + serviceKey = "app-service-metadata" +) + +type msgStruct struct { + ID string `json:"id"` + Device string `json:"device"` + Origin int64 `json:"source"` + Gateway string `json:"gateway"` + Readings []models.Reading `json:"readings"` +} + +type gatewayStruct struct { + UUUID string `json:"uuid"` + Description string `json:"description"` + Address string `json:"address"` + Router string `json:"router"` + RouterPort string `json:"routerPort"` + DeployNetwork string `json:"deployNetwork"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + AccessToken string `json:"accessToken"` + Username string `json:"username"` + Platform string `json:"platform"` + Createdts int64 `json:"createdts"` + Updatedts int64 `json:"updatedts"` +} + +type metadataStruct struct { + Gateway gatewayStruct `json:"gateway"` + Devices []models.Device `json:"devices"` +} + +var mdc metadata.DeviceClient +var mqttSender *inttransforms.MQTTSender +var gatewayInfo gatewayStruct + +func main() { + + // Create an instance of the EdgeX SDK and initialize it. + edgexSdk := &appsdk.AppFunctionsSDK{ServiceKey: serviceKey} + if err := edgexSdk.Initialize(); err != nil { + message := fmt.Sprintf("SDK initialization failed: %v\n", err) + if edgexSdk.LoggingClient != nil { + edgexSdk.LoggingClient.Error(message) + } else { + fmt.Println(message) + } + os.Exit(-1) + } + + // Set the logging client for the transforms + inttransforms.SetLoggingClient(edgexSdk.LoggingClient) + + // Get the application's specific configuration settings. + metadataPublishInterval := 30 + metadataClient := "" + appSettings := edgexSdk.ApplicationSettings() + if appSettings != nil { + + gatewayID := inttransforms.GetAppSetting(appSettings, "GatewayId") + gatewayDescription := inttransforms.GetAppSetting(appSettings, "GatewayDescription") + gatewayHostname := inttransforms.GetAppSetting(appSettings, "GatewayHostname") + gatewayRouter := inttransforms.GetAppSetting(appSettings, "GatewayRouter") + gatewayRouterPort := inttransforms.GetAppSetting(appSettings, "GatewayRouterPort") + gatewayDeployNetwork := inttransforms.GetAppSetting(appSettings, "GatewayDeployNetwork") + gatewayLatitude := inttransforms.GetAppSetting(appSettings, "GatewayLatitude") + gatewayLongitude := inttransforms.GetAppSetting(appSettings, "GatewayLongitude") + gatewayAccessToken := inttransforms.GetAppSetting(appSettings, "GatewayAccessToken") + gatewayUsername := inttransforms.GetAppSetting(appSettings, "GatewayUsername") + gatewayPlatform := inttransforms.GetAppSetting(appSettings, "GatewayPlatform") + metadataClient = inttransforms.GetAppSetting(appSettings, "MetadataClient") + metadataPublishIntervalStr := inttransforms.GetAppSetting(appSettings, "MetadataPublishIntervalSecs") + + startupTime := time.Now().UnixNano() / int64(time.Millisecond) + gatewayInfo.UUUID = gatewayID + gatewayInfo.AccessToken = gatewayAccessToken + gatewayInfo.Address = gatewayHostname + gatewayInfo.Router = gatewayRouter + gatewayInfo.RouterPort = gatewayRouterPort + gatewayInfo.DeployNetwork = gatewayDeployNetwork + gatewayInfo.Createdts = startupTime + gatewayInfo.Updatedts = startupTime + gatewayInfo.Description = gatewayDescription + gatewayInfo.Username = gatewayUsername + gatewayInfo.Platform = gatewayPlatform + gatewayInfo.Latitude, _ = strconv.ParseFloat(gatewayLatitude, 32) + gatewayInfo.Longitude, _ = strconv.ParseFloat(gatewayLongitude, 32) + metadataPublishInterval64, _ := strconv.ParseInt(metadataPublishIntervalStr, 10, 32) + metadataPublishInterval = int(metadataPublishInterval64) + + } else { + fmt.Println("Application settings nil") + } + + deviceClientURL := metadataClient + clients.ApiDeviceRoute + + mdc = metadata.NewDeviceClient( + local.New(deviceClientURL)) + // local.New(config.Clients[common.CoreDataClientName].Url() + clients.ApiValueDescriptorRoute)) + + // Create the MQTT Sender + mqttConfig, _ := inttransforms.LoadMQTTConfig(appSettings) + mqttSender = inttransforms.NewMQTTSender(edgexSdk.LoggingClient, nil, mqttConfig) + + // Set pipeline configuration, the collection of functions to + // execute every time an event is triggered. + edgexSdk.SetFunctionsPipeline( + // transforms.NewFilter(deviceNames).FilterByDeviceName, + processEvent, + ) + + // Set ticker to trigger metadata publishing + ticker := time.NewTicker(time.Duration(metadataPublishInterval) * time.Second) + go func() { + for ; true; <-ticker.C { + publishMetadata(edgexSdk.LoggingClient) + } + }() + + // Lastly, we'll go ahead and tell the SDK to "start" and begin listening for events + // to trigger the pipeline. + err := edgexSdk.MakeItRun() + if err != nil { + edgexSdk.LoggingClient.Error("MakeItRun returned error: ", err.Error()) + os.Exit(-1) + } + + // Do any required cleanup here + + ticker.Stop() + os.Exit(0) +} + +func processEvent(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) { + + if len(params) < 1 { + // We didn't receive a result + return false, nil + } + + if event, ok := params[0].(models.Event); ok { + + edgexcontext.LoggingClient.Debug(fmt.Sprintf("Processing event for device: %s", event.Device)) + + // Check to see if reading is of type binary and if so, update reading + if event.Readings[0].ValueType == "Binary" { + edgexcontext.LoggingClient.Debug(fmt.Sprintf("Processing Binary Data")) + event.Readings[0].Id = uuid.New().String() + // event.Readings[0].Value = string(event.Readings[0].BinaryValue) + event.Readings[0].Value = base64.StdEncoding.EncodeToString(event.Readings[0].BinaryValue) + // event.Readings[0].Value = "image" + event.Readings[0].BinaryValue = nil + event.Readings[0].ValueType = "String" + } + + jsondat := &msgStruct{ + ID: event.ID, + Device: event.Device, + Origin: event.Origin, + Gateway: gatewayInfo.UUUID, + Readings: event.Readings, + } + + encjson, _ := json.Marshal(jsondat) + + edgexcontext.Complete(encjson) + + // return false, nil + + return true, nil + } + + return false, errors.New("Unexpected type received") + +} + +func publishMetadata(loggingClient logger.LoggingClient) (bool, interface{}) { + + loggingClient.Debug("Publishing metadata") + + // Update gatewayInfo with publish time + publishTime := time.Now().UnixNano() / int64(time.Millisecond) + gatewayInfo.Updatedts = publishTime + + devices, err := mdc.Devices(context.Background()) + + if devices != nil && err == nil { + jsonmd := &metadataStruct{ + Gateway: gatewayInfo, + Devices: devices, + } + + encjson, _ := json.Marshal(jsonmd) + + // Export metadata + return mqttSender.MQTTSendMetadata(string(encjson)) + } + + return false, errors.New("Unexpected devices result received") +} diff --git a/app-service-metadata/cmd/res/configuration.toml b/app-service-metadata/cmd/res/configuration.toml new file mode 100644 index 0000000..1671b70 --- /dev/null +++ b/app-service-metadata/cmd/res/configuration.toml @@ -0,0 +1,108 @@ +[Writable] +LogLevel = 'INFO' + [Writable.StoreAndForward] + Enabled = false + RetryInterval = '5m' + MaxRetryCount = 10 + +[Service] +BootTimeout = '30s' +ClientMonitor = '15s' +CheckInterval = '10s' +Host = 'localhost' +Port = 48530 +Protocol = 'http' +ReadMaxLimit = 100 +StartupMsg = 'This is a MQTT Export Application Service' +Timeout = '30s' + +[Registry] +Host = 'localhost' +Port = 8500 +Type = 'consul' + +[Logging] +EnableRemote = false +File = './logs/app-service-metadata.log' + +[Database] +Type = "mongodb" +Host = "localhost" +Port = 27017 +Timeout = "30s" +Username = "" +Password = "" + +# [SecretStore] +# Host = 'localhost' +# Port = 8200 +# Path = '/v1/secret/edgex/appservice/' +# Protocol = 'https' + +# [SecretStore.Authentication] +# AuthType = 'X-Vault-Token' +# AuthToken = 'edgex' + +[Clients] + [Clients.CoreData] + Protocol = 'http' + Host = 'localhost' + Port = 48080 + + [Clients.Metadata] + Name = "edgex-core-metadata" + Protocol = "http" + Host = "localhost" + Port = 48081 + Timeout = 5000 + + [Clients.Logging] + Protocol = "http" + Host = "localhost" + Port = 48061 + +[MessageBus] +Type = 'zero' + [MessageBus.PublishHost] + Host = '*' + Port = 5580 + Protocol = 'tcp' + [MessageBus.SubscribeHost] + Host = 'localhost' + Port = 5563 + Protocol = 'tcp' + +# Choose either an HTTP trigger or MessageBus trigger (aka Binding) + +#[Binding] +#Type="http" + +[Binding] +Type="messagebus" +SubscribeTopic="events" +PublishTopic="filteredevents" + +[ApplicationSettings] +GatewayId = "ElectricPlant" +GatewayDescription = "description" +GatewayHostname = "localhost" +GatewayRouter = "localhost" +GatewayRouterPort = "22" +GatewayDeployNetwork = "edgex-network" +GatewayLatitude = "36.0" +GatewayLongitude = "-98.0" +GatewayAccessToken = "changeme" +GatewayUsername = "changeme" +GatewayPlatform = "changeme" +DeviceNames = "versicharge-0001" +MqttProtocol = "tcp" +MqttHostname = "" +MqttPort = "443" +MqttPublisher = "EdgexExport" +MqttUser = "mqtt_admin" +MqttPassword = "mqtt_admin" +MqttTrustStore = "" +MqttTopic = "EdgexGatewayData" +MetadataClient = "http://localhost:48081" +MetadataPublishIntervalSecs = "30" + diff --git a/app-service-metadata/go.mod b/app-service-metadata/go.mod new file mode 100644 index 0000000..e60ace3 --- /dev/null +++ b/app-service-metadata/go.mod @@ -0,0 +1,10 @@ +module github.com/TIBCOSoftware/labs-air/edgexfoundry/app-service-metadata + +go 1.15 + +require ( + github.com/eclipse/paho.mqtt.golang v1.2.0 + github.com/edgexfoundry/app-functions-sdk-go v1.3.0 + github.com/edgexfoundry/go-mod-core-contracts v0.1.112 + github.com/google/uuid v1.1.2 +) diff --git a/app-service-metadata/internal/transforms/mqtt.go b/app-service-metadata/internal/transforms/mqtt.go new file mode 100644 index 0000000..5c9f00a --- /dev/null +++ b/app-service-metadata/internal/transforms/mqtt.go @@ -0,0 +1,240 @@ +package transforms + +import ( + "crypto/tls" + "errors" + "fmt" + "strings" + // "time" + + MQTT "github.com/eclipse/paho.mqtt.golang" + "github.com/edgexfoundry/app-functions-sdk-go/pkg/util" + "github.com/edgexfoundry/go-mod-core-contracts/clients" + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" +) + +// MQTTConfig contains mqtt client parameters +type MQTTConfig struct { + Protocol string + Hostname string + Port string + TrustStore string + User string + Password string + Publisher string + Topic string + QOS byte + Retain bool + AutoReconnect bool +} + +// KeyCertPair is used to pass key/cert pair to NewMQTTSender +// KeyPEMBlock and CertPEMBlock will be used if they are not nil +// then it will fall back to KeyFile and CertFile +type KeyCertPair struct { + KeyFile string + CertFile string + KeyPEMBlock []byte + CertPEMBlock []byte +} + +// MQTTSender ... +type MQTTSender struct { + client MQTT.Client + topic string + opts MQTTConfig +} + +var loggingClient logger.LoggingClient + +// SetLoggingClient set the logging client +func SetLoggingClient(logging logger.LoggingClient) { + loggingClient = logging +} + +// GetAppSetting get application setting +func GetAppSetting(settings map[string]string, name string) string { + value, ok := settings[name] + + if ok { + loggingClient.Info(fmt.Sprintf("Setting for %s: %s", name, value)) + return value + } + loggingClient.Error(fmt.Sprintf("ApplicationName application setting %s not found", name)) + return "" + +} + +// LoadMQTTConfig Loads the MQTT configuration necessary to connect to MQTT +func LoadMQTTConfig(appSettings map[string]string) (*MQTTConfig, error) { + + var protocol, host, port, publisher, user, password, trustStore, topic string + + if appSettings != nil { + protocol = GetAppSetting(appSettings, "MqttProtocol") + host = GetAppSetting(appSettings, "MqttHostname") + port = GetAppSetting(appSettings, "MqttPort") + publisher = GetAppSetting(appSettings, "MqttPublisher") + user = GetAppSetting(appSettings, "MqttUser") + password = GetAppSetting(appSettings, "MqttPassword") + trustStore = GetAppSetting(appSettings, "MqttTrustStore") + topic = GetAppSetting(appSettings, "MqttTopic") + } else { + return nil, errors.New("No application-specific settings found") + } + + config := MQTTConfig{} + + config.Protocol = protocol + config.Hostname = host + config.Port = port + config.Publisher = publisher + config.User = user + config.Password = password + config.TrustStore = trustStore + config.Topic = topic + config.QOS = 0 + config.Retain = false + config.AutoReconnect = false + + return &config, nil +} + +// NewMQTTSender creates, initializes and returns a new instance of MQTTSender +func NewMQTTSender(logging logger.LoggingClient, keyCertPair *KeyCertPair, mqttConfig *MQTTConfig) *MQTTSender { + protocol := strings.ToLower(mqttConfig.Protocol) + + opts := MQTT.NewClientOptions() + broker := protocol + "://" + mqttConfig.Hostname + ":" + mqttConfig.Port + opts.AddBroker(broker) + opts.SetClientID(mqttConfig.Publisher) + opts.SetUsername(mqttConfig.User) + opts.SetPassword(mqttConfig.Password) + // opts.SetAutoReconnect(mqttConfig.AutoReconnect) + opts.SetAutoReconnect(true) + // opts.SetKeepAlive(time.Second * time.Duration(30)) + + if (protocol == "tcps" || protocol == "ssl" || protocol == "tls") && keyCertPair != nil { + var cert tls.Certificate + var err error + + if keyCertPair.KeyPEMBlock != nil && keyCertPair.CertPEMBlock != nil { + cert, err = tls.X509KeyPair(keyCertPair.CertPEMBlock, keyCertPair.KeyPEMBlock) + } else { + cert, err = tls.LoadX509KeyPair(keyCertPair.CertFile, keyCertPair.KeyFile) + } + + if err != nil { + logging.Error("Failed loading x509 data") + return nil + } + + tlsConfig := &tls.Config{ + ClientCAs: nil, + InsecureSkipVerify: true, + Certificates: []tls.Certificate{cert}, + } + + opts.SetTLSConfig(tlsConfig) + + } + + opts.SetConnectionLostHandler(func(client MQTT.Client, e error) { + logging.Warn(fmt.Sprintf("Connection lost : %v", e)) + token := client.Connect() + if token.Wait() && token.Error() != nil { + logging.Info(fmt.Sprintf("Reconnection failed : %v", token.Error())) + } else { + logging.Info(fmt.Sprintf("Reconnection sucessful")) + } + }) + + client := MQTT.NewClient(opts) + token := client.Connect() + if token.Wait() && token.Error() != nil { + logging.Error("Failed to connect: %v", token.Error()) + return nil + } + + logging.Info(fmt.Sprintf("Connected to MQTT sucessfully")) + + sender := &MQTTSender{ + client: client, + topic: mqttConfig.Topic, + opts: *mqttConfig, + } + + // sender := &MQTTSender{ + // client: MQTT.NewClient(opts), + // topic: mqttConfig.Topic, + // opts: *mqttConfig, + // } + + return sender +} + +// MQTTSend will send data from the previous function to the specified Endpoint via MQTT. +// If no previous function exists, then the event that triggered the pipeline will be used. +// An empty string for the mimetype will default to application/json. +func (sender MQTTSender) MQTTSend(msg string) (bool, interface{}) { + + loggingClient.Debug(fmt.Sprintf("Sending Message: [%s]\n", msg)) + + if !sender.client.IsConnected() { + loggingClient.Info("Reconnecting to mqtt server") + if token := sender.client.Connect(); token.Wait() && token.Error() != nil { + return false, fmt.Errorf("Could not connect to mqtt server, drop event. Error: %s", token.Error().Error()) + } + loggingClient.Info("Reconnected to mqtt server") + } + + data, err := util.CoerceType(msg) + if err != nil { + return false, err + } + + token := sender.client.Publish(sender.topic, sender.opts.QOS, sender.opts.Retain, data) + // FIXME: could be removed? set of tokens? + token.Wait() + + if token.Error() != nil { + return false, token.Error() + } + + loggingClient.Trace("Data exported", "Transport", "MQTT", clients.CorrelationHeader) + + return true, nil +} + +// MQTTSendMetadata will send data from the previous function to the specified Endpoint via MQTT. +// If no previous function exists, then the event that triggered the pipeline will be used. +// An empty string for the mimetype will default to application/json. +func (sender MQTTSender) MQTTSendMetadata(msg string) (bool, interface{}) { + + loggingClient.Trace(fmt.Sprintf("Sending Message: [%s]\n", msg)) + + if !sender.client.IsConnected() { + loggingClient.Info("Reconnecting to mqtt server") + if token := sender.client.Connect(); token.Wait() && token.Error() != nil { + return false, fmt.Errorf("Could not connect to mqtt server, drop event. Error: %s", token.Error().Error()) + } + loggingClient.Info("Reconnected to mqtt server") + } + + data, err := util.CoerceType(msg) + if err != nil { + return false, err + } + + token := sender.client.Publish("EdgexGatewayMetadata", sender.opts.QOS, sender.opts.Retain, data) + // FIXME: could be removed? set of tokens? + token.Wait() + + if token.Error() != nil { + return false, token.Error() + } + + loggingClient.Trace("Data exported", "Transport", "MQTT", clients.CorrelationHeader) + + return true, nil +} diff --git a/app-service-metadata/snapcraft.yaml b/app-service-metadata/snapcraft.yaml new file mode 100644 index 0000000..92cdac2 --- /dev/null +++ b/app-service-metadata/snapcraft.yaml @@ -0,0 +1,20 @@ +name: labs-air-app-service-metadata +version: git +summary: An edgex service to provide metadata. +description: | + An edgex service to provide metadata. +confinement: devmode +base: core18 + +parts: + app-service-metadata: + plugin: go + go-importpath: github.com/TIBCOSoftware/labs-air/edgexfoundry/app-service-metadata + source: . + source-type: git + build-packages: + - gcc + +apps: + app-service-metadata: + command: bin/app-service-metadata \ No newline at end of file diff --git a/app-service-metadata/version.go b/app-service-metadata/version.go new file mode 100644 index 0000000..8b534c5 --- /dev/null +++ b/app-service-metadata/version.go @@ -0,0 +1,10 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +// Copyright (C) 2019 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package device + +// Global version for device-sdk-go +var Version string = "to be replaced by makefile" diff --git a/device-generic-mqtt/.dockerignore b/device-generic-mqtt/.dockerignore new file mode 100644 index 0000000..3f3e170 --- /dev/null +++ b/device-generic-mqtt/.dockerignore @@ -0,0 +1,4 @@ +# Ignoring the go.sum file. There are checksum issues with +# different versions of Golang. Remove once all Go versions +# are in sync +go.sum diff --git a/device-generic-mqtt/.gitignore b/device-generic-mqtt/.gitignore new file mode 100644 index 0000000..d843a4f --- /dev/null +++ b/device-generic-mqtt/.gitignore @@ -0,0 +1,34 @@ +.vscode +**/debug +**/debug.test +*.txt +*.html + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +*.log + +.idea/ +vendor/ +cmd/device-generic-mqtt +glide.lock +go.sum + +# snap files +*.snap +*.assert +prime/ +stage/ +parts/ +squashfs-root/ \ No newline at end of file diff --git a/device-generic-mqtt/Dockerfile b/device-generic-mqtt/Dockerfile new file mode 100644 index 0000000..8936a82 --- /dev/null +++ b/device-generic-mqtt/Dockerfile @@ -0,0 +1,35 @@ +# +# Copyright (C) 2018 IOTech Ltd +# +# SPDX-License-Identifier: Apache-2.0 + +ARG ALPINE=golang:1.15-alpine +FROM ${ALPINE} AS builder +ARG ALPINE_PKG_BASE="build-base git openssh-client" +ARG ALPINE_PKG_EXTRA="" + +# Replicate the APK repository override. +# If it is no longer necessary to avoid the CDN mirros we should consider dropping this as it is brittle. +RUN sed -e 's/dl-cdn[.]alpinelinux.org/nl.alpinelinux.org/g' -i~ /etc/apk/repositories +# Install our build time packages. +RUN apk add --no-cache ${ALPINE_PKG_BASE} ${ALPINE_PKG_EXTRA} + +WORKDIR /device-generic-mqtt + +COPY . . + +# To run tests in the build container: +# docker build --build-arg 'MAKE=build test' . +# This is handy of you do your Docker business on a Mac +ARG MAKE=build +RUN make $MAKE + + +FROM scratch + +ENV APP_PORT=49982 +EXPOSE $APP_PORT + +COPY --from=builder /device-generic-mqtt/cmd / + +ENTRYPOINT ["/device-generic-mqtt","--confdir=/res"] diff --git a/device-generic-mqtt/LICENSE b/device-generic-mqtt/LICENSE new file mode 100644 index 0000000..7337dfa --- /dev/null +++ b/device-generic-mqtt/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 IOTech, Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/device-generic-mqtt/Makefile b/device-generic-mqtt/Makefile new file mode 100644 index 0000000..f169616 --- /dev/null +++ b/device-generic-mqtt/Makefile @@ -0,0 +1,51 @@ +.PHONY: build test clean prepare update docker + +GO = CGO_ENABLED=0 GO111MODULE=on go + +MICROSERVICES=cmd/device-generic-mqtt +.PHONY: $(MICROSERVICES) + +DOCKERS=docker_device_generic_mqtt +.PHONY: $(DOCKERS) + +VERSION=$(shell cat ./VERSION) +GIT_SHA=$(shell git rev-parse HEAD) + +GOFLAGS=-ldflags "-X github.com/TIBCOSoftware/labs-air/edgexfoundry/device-generic-mqtt.Version=$(VERSION)" + +build: $(MICROSERVICES) + $(GO) build ./... + +cmd/device-generic-mqtt: + $(GO) build $(GOFLAGS) -o $@ ./cmd + +test: + $(GO) test ./... -cover + +clean: + rm -f $(MICROSERVICES) + +run: + cd bin && ./edgex-launch.sh + +docker: $(DOCKERS) + +docker_device_generic_mqtt: + docker build --no-cache \ + --label "git_sha=$(GIT_SHA)" \ + -t tibcosoftware/labs-air-edgex-device-generic-mqtt:$(VERSION) \ + . + +dockerarm64: + docker build --no-cache \ + --label "git_sha=$(GIT_SHA)" \ + -t tibcosoftware/labs-air-edgex-device-generic-mqtt-arm64:$(VERSION) \ + . + +dockerbuildxarm64: + docker buildx build \ + --platform linux/arm64 \ + --label "git_sha=$(GIT_SHA)" \ + -t magallardo/docker-device-generic-mqtt-arm64:$(VERSION) \ + . + diff --git a/device-generic-mqtt/README.md b/device-generic-mqtt/README.md new file mode 100644 index 0000000..ec7abca --- /dev/null +++ b/device-generic-mqtt/README.md @@ -0,0 +1,67 @@ +# Device MQTT Go +MQTT device service go version. The design is base on [ document](https://wiki.edgexfoundry.org/display/FA/MQTT+Device+Service+-+How+to+use%2C+configure%2C+and+where+to+customize) . + +## Requisite +* core-data +* core-metadata +* core-command + +## Predefined configuration + +### Incoming data listener and command response listener +Modify `configuration-driver.toml` file which under `./cmd/res` folder +```toml +[Incoming] +Protocol = "tcp" +Host = "m12.cloudmqtt.com" +Port = 17217 +Username = "tobeprovided" +Password = "tobeprovided" +Qos = 0 +KeepAlive = 3600 +MqttClientId = "IncomingDataSubscriber" +Topic = "DataTopic" + +[Response] +Protocol = "tcp" +Host = "m12.cloudmqtt.com" +Port = 17217 +Username = "tobeprovided" +Password = "tobeprovided" +Qos = 0 +KeepAlive = 3600 +MqttClientId = "CommandResponseSubscriber" +Topic = "ResponseTopic" +``` + +### Device list +Define devices info for device-sdk to auto upload device profile and create device instance. Please modify `configuration.toml` file which under `./cmd/res` folder +```toml +[[DeviceList]] + Name = "MQTT test device" + Profile = "Test.Device.MQTT.Profile" + Description = "MQTT device is created for test purpose" + Labels = [ "MQTT", "test"] + [DeviceList.Addressable] + name = "Gateway address" + Protocol = "TCP" + Address = "m12.cloudmqtt.com" + Port = 17217 + Publisher = "CommandPublisher" + user = "tobeprovided" + password = "tobeprovided" + topic = "CommandTopic" + +``` + +## Installation and Execution +```bash +make prepare +make build +make run +``` + +## build image +```bash +docker build -t edgexfoundry/docker-device-mqtt-go:0.1.0 . +``` \ No newline at end of file diff --git a/device-generic-mqtt/VERSION b/device-generic-mqtt/VERSION new file mode 100644 index 0000000..f0bb29e --- /dev/null +++ b/device-generic-mqtt/VERSION @@ -0,0 +1 @@ +1.3.0 diff --git a/device-generic-mqtt/bin/edgex-launch.sh b/device-generic-mqtt/bin/edgex-launch.sh new file mode 100644 index 0000000..73b3482 --- /dev/null +++ b/device-generic-mqtt/bin/edgex-launch.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# +# Copyright (c) 2018 +# Mainflux +# +# SPDX-License-Identifier: Apache-2.0 +# + +### +# Launches all EdgeX Go binaries (must be previously built). +# +# Expects that Consul and MongoDB are already installed and running. +# +### + +DIR=$PWD +CMD=../cmd + +function cleanup { + pkill edgex-device-generic-mqtt +} + +cd $CMD +exec -a edgex-device-generic-mqtt ./device-generic-mqtt & +cd $DIR + + +trap cleanup EXIT + +while : ; do sleep 1 ; done \ No newline at end of file diff --git a/device-generic-mqtt/cmd/main.go b/device-generic-mqtt/cmd/main.go new file mode 100644 index 0000000..799ee83 --- /dev/null +++ b/device-generic-mqtt/cmd/main.go @@ -0,0 +1,22 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +// Copyright (C) 2018 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + device_generic_mqtt "github.com/TIBCOSoftware/labs-air/edgexfoundry/device-generic-mqtt" + "github.com/TIBCOSoftware/labs-air/edgexfoundry/device-generic-mqtt/internal/driver" + "github.com/edgexfoundry/device-sdk-go/pkg/startup" +) + +const ( + serviceName string = "device-generic-mqtt" +) + +func main() { + sd := driver.NewProtocolDriver() + startup.Bootstrap(serviceName, device_generic_mqtt.Version, sd) +} diff --git a/device-generic-mqtt/cmd/res/Generic-MQTT-Device.yaml b/device-generic-mqtt/cmd/res/Generic-MQTT-Device.yaml new file mode 100644 index 0000000..6fc6899 --- /dev/null +++ b/device-generic-mqtt/cmd/res/Generic-MQTT-Device.yaml @@ -0,0 +1,77 @@ +name: "Generic-MQTT-Device" +manufacturer: "Acne" +model: "Acne 12334" +labels: +- "mqtt" +description: "Generic device profile" + + +deviceResources: + - name: "image_reading" + description: "Image from a camera" + attributes: + { Pin_Num: "A0", Interface: "AIO", Type: "IN", Visualization: "Image" } + properties: + value: + { type: "Binary", readWrite: "R", mediaType: "image/jpeg" } + units: + { type: "Binary", readWrite: "R", defaultValue: "Image" } + - name: "str_reading" + description: "sensor string reading" + properties: + value: + { type: "String", readWrite: "R" } + units: + { type: "String", readWrite: "R", defaultValue: "" } + - name: "bool_reading" + description: "sensor bool reading" + properties: + value: + { type: "Bool", readWrite: "R" } + units: + { type: "Bool", readWrite: "R" } + - name: "int16_reading" + description: "sensor integer reading" + properties: + value: + { type: "Int16", readWrite: "R", minimum: "0", maximum: "100", defaultValue: "0"} + units: + { type: "String", readWrite: "R", defaultValue: "NA" } + - name: "float32_reading" + description: "sensor float32 reading" + properties: + value: + { type: "Float32", size: "4", readWrite: "R", floatEncoding: "eNotation", defaultValue: "0.00", minimum: "0.0", maximum: "100.0" } + units: + { type: "String", readWrite: "R", defaultValue: "" } +deviceCommands: + - name: testImageReading + get: + - { index: "1", operation: "get", deviceResource: "image_reading"} + - name: testStrReading + get: + - { index: "1", operation: "get", deviceResource: "str_reading"} + - name: testBoolReading + get: + - { index: "1", operation: "get", deviceResource: "bool_reading"} + - name: testInt16Reading + get: + - { index: "1", operation: "get", deviceResource: "int16_reading"} + - name: testFloat32Reading + get: + - { index: "1", operation: "get", deviceResource: "float32_reading"} + +coreCommands: + - name: testFloat32Reading + get: + path: "/api/v1/device/{deviceId}/testFloat32Reading" + responses: + - code: "200" + description: "get the random float32 value" + expectedValues: ["float32_reading"] + - code: "500" + description: "internal server error" + expectedValues: [] + + + diff --git a/device-generic-mqtt/cmd/res/configuration.toml b/device-generic-mqtt/cmd/res/configuration.toml new file mode 100644 index 0000000..f334724 --- /dev/null +++ b/device-generic-mqtt/cmd/res/configuration.toml @@ -0,0 +1,87 @@ +[Service] +Host = "host.docker.internal" +Port = 49560 +ConnectRetries = 20 +Labels = [] +OpenMsg = "generic MQTT device started" +Timeout = 5000 +EnableAsyncReadings = true +AsyncBufferSize = 16 + +[Registry] +Host = "localhost" +Port = 8500 +Type = "consul" +CheckInterval = "10s" +FailLimit = 3 +FailWaitTime = 10 + +[Clients] + [Clients.Data] + Name = "edgex-core-data" + Protocol = "http" + Host = "localhost" + Port = 48080 + Timeout = 5000 + + [Clients.Metadata] + Name = "edgex-core-metadata" + Protocol = "http" + Host = "localhost" + Port = 48081 + Timeout = 5000 + +[Device] + DataTransform = true + InitCmd = "" + InitCmdArgs = "" + MaxCmdOps = 128 + MaxCmdValueLen = 256 + RemoveCmd = "" + RemoveCmdArgs = "" + ProfilesDir = "./res" + +[Logging] +EnableRemote = false +File = "./device-generic-mqtt.log" + +[Writable] +LogLevel = 'INFO' + +# Pre-define Devices +[[DeviceList]] + Name = "MQTTDevice" + Profile = "Generic-MQTT-Device" + Description = "Generic MQTT Device" + Labels = [ "industrial", "mqtt" ] + [DeviceList.Protocols] + [DeviceList.Protocols.mqtt] + Schema = "tcp" + Host = "localhost" + Port = "1883" + ClientId = "EdgexDepthAICommandPublisher" + User = "mqtt_admin" + Password = "mqtt_admin" + Topic = "CommandTopic_STc47f5194b9d6" + + +# Driver configs +[Driver] +IncomingSchema = "tcp" +IncomingHost = "" +IncomingPort = "443" +IncomingUser = "mqtt_admin" +IncomingPassword = "mqtt_admin" +IncomingQos = "2" +IncomingKeepAlive = "36000" +IncomingClientId = "EdgexDepthAIDataSubscriber" +IncomingTopic = "/generic/event" +ResponseSchema = "tcp" +ResponseHost = "" +ResponsePort = "443" +ResponseUser = "mqtt_admin" +ResponsePassword = "mqtt_admin" +ResponseQos = "0" +ResponseKeepAlive = "36000" +ResponseClientId = "EdgexDepthAICommandResponseSubscriber" +ResponseTopic = "ResponseTopic" diff --git a/device-generic-mqtt/go.mod b/device-generic-mqtt/go.mod new file mode 100644 index 0000000..b9d3f5e --- /dev/null +++ b/device-generic-mqtt/go.mod @@ -0,0 +1,13 @@ +module github.com/TIBCOSoftware/labs-air/edgexfoundry/device-generic-mqtt + +go 1.15 + +require ( + github.com/eclipse/paho.mqtt.golang v1.2.0 + github.com/edgexfoundry/device-sdk-go v1.3.0 + github.com/edgexfoundry/go-mod-core-contracts v0.1.112 + github.com/kr/pretty v0.1.0 // indirect + github.com/spf13/cast v1.3.0 + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect + gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce +) diff --git a/device-generic-mqtt/internal/driver/config.go b/device-generic-mqtt/internal/driver/config.go new file mode 100644 index 0000000..3df4323 --- /dev/null +++ b/device-generic-mqtt/internal/driver/config.go @@ -0,0 +1,101 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +// Copyright (C) 2019 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package driver + +import ( + "fmt" + "reflect" + "strconv" + + "github.com/edgexfoundry/go-mod-core-contracts/models" +) + +type ConnectionInfo struct { + Schema string + Host string + Port string + User string + Password string + ClientId string + Topic string +} + +type configuration struct { + IncomingSchema string + IncomingHost string + IncomingPort int + IncomingUser string + IncomingPassword string + IncomingQos int + IncomingKeepAlive int + IncomingClientId string + IncomingTopic string + + ResponseSchema string + ResponseHost string + ResponsePort int + ResponseUser string + ResponsePassword string + ResponseQos int + ResponseKeepAlive int + ResponseClientId string + ResponseTopic string +} + +// CreateDriverConfig use to load driver config for incoming listener and response listener +func CreateDriverConfig(configMap map[string]string) (*configuration, error) { + config := new(configuration) + err := load(configMap, config) + if err != nil { + return config, err + } + return config, nil +} + +// CreateConnectionInfo use to load MQTT connectionInfo for read and write command +func CreateConnectionInfo(protocols map[string]models.ProtocolProperties) (*ConnectionInfo, error) { + info := new(ConnectionInfo) + protocol, ok := protocols[Protocol] + if !ok { + return info, fmt.Errorf("unable to load config, '%s' not exist", Protocol) + } + + err := load(protocol, info) + if err != nil { + return info, err + } + return info, nil +} + +// load by reflect to check map key and then fetch the value +func load(config map[string]string, des interface{}) error { + errorMessage := "unable to load config, '%s' not exist" + val := reflect.ValueOf(des).Elem() + for i := 0; i < val.NumField(); i++ { + typeField := val.Type().Field(i) + valueField := val.Field(i) + + val, ok := config[typeField.Name] + if !ok { + return fmt.Errorf(errorMessage, typeField.Name) + } + + switch valueField.Kind() { + case reflect.Int: + intVal, err := strconv.Atoi(val) + if err != nil { + return err + } + valueField.SetInt(int64(intVal)) + case reflect.String: + valueField.SetString(val) + default: + return fmt.Errorf("none supported value type %v ,%v", valueField.Kind(), typeField.Name) + } + } + return nil +} diff --git a/device-generic-mqtt/internal/driver/config_test.go b/device-generic-mqtt/internal/driver/config_test.go new file mode 100644 index 0000000..dcdfacb --- /dev/null +++ b/device-generic-mqtt/internal/driver/config_test.go @@ -0,0 +1,94 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +// Copyright (C) 2019 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package driver + +import ( + "strings" + "testing" + + "github.com/edgexfoundry/go-mod-core-contracts/models" +) + +func TestCreateConnectionInfo(t *testing.T) { + schema := "tcp" + host := "0.0.0.0" + port := "1883" + user := "admin" + password := "password" + clientId := "CommandPublisher" + topic := "CommandTopic" + protocols := map[string]models.ProtocolProperties{ + Protocol: { + Schema: schema, + Host: host, + Port: port, + User: user, + Password: password, + ClientId: clientId, + Topic: topic, + }, + } + + connectionInfo, err := CreateConnectionInfo(protocols) + + if err != nil { + t.Fatalf("Fail to create connectionIfo. Error: %v", err) + } + if connectionInfo.Schema != schema || connectionInfo.Host != host || connectionInfo.Port != port || + connectionInfo.User != user || connectionInfo.Password != password || connectionInfo.ClientId != clientId || + connectionInfo.Topic != topic { + t.Fatalf("Unexpect test result. %v should match to %v ", connectionInfo, protocols) + } +} + +func TestCreateConnectionInfo_fail(t *testing.T) { + protocols := map[string]models.ProtocolProperties{ + Protocol: {}, + } + + _, err := CreateConnectionInfo(protocols) + if err == nil || !strings.Contains(err.Error(), "unable to load config") { + t.Fatalf("Unexpect test result, config should be fail to load") + } +} + +func TestCreateDriverConfig(t *testing.T) { + configs := map[string]string{ + IncomingSchema: "tcp", IncomingHost: "0.0.0.0", IncomingPort: "1883", + IncomingUser: "admin", IncomingPassword: "public", IncomingQos: "0", + IncomingKeepAlive: "3600", IncomingClientId: "IncomingDataSubscriber", IncomingTopic: "DataTopic", + + ResponseSchema: "tcp", ResponseHost: "0.0.0.0", ResponsePort: "1883", + ResponseUser: "admin", ResponsePassword: "public", ResponseQos: "0", + ResponseKeepAlive: "3600", ResponseClientId: "CommandResponseSubscriber", ResponseTopic: "ResponseTopic", + } + diverConfig, err := CreateDriverConfig(configs) + if err != nil { + t.Fatalf("Fail to load config, %v", err) + } + if diverConfig.IncomingSchema != configs[IncomingSchema] || diverConfig.IncomingHost != configs[IncomingHost] || + diverConfig.IncomingPort != 1883 || diverConfig.IncomingUser != configs[IncomingUser] || + diverConfig.IncomingPassword != configs[IncomingPassword] || diverConfig.IncomingQos != 0 || + diverConfig.IncomingKeepAlive != 3600 || diverConfig.IncomingClientId != configs[IncomingClientId] || + diverConfig.IncomingTopic != configs[IncomingTopic] || + diverConfig.ResponseSchema != configs[ResponseSchema] || diverConfig.ResponseHost != configs[ResponseHost] || + diverConfig.ResponsePort != 1883 || diverConfig.ResponseUser != configs[ResponseUser] || + diverConfig.ResponsePassword != configs[ResponsePassword] || diverConfig.ResponseQos != 0 || + diverConfig.ResponseKeepAlive != 3600 || diverConfig.ResponseClientId != configs[ResponseClientId] || + diverConfig.ResponseTopic != configs[ResponseTopic] { + + t.Fatalf("Unexpect test result, driver config doesn't correct load") + } +} + +func TestCreateDriverConfig_fail(t *testing.T) { + configs := map[string]string{} + _, err := CreateDriverConfig(configs) + if err == nil || !strings.Contains(err.Error(), "unable to load config") { + t.Fatalf("Unexpect test result, config should be fail to load") + } +} diff --git a/device-generic-mqtt/internal/driver/driver.go b/device-generic-mqtt/internal/driver/driver.go new file mode 100644 index 0000000..c45b41e --- /dev/null +++ b/device-generic-mqtt/internal/driver/driver.go @@ -0,0 +1,486 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +// Copyright (C) 2019 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package driver + +import ( + "encoding/json" + "fmt" + "net/url" + "strings" + "sync" + "time" + + MQTT "github.com/eclipse/paho.mqtt.golang" + sdkModel "github.com/edgexfoundry/device-sdk-go/pkg/models" + device "github.com/edgexfoundry/device-sdk-go/pkg/service" + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" + "github.com/edgexfoundry/go-mod-core-contracts/models" + contract "github.com/edgexfoundry/go-mod-core-contracts/models" + "github.com/spf13/cast" + "gopkg.in/mgo.v2/bson" +) + +var once sync.Once +var driver *Driver + +type Config struct { + Incoming connectionInfo + Response connectionInfo +} + +type connectionInfo struct { + MqttProtocol string + MqttBroker string + MqttBrokerPort int + MqttClientID string + MqttTopic string + MqttQos int + MqttUser string + MqttPassword string + MqttKeepAlive int +} + +type Driver struct { + Logger logger.LoggingClient + AsyncCh chan<- *sdkModel.AsyncValues + CommandResponses sync.Map + Config *configuration +} + +func NewProtocolDriver() sdkModel.ProtocolDriver { + once.Do(func() { + driver = new(Driver) + }) + return driver +} + +func (d *Driver) Initialize(lc logger.LoggingClient, asyncCh chan<- *sdkModel.AsyncValues, deviceCh chan<- []sdkModel.DiscoveredDevice) error { + d.Logger = lc + d.AsyncCh = asyncCh + + config, err := CreateDriverConfig(device.DriverConfigs()) + if err != nil { + panic(fmt.Errorf("read MQTT driver configuration failed: %v", err)) + } + d.Config = config + + // go func() { + // lc.Info("Initialize startCommandResponseListening") + // err := startCommandResponseListening() + // if err != nil { + // panic(fmt.Errorf("start command response Listener failed, please check MQTT broker settings are correct, %v", err)) + // } + // }() + + go func() { + lc.Info("Initialize startIncomingListening") + err := startIncomingListening() + if err != nil { + panic(fmt.Errorf("start incoming data Listener failed, please check MQTT broker settings are correct, %v", err)) + } + }() + + return nil +} + +func (d *Driver) DisconnectDevice(deviceName string, protocols map[string]models.ProtocolProperties) error { + d.Logger.Warn("Driver's DisconnectDevice function didn't implement") + return nil +} + +func (d *Driver) HandleReadCommands(deviceName string, protocols map[string]models.ProtocolProperties, reqs []sdkModel.CommandRequest) ([]*sdkModel.CommandValue, error) { + var responses = make([]*sdkModel.CommandValue, len(reqs)) + var err error + + // create device client and open connection + connectionInfo, err := CreateConnectionInfo(protocols) + if err != nil { + return responses, err + } + + uri := &url.URL{ + Scheme: strings.ToLower(connectionInfo.Schema), + Host: fmt.Sprintf("%s:%s", connectionInfo.Host, connectionInfo.Port), + User: url.UserPassword(connectionInfo.User, connectionInfo.Password), + } + + client, err := createClient(connectionInfo.ClientId, uri, 30) + if err != nil { + return responses, err + } + + defer func() { + if client.IsConnected() { + client.Disconnect(5000) + } + }() + + for i, req := range reqs { + res, err := d.handleReadCommandRequest(client, req, connectionInfo.Topic) + if err != nil { + driver.Logger.Info(fmt.Sprintf("Handle read commands failed: %v", err)) + return responses, err + } + + responses[i] = res + } + + return responses, err +} + +func (d *Driver) handleReadCommandRequest(deviceClient MQTT.Client, req sdkModel.CommandRequest, topic string) (*sdkModel.CommandValue, error) { + var result = &sdkModel.CommandValue{} + var err error + var qos = byte(0) + var retained = false + + var method = "get" + var cmdUuid = bson.NewObjectId().Hex() + var cmd = req.DeviceResourceName + + data := make(map[string]interface{}) + data["uuid"] = cmdUuid + data["method"] = method + data["cmd"] = cmd + + jsonData, err := json.Marshal(data) + if err != nil { + return result, err + } + + deviceClient.Publish(topic, qos, retained, jsonData) + + driver.Logger.Info(fmt.Sprintf("Publish command: %v", string(jsonData))) + + // fetch response from MQTT broker after publish command successful + cmdResponse, ok := d.fetchCommandResponse(cmdUuid) + if !ok { + err = fmt.Errorf("can not fetch command response: method=%v cmd=%v", method, cmd) + return result, err + } + + driver.Logger.Info(fmt.Sprintf("Parse command response: %v", cmdResponse)) + + var response map[string]interface{} + json.Unmarshal([]byte(cmdResponse), &response) + reading, ok := response[cmd] + if !ok { + err = fmt.Errorf("can not fetch command reading: method=%v cmd=%v", method, cmd) + return result, err + } + + result, err = newResult(req, reading) + if err != nil { + return result, err + } else { + driver.Logger.Info(fmt.Sprintf("Get command finished: %v", result)) + } + + return result, err +} + +func (d *Driver) HandleWriteCommands(deviceName string, protocols map[string]models.ProtocolProperties, reqs []sdkModel.CommandRequest, params []*sdkModel.CommandValue) error { + var err error + + // create device client and open connection + connectionInfo, err := CreateConnectionInfo(protocols) + if err != nil { + return err + } + + uri := &url.URL{ + Scheme: strings.ToLower(connectionInfo.Schema), + Host: fmt.Sprintf("%s:%s", connectionInfo.Host, connectionInfo.Port), + User: url.UserPassword(connectionInfo.User, connectionInfo.Password), + } + + client, err := createClient(connectionInfo.ClientId, uri, 30) + if err != nil { + return err + } + defer func() { + if client.IsConnected() { + client.Disconnect(5000) + } + }() + + for i, req := range reqs { + err = d.handleWriteCommandRequest(client, req, connectionInfo.Topic, params[i]) + if err != nil { + driver.Logger.Info(fmt.Sprintf("Handle write commands failed: %v", err)) + return err + } + } + + return err +} + +func (d *Driver) handleWriteCommandRequest(deviceClient MQTT.Client, req sdkModel.CommandRequest, topic string, param *sdkModel.CommandValue) error { + var err error + var qos = byte(0) + var retained = false + + var method = "set" + var cmdUuid = bson.NewObjectId().Hex() + var cmd = req.DeviceResourceName + + data := make(map[string]interface{}) + data["uuid"] = cmdUuid + data["method"] = method + data["cmd"] = cmd + + commandValue, err := newCommandValue(req.Type, param) + if err != nil { + return err + } else { + data[cmd] = commandValue + } + + jsonData, err := json.Marshal(data) + if err != nil { + return err + } + + deviceClient.Publish(topic, qos, retained, jsonData) + + driver.Logger.Info(fmt.Sprintf("Publish command: %v", string(jsonData))) + + //wait and fetch response from CommandResponses map + var cmdResponse interface{} + var ok bool + for i := 0; i < 5; i++ { + cmdResponse, ok = d.CommandResponses.Load(cmdUuid) + if ok { + d.CommandResponses.Delete(cmdUuid) + break + } else { + time.Sleep(time.Second * time.Duration(1)) + } + } + + if !ok { + err = fmt.Errorf("can not fetch command response: method=%v cmd=%v", method, cmd) + return err + } + + driver.Logger.Info(fmt.Sprintf("Put command finished: %v", cmdResponse)) + + return nil +} + +// Stop the protocol-specific DS code to shutdown gracefully, or +// if the force parameter is 'true', immediately. The driver is responsible +// for closing any in-use channels, including the channel used to send async +// readings (if supported). +func (d *Driver) Stop(force bool) error { + // Then Logging Client might not be initialized + if d.Logger != nil { + d.Logger.Debug(fmt.Sprintf("Driver.Stop called: force=%v", force)) + } + return nil +} + +// AddDevice is a callback function that is invoked +// when a new Device associated with this Device Service is added +func (d *Driver) AddDevice(deviceName string, protocols map[string]contract.ProtocolProperties, adminState contract.AdminState) error { + d.Logger.Debug(fmt.Sprintf("a new Device is added: %s", deviceName)) + return nil +} + +// UpdateDevice is a callback function that is invoked +// when a Device associated with this Device Service is updated +func (d *Driver) UpdateDevice(deviceName string, protocols map[string]contract.ProtocolProperties, adminState contract.AdminState) error { + d.Logger.Debug(fmt.Sprintf("Device %s is updated", deviceName)) + return nil +} + +// RemoveDevice is a callback function that is invoked +// when a Device associated with this Device Service is removed +func (d *Driver) RemoveDevice(deviceName string, protocols map[string]contract.ProtocolProperties) error { + d.Logger.Debug(fmt.Sprintf("Device %s is removed", deviceName)) + return nil +} + +// Create a MQTT client +func createClient(clientID string, uri *url.URL, keepAlive int) (MQTT.Client, error) { + driver.Logger.Info(fmt.Sprintf("Create MQTT client and connection: uri=%v clientID=%v ", uri.String(), clientID)) + + opts := MQTT.NewClientOptions() + opts.AddBroker(fmt.Sprintf("%s://%s", uri.Scheme, uri.Host)) + opts.SetClientID(clientID) + opts.SetUsername(uri.User.Username()) + password, _ := uri.User.Password() + opts.SetPassword(password) + // opts.SetKeepAlive(time.Second * time.Duration(keepAlive)) + opts.SetConnectionLostHandler(func(client MQTT.Client, e error) { + driver.Logger.Warn(fmt.Sprintf("Connection lost : %v", e)) + token := client.Connect() + if token.Wait() && token.Error() != nil { + driver.Logger.Warn(fmt.Sprintf("Reconnection failed : %v", token.Error())) + } else { + driver.Logger.Warn(fmt.Sprintf("Reconnection sucessful")) + } + }) + + client := MQTT.NewClient(opts) + token := client.Connect() + if token.Wait() && token.Error() != nil { + return client, token.Error() + } + + return client, nil +} + +func newResult(req sdkModel.CommandRequest, reading interface{}) (*sdkModel.CommandValue, error) { + var result = &sdkModel.CommandValue{} + var err error + var resTime = time.Now().UnixNano() / int64(time.Millisecond) + castError := "fail to parse %v reading, %v" + + if !checkValueInRange(req.Type, reading) { + err = fmt.Errorf("parse reading fail. Reading %v is out of the value type(%v)'s range", reading, req.Type) + driver.Logger.Error(err.Error()) + return result, err + } + + switch req.Type { + case sdkModel.Binary: + val, ok := reading.([]byte) + if !ok { + return nil, fmt.Errorf(castError, req.DeviceResourceName, "not []byte") + } + result, err = sdkModel.NewBinaryValue(req.DeviceResourceName, resTime, val) + case sdkModel.Bool: + val, err := cast.ToBoolE(reading) + if err != nil { + return nil, fmt.Errorf(castError, req.DeviceResourceName, err) + } + result, err = sdkModel.NewBoolValue(req.DeviceResourceName, resTime, val) + case sdkModel.String: + val, err := cast.ToStringE(reading) + if err != nil { + return nil, fmt.Errorf(castError, req.DeviceResourceName, err) + } + result = sdkModel.NewStringValue(req.DeviceResourceName, resTime, val) + case sdkModel.Uint8: + val, err := cast.ToUint8E(reading) + if err != nil { + return nil, fmt.Errorf(castError, req.DeviceResourceName, err) + } + result, err = sdkModel.NewUint8Value(req.DeviceResourceName, resTime, val) + case sdkModel.Uint16: + val, err := cast.ToUint16E(reading) + if err != nil { + return nil, fmt.Errorf(castError, req.DeviceResourceName, err) + } + result, err = sdkModel.NewUint16Value(req.DeviceResourceName, resTime, val) + case sdkModel.Uint32: + val, err := cast.ToUint32E(reading) + if err != nil { + return nil, fmt.Errorf(castError, req.DeviceResourceName, err) + } + result, err = sdkModel.NewUint32Value(req.DeviceResourceName, resTime, val) + case sdkModel.Uint64: + val, err := cast.ToUint64E(reading) + if err != nil { + return nil, fmt.Errorf(castError, req.DeviceResourceName, err) + } + result, err = sdkModel.NewUint64Value(req.DeviceResourceName, resTime, val) + case sdkModel.Int8: + val, err := cast.ToInt8E(reading) + if err != nil { + return nil, fmt.Errorf(castError, req.DeviceResourceName, err) + } + result, err = sdkModel.NewInt8Value(req.DeviceResourceName, resTime, val) + case sdkModel.Int16: + val, err := cast.ToInt16E(reading) + if err != nil { + return nil, fmt.Errorf(castError, req.DeviceResourceName, err) + } + result, err = sdkModel.NewInt16Value(req.DeviceResourceName, resTime, val) + case sdkModel.Int32: + val, err := cast.ToInt32E(reading) + if err != nil { + return nil, fmt.Errorf(castError, req.DeviceResourceName, err) + } + result, err = sdkModel.NewInt32Value(req.DeviceResourceName, resTime, val) + case sdkModel.Int64: + val, err := cast.ToInt64E(reading) + if err != nil { + return nil, fmt.Errorf(castError, req.DeviceResourceName, err) + } + result, err = sdkModel.NewInt64Value(req.DeviceResourceName, resTime, val) + case sdkModel.Float32: + val, err := cast.ToFloat32E(reading) + if err != nil { + return nil, fmt.Errorf(castError, req.DeviceResourceName, err) + } + result, err = sdkModel.NewFloat32Value(req.DeviceResourceName, resTime, val) + case sdkModel.Float64: + val, err := cast.ToFloat64E(reading) + if err != nil { + return nil, fmt.Errorf(castError, req.DeviceResourceName, err) + } + result, err = sdkModel.NewFloat64Value(req.DeviceResourceName, resTime, val) + default: + err = fmt.Errorf("return result fail, none supported value type: %v", req.Type) + } + + return result, err +} + +func newCommandValue(valueType sdkModel.ValueType, param *sdkModel.CommandValue) (interface{}, error) { + var commandValue interface{} + var err error + switch valueType { + case sdkModel.Bool: + commandValue, err = param.BoolValue() + case sdkModel.String: + commandValue, err = param.StringValue() + case sdkModel.Uint8: + commandValue, err = param.Uint8Value() + case sdkModel.Uint16: + commandValue, err = param.Uint16Value() + case sdkModel.Uint32: + commandValue, err = param.Uint32Value() + case sdkModel.Uint64: + commandValue, err = param.Uint64Value() + case sdkModel.Int8: + commandValue, err = param.Int8Value() + case sdkModel.Int16: + commandValue, err = param.Int16Value() + case sdkModel.Int32: + commandValue, err = param.Int32Value() + case sdkModel.Int64: + commandValue, err = param.Int64Value() + case sdkModel.Float32: + commandValue, err = param.Float32Value() + case sdkModel.Float64: + commandValue, err = param.Float64Value() + default: + err = fmt.Errorf("fail to convert param, none supported value type: %v", valueType) + } + + return commandValue, err +} + +// fetchCommandResponse use to wait and fetch response from CommandResponses map +func (d *Driver) fetchCommandResponse(cmdUuid string) (string, bool) { + var cmdResponse interface{} + var ok bool + for i := 0; i < 5; i++ { + cmdResponse, ok = d.CommandResponses.Load(cmdUuid) + if ok { + d.CommandResponses.Delete(cmdUuid) + break + } else { + time.Sleep(time.Second * time.Duration(1)) + } + } + + return fmt.Sprintf("%v", cmdResponse), ok +} diff --git a/device-generic-mqtt/internal/driver/driver_test.go b/device-generic-mqtt/internal/driver/driver_test.go new file mode 100644 index 0000000..f10a9b4 --- /dev/null +++ b/device-generic-mqtt/internal/driver/driver_test.go @@ -0,0 +1,595 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +// Copyright (C) 2019 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package driver + +import ( + "fmt" + "strings" + "testing" + + sdkModel "github.com/edgexfoundry/device-sdk-go/pkg/models" + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" +) + +func init() { + driver = new(Driver) + driver.Logger = logger.NewClient("test", false, "", "DEBUG") +} + +func TestNewResult_bool(t *testing.T) { + var reading interface{} = true + req := sdkModel.CommandRequest{ + DeviceResourceName: "light", + Type: sdkModel.Bool, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.BoolValue() + if val != true || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_uint8(t *testing.T) { + var reading interface{} = uint8(123) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Uint8, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Uint8Value() + if val != uint8(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_int8(t *testing.T) { + var reading interface{} = int8(123) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Int8, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Int8Value() + if val != int8(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResultFailed_int8(t *testing.T) { + var reading interface{} = int16(256) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Int8, + } + + _, err := newResult(req, reading) + if err == nil || !strings.Contains(err.Error(), "Reading 256 is out of the value type(6)'s range") { + t.Errorf("Convert new result should be failed") + } +} + +func TestNewResult_uint16(t *testing.T) { + var reading interface{} = uint16(123) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Uint16, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Uint16Value() + if val != uint16(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_int16(t *testing.T) { + var reading interface{} = int16(123) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Int16, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Int16Value() + if val != int16(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_uint32(t *testing.T) { + var reading interface{} = uint32(123) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Uint32, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Uint32Value() + if val != uint32(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_int32(t *testing.T) { + var reading interface{} = int32(123) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Int32, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Int32Value() + if val != int32(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_uint64(t *testing.T) { + var reading interface{} = uint64(123) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Uint64, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Uint64Value() + if val != uint64(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_int64(t *testing.T) { + var reading interface{} = int64(123) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Int64, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Int64Value() + if val != int64(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_float32(t *testing.T) { + var reading interface{} = float32(123) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Float32, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Float32Value() + if val != float32(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_float64(t *testing.T) { + var reading interface{} = float64(0.123) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Float64, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Float64Value() + if val != float64(0.123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_float64ToInt8(t *testing.T) { + var reading interface{} = float64(123.11) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Int8, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Int8Value() + if val != int8(reading.(float64)) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_float64ToInt16(t *testing.T) { + var reading interface{} = float64(123.11) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Int16, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Int16Value() + if val != int16(reading.(float64)) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_float64ToInt32(t *testing.T) { + var reading interface{} = float64(123.11) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Int32, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Int32Value() + if val != int32(reading.(float64)) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_float64ToInt64(t *testing.T) { + var reading interface{} = float64(123.11) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Int64, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Int64Value() + if val != int64(reading.(float64)) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_float64ToUint8(t *testing.T) { + var reading interface{} = float64(123.11) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Uint8, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Uint8Value() + if val != uint8(reading.(float64)) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_float64ToUint16(t *testing.T) { + var reading interface{} = float64(123.11) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Uint16, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Uint16Value() + if val != uint16(reading.(float64)) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_float64ToUint32(t *testing.T) { + var reading interface{} = float64(123.11) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Uint32, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Uint32Value() + if val != uint32(reading.(float64)) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_float64ToUint64(t *testing.T) { + var reading interface{} = float64(123.11) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Uint64, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Uint64Value() + if val != uint64(reading.(float64)) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_float64ToFloat32(t *testing.T) { + var reading interface{} = float64(123.11) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Float32, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Float32Value() + if val != float32(reading.(float64)) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_float64ToString(t *testing.T) { + var reading interface{} = float64(123.11) + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.String, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.StringValue() + if val != fmt.Sprintf("%v", reading) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_string(t *testing.T) { + var reading interface{} = "test string" + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.String, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.StringValue() + if val != "test string" || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_stringToFloat32(t *testing.T) { + var reading interface{} = "123.0" + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Float32, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Float32Value() + if val != float32(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_stringToFloat64(t *testing.T) { + var reading interface{} = "123.0" + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Float64, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Float64Value() + if val != float64(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_stringToInt64(t *testing.T) { + var reading interface{} = "123" + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Int64, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Int64Value() + if val != int64(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_stringToInt8(t *testing.T) { + var reading interface{} = "123" + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Int8, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Int8Value() + if val != int8(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_stringToUint8(t *testing.T) { + var reading interface{} = "123" + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Uint8, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Uint8Value() + if val != uint8(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_stringToUint32(t *testing.T) { + var reading interface{} = "123" + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Uint32, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Uint32Value() + if val != uint32(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_stringToUint64(t *testing.T) { + var reading interface{} = "123" + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Uint64, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Uint64Value() + if val != uint64(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_stringToBool(t *testing.T) { + var reading interface{} = "true" + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Bool, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.BoolValue() + if val != true || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_numberToUint64(t *testing.T) { + var reading interface{} = 123 + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Uint64, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Uint64Value() + if val != uint64(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_floatNumberToFloat32(t *testing.T) { + var reading interface{} = 123.0 + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.Float32, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.Float32Value() + if val != float32(123) || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} + +func TestNewResult_numberToString(t *testing.T) { + var reading interface{} = 123 + req := sdkModel.CommandRequest{ + DeviceResourceName: "temperature", + Type: sdkModel.String, + } + + cmdVal, err := newResult(req, reading) + if err != nil { + t.Fatalf("Fail to create new ReadCommand result, %v", err) + } + val, err := cmdVal.StringValue() + if val != "123" || err != nil { + t.Errorf("Convert new result(%v) failed, error: %v", val, err) + } +} diff --git a/device-generic-mqtt/internal/driver/incominglistener.go b/device-generic-mqtt/internal/driver/incominglistener.go new file mode 100644 index 0000000..3beb4a3 --- /dev/null +++ b/device-generic-mqtt/internal/driver/incominglistener.go @@ -0,0 +1,153 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +// Copyright (C) 2018-2019 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package driver + +import ( + "encoding/json" + "fmt" + "net/url" + "strings" + "time" + + mqtt "github.com/eclipse/paho.mqtt.golang" + sdkModel "github.com/edgexfoundry/device-sdk-go/pkg/models" + sdk "github.com/edgexfoundry/device-sdk-go/pkg/service" +) + +type DepthAIMsg struct { + PredictNumMasks int `json:"predictNumMasks"` + PredictNumNoMasks int `json:"predictNumNoMasks"` + HasImage bool `json:"hasimage"` + Image []byte `json:"image"` + Ts int64 `json:"ts"` +} + +func startIncomingListening() error { + var scheme = driver.Config.IncomingSchema + var brokerUrl = driver.Config.IncomingHost + var brokerPort = driver.Config.IncomingPort + var username = driver.Config.IncomingUser + var password = driver.Config.IncomingPassword + var mqttClientId = driver.Config.IncomingClientId + var qos = byte(driver.Config.IncomingQos) + var keepAlive = driver.Config.IncomingKeepAlive + var topic = driver.Config.IncomingTopic + + uri := &url.URL{ + Scheme: strings.ToLower(scheme), + Host: fmt.Sprintf("%s:%d", brokerUrl, brokerPort), + User: url.UserPassword(username, password), + } + + driver.Logger.Info(fmt.Sprintf("startIncomingListening calling createClient for qos: %v and topic: %s", qos, topic)) + + // client, err := createClient(mqttClientId, uri, keepAlive) + // if err != nil { + // return err + // } + + var client mqtt.Client + var err error + for i := 1; i <= 10; i++ { + client, err = createClient(mqttClientId, uri, keepAlive) + if err != nil && i == 10 { + return err + } else if err != nil { + driver.Logger.Error(fmt.Sprintf("Fail to initial conn for incoming data, %v ", err)) + time.Sleep(time.Duration(5) * time.Second) + driver.Logger.Warn("Retry to initial conn for incoming data") + continue + } + driver.Logger.Info("Created client successfully") + break + } + + defer func() { + if client.IsConnected() { + client.Disconnect(5000) + } + }() + + driver.Logger.Info(fmt.Sprintf("Subscribing for qos: %v and topic: %s", qos, topic)) + + token := client.Subscribe(topic, qos, onIncomingDataReceived) + if token.Wait() && token.Error() != nil { + driver.Logger.Info(fmt.Sprintf("[Incoming listener] Stop incoming data listening. Cause:%v", token.Error())) + return token.Error() + } + + driver.Logger.Info("[Incoming listener] Start incoming data listening. ") + select {} +} + +func onIncomingDataReceived(client mqtt.Client, message mqtt.Message) { + driver.Logger.Debug(fmt.Sprintf("[Incoming listener] Incoming reading received: topic=%v msg=%v", message.Topic(), string(message.Payload()))) + + var data map[string]interface{} + json.Unmarshal(message.Payload(), &data) + + driver.Logger.Debug(fmt.Sprintf("Incoming data: %+v", data)) + + if !checkDataWithKey(data, "deviceName") || !checkDataWithKey(data, "resourceName") { + return + } + + deviceName := data["deviceName"].(string) + resourceName := data["resourceName"].(string) + + reading, ok := data[resourceName] + if !ok { + driver.Logger.Warn(fmt.Sprintf("[Incoming listener] Incoming reading ignored. No reading data found : topic=%v msg=%v", message.Topic(), string(message.Payload()))) + return + } + + service := sdk.RunningService() + + deviceObject, ok := service.DeviceResource(deviceName, resourceName, "get") + if !ok { + driver.Logger.Warn(fmt.Sprintf("[Incoming listener] Incoming reading ignored. No DeviceObject found : topic=%v msg=%v", message.Topic(), string(message.Payload()))) + return + } + + req := sdkModel.CommandRequest{ + DeviceResourceName: resourceName, + Type: sdkModel.ParseValueType(deviceObject.Properties.Value.Type), + } + + result, err := newResult(req, reading) + + if err != nil { + driver.Logger.Warn(fmt.Sprintf("[Incoming listener] Incoming reading ignored. topic=%v msg=%v error=%v", message.Topic(), string(message.Payload()), err)) + return + } + + asyncValues := &sdkModel.AsyncValues{ + DeviceName: deviceName, + CommandValues: []*sdkModel.CommandValue{result}, + } + + driver.Logger.Info(fmt.Sprintf("[Incoming listener] Incoming reading received: topic=%v msg=%v", message.Topic(), string(message.Payload()))) + + driver.AsyncCh <- asyncValues + +} + +func checkDataWithKey(data map[string]interface{}, key string) bool { + val, ok := data[key] + if !ok { + driver.Logger.Warn(fmt.Sprintf("[Incoming listener] Incoming reading ignored. No %v found : msg=%v", key, data)) + return false + } + + switch val.(type) { + case string: + return true + default: + driver.Logger.Warn(fmt.Sprintf("[Incoming listener] Incoming reading ignored. %v should be string : msg=%v", key, data)) + return false + } +} diff --git a/device-generic-mqtt/internal/driver/protocolpropertykey.go b/device-generic-mqtt/internal/driver/protocolpropertykey.go new file mode 100644 index 0000000..6f110f1 --- /dev/null +++ b/device-generic-mqtt/internal/driver/protocolpropertykey.go @@ -0,0 +1,40 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +// Copyright (C) 2019 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package driver + +const ( + Protocol = "mqtt" + + Schema = "Schema" + Host = "Host" + Port = "Port" + User = "User" + Password = "Password" + ClientId = "ClientId" + Topic = "Topic" + + // Driver config + IncomingSchema = "IncomingSchema" + IncomingHost = "IncomingHost" + IncomingPort = "IncomingPort" + IncomingUser = "IncomingUser" + IncomingPassword = "IncomingPassword" + IncomingQos = "IncomingQos" + IncomingKeepAlive = "IncomingKeepAlive" + IncomingClientId = "IncomingClientId" + IncomingTopic = "IncomingTopic" + + ResponseSchema = "ResponseSchema" + ResponseHost = "ResponseHost" + ResponsePort = "ResponsePort" + ResponseUser = "ResponseUser" + ResponsePassword = "ResponsePassword" + ResponseQos = "ResponseQos" + ResponseKeepAlive = "ResponseKeepAlive" + ResponseClientId = "ResponseClientId" + ResponseTopic = "ResponseTopic" +) diff --git a/device-generic-mqtt/internal/driver/readingchecker.go b/device-generic-mqtt/internal/driver/readingchecker.go new file mode 100644 index 0000000..14e0706 --- /dev/null +++ b/device-generic-mqtt/internal/driver/readingchecker.go @@ -0,0 +1,105 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +// Copyright (C) 2019 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package driver + +import ( + "math" + + "github.com/edgexfoundry/device-sdk-go/pkg/models" + sdkModel "github.com/edgexfoundry/device-sdk-go/pkg/models" + "github.com/spf13/cast" +) + +// checkValueInRange checks value range is valid +func checkValueInRange(valueType sdkModel.ValueType, reading interface{}) bool { + isValid := false + + if valueType == sdkModel.String || valueType == sdkModel.Bool || valueType == models.Binary { + return true + } + + if valueType == sdkModel.Int8 || valueType == sdkModel.Int16 || + valueType == sdkModel.Int32 || valueType == sdkModel.Int64 { + val := cast.ToInt64(reading) + isValid = checkIntValueRange(valueType, val) + } + + if valueType == sdkModel.Uint8 || valueType == sdkModel.Uint16 || + valueType == sdkModel.Uint32 || valueType == sdkModel.Uint64 { + val := cast.ToUint64(reading) + isValid = checkUintValueRange(valueType, val) + } + + if valueType == sdkModel.Float32 || valueType == sdkModel.Float64 { + val := cast.ToFloat64(reading) + isValid = checkFloatValueRange(valueType, val) + } + + return isValid +} + +func checkUintValueRange(valueType sdkModel.ValueType, val uint64) bool { + var isValid = false + switch valueType { + case sdkModel.Uint8: + if val >= 0 && val <= math.MaxUint8 { + isValid = true + } + case sdkModel.Uint16: + if val >= 0 && val <= math.MaxUint16 { + isValid = true + } + case sdkModel.Uint32: + if val >= 0 && val <= math.MaxUint32 { + isValid = true + } + case sdkModel.Uint64: + maxiMum := uint64(math.MaxUint64) + if val >= 0 && val <= maxiMum { + isValid = true + } + } + return isValid +} + +func checkIntValueRange(valueType sdkModel.ValueType, val int64) bool { + var isValid = false + switch valueType { + case sdkModel.Int8: + if val >= math.MinInt8 && val <= math.MaxInt8 { + isValid = true + } + case sdkModel.Int16: + if val >= math.MinInt16 && val <= math.MaxInt16 { + isValid = true + } + case sdkModel.Int32: + if val >= math.MinInt32 && val <= math.MaxInt32 { + isValid = true + } + case sdkModel.Int64: + if val >= math.MinInt64 && val <= math.MaxInt64 { + isValid = true + } + } + return isValid +} + +func checkFloatValueRange(valueType sdkModel.ValueType, val float64) bool { + var isValid = false + switch valueType { + case sdkModel.Float32: + if math.Abs(val) >= math.SmallestNonzeroFloat32 && math.Abs(val) <= math.MaxFloat32 { + isValid = true + } + case sdkModel.Float64: + if math.Abs(val) >= math.SmallestNonzeroFloat64 && math.Abs(val) <= math.MaxFloat64 { + isValid = true + } + } + return isValid +} diff --git a/device-generic-mqtt/internal/driver/responselistener.go b/device-generic-mqtt/internal/driver/responselistener.go new file mode 100644 index 0000000..7f3280e --- /dev/null +++ b/device-generic-mqtt/internal/driver/responselistener.go @@ -0,0 +1,70 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +// Copyright (C) 2018-2019 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package driver + +import ( + "encoding/json" + "fmt" + "net/url" + "strings" + + mqtt "github.com/eclipse/paho.mqtt.golang" +) + +func startCommandResponseListening() error { + var scheme = driver.Config.ResponseSchema + var brokerUrl = driver.Config.ResponseHost + var brokerPort = driver.Config.ResponsePort + var username = driver.Config.ResponseUser + var password = driver.Config.ResponsePassword + var mqttClientId = driver.Config.ResponseClientId + var qos = byte(driver.Config.ResponseQos) + var keepAlive = driver.Config.ResponseKeepAlive + var topic = driver.Config.ResponseTopic + + uri := &url.URL{ + Scheme: strings.ToLower(scheme), + Host: fmt.Sprintf("%s:%d", brokerUrl, brokerPort), + User: url.UserPassword(username, password), + } + + driver.Logger.Info(fmt.Sprintf("startCommandResponseListening calling createClient for qos: %v and topic: %s", qos, topic)) + + client, err := createClient(mqttClientId, uri, keepAlive) + if err != nil { + return err + } + + defer func() { + if client.IsConnected() { + client.Disconnect(5000) + } + }() + + token := client.Subscribe(topic, qos, onCommandResponseReceived) + if token.Wait() && token.Error() != nil { + driver.Logger.Info(fmt.Sprintf("[Response listener] Stop command response listening. Cause:%v", token.Error())) + return token.Error() + } + + driver.Logger.Info("[Response listener] Start command response listening. ") + select {} +} + +func onCommandResponseReceived(client mqtt.Client, message mqtt.Message) { + var response map[string]interface{} + + json.Unmarshal(message.Payload(), &response) + uuid, ok := response["uuid"].(string) + if ok { + driver.CommandResponses.Store(uuid, string(message.Payload())) + driver.Logger.Info(fmt.Sprintf("[Response listener] Command response received: topic=%v uuid=%v msg=%v", message.Topic(), uuid, string(message.Payload()))) + } else { + driver.Logger.Warn(fmt.Sprintf("[Response listener] Command response ignored. No UUID found in the message: topic=%v msg=%v", message.Topic(), string(message.Payload()))) + } + +} diff --git a/device-generic-mqtt/mock/device.go b/device-generic-mqtt/mock/device.go new file mode 100644 index 0000000..b4d0c8d --- /dev/null +++ b/device-generic-mqtt/mock/device.go @@ -0,0 +1,189 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +// Copyright (C) 2018 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "encoding/json" + "fmt" + "github.com/eclipse/paho.mqtt.golang" + "log" + "math/rand" + "net/url" + "time" +) + +const ( + brokerUrl = "0.0.0.0" + brokerPort = 1883 + username = "admin" + password = "public" +) + +func main() { + go runCommandHandler() + go runDataSender() + select {} +} + +// runCommandHandler use to test receiving commands from the device service and responded back for get/set commands. +// +// Use a REST client to send a command to the service like: +// http://localhost:49982/api/v1/devices/{device id}>/message - use POST on this one with +// {"message":"some text"} in body http://localhost:49982/api/v1/devices//ping - use GET +// http://localhost:49982/api/v1/devices//randnum - use GET +// +// If command micro service is running, the same can be performed through command to device service +// like this http://localhost:48082/api/v1/device//command/ +// +// Requires the Device Service, Command, Core Data, Metadata and Mongo to all be running +func runCommandHandler() { + var mqttClientId = "CommandSubscriber" + var qos = 0 + var topic = "CommandTopic" + + uri := &url.URL{ + Scheme: "tcp", + Host: fmt.Sprintf("%s:%d", brokerUrl, brokerPort), + User: url.UserPassword(username, password), + } + + client, err := createMqttClient(mqttClientId, uri) + defer client.Disconnect(5000) + if err != nil { + fmt.Println(err) + } + + token := client.Subscribe(topic, byte(qos), onCommandReceivedFromBroker) + if token.Wait() && token.Error() != nil { + fmt.Println(token.Error()) + } + + select {} +} + +// runDataSender use to to generate random numbers and send them into the device service as if a sensor +// was sending the data. Requires the Device Service along with Mongo, Core Data, and Metadata to be running +func runDataSender() { + var mqttClientId = "IncomingDataPublisher" + var qos = byte(0) + var topic = "DataTopic" + + uri := &url.URL{ + Scheme: "tcp", + Host: fmt.Sprintf("%s:%d", brokerUrl, brokerPort), + User: url.UserPassword(username, password), + } + + client, err := createMqttClient(mqttClientId, uri) + defer client.Disconnect(5000) + if err != nil { + fmt.Println(err) + } + + var data = make(map[string]interface{}) + data["name"] = "MQTT test device" + data["cmd"] = "randnum" + data["method"] = "get" + + for { + data["randnum"] = rand.Float64() + jsonData, err := json.Marshal(data) + if err != nil { + fmt.Println(err) + } + client.Publish(topic, qos, false, jsonData) + + fmt.Println(fmt.Sprintf("Send response: %v", string(jsonData))) + + time.Sleep(time.Second * time.Duration(30)) + } + +} + +func onCommandReceivedFromBroker(client mqtt.Client, message mqtt.Message) { + var request map[string]interface{} + + json.Unmarshal(message.Payload(), &request) + uuid, ok := request["uuid"] + if ok { + log.Println(fmt.Sprintf("Command response received: topic=%v uuid=%v msg=%v", message.Topic(), uuid, string(message.Payload()))) + + if request["method"] == "set" { + sendTestData(request) + } else { + switch request["cmd"] { + case "ping": + request["ping"] = "pong" + sendTestData(request) + case "randfloat32": + request["randfloat32"] = rand.Float32() + sendTestData(request) + case "randfloat64": + request["randfloat64"] = rand.Float64() + sendTestData(request) + case "message": + request["message"] = "test-message" + sendTestData(request) + } + } + } else { + log.Println(fmt.Sprintf("Command response ignored. No UUID found in the message: topic=%v msg=%v", message.Topic(), string(message.Payload()))) + } +} + +func sendTestData(response map[string]interface{}) { + var mqttClientId = "ResponsePublisher" + var qos = byte(0) + var topic = "ResponseTopic" + + uri := &url.URL{ + Scheme: "tcp", + Host: fmt.Sprintf("%s:%d", brokerUrl, brokerPort), + User: url.UserPassword(username, password), + } + + client, err := createMqttClient(mqttClientId, uri) + defer client.Disconnect(5000) + if err != nil { + fmt.Println(err) + } + + jsonData, err := json.Marshal(response) + if err != nil { + fmt.Println(err) + } + client.Publish(topic, qos, false, jsonData) + + fmt.Println(fmt.Sprintf("Send response: %v", string(jsonData))) +} + +func createMqttClient(clientID string, uri *url.URL) (mqtt.Client, error) { + fmt.Println(fmt.Sprintf("Create MQTT client and connection: uri=%v clientID=%v ", uri.String(), clientID)) + opts := mqtt.NewClientOptions() + opts.AddBroker(fmt.Sprintf("%s://%s", uri.Scheme, uri.Host)) + opts.SetClientID(clientID) + opts.SetUsername(uri.User.Username()) + password, _ := uri.User.Password() + opts.SetPassword(password) + opts.SetConnectionLostHandler(func(client mqtt.Client, e error) { + fmt.Println(fmt.Sprintf("Connection lost : %v", e)) + token := client.Connect() + if token.Wait() && token.Error() != nil { + fmt.Println(fmt.Sprintf("Reconnection failed : %v", e)) + } else { + fmt.Println(fmt.Sprintf("Reconnection sucessful : %v", e)) + } + }) + + client := mqtt.NewClient(opts) + token := client.Connect() + if token.Wait() && token.Error() != nil { + return client, token.Error() + } + + return client, nil +} diff --git a/device-generic-mqtt/snap/hooks/configure b/device-generic-mqtt/snap/hooks/configure new file mode 100644 index 0000000..d847a76 --- /dev/null +++ b/device-generic-mqtt/snap/hooks/configure @@ -0,0 +1,4 @@ +#!/bin/bash -e + +# empty configure hook to allow snap configuration with snap set / snapctl set +# to work diff --git a/device-generic-mqtt/snap/hooks/install b/device-generic-mqtt/snap/hooks/install new file mode 100644 index 0000000..9f67400 --- /dev/null +++ b/device-generic-mqtt/snap/hooks/install @@ -0,0 +1,24 @@ +#!/bin/bash -e + +# get the values of $SNAP_DATA and $SNAP using the current symlink instead of +# the default behavior which has the revision hard-coded, which breaks after +# a refresh +SNAP_DATA_CURRENT=${SNAP_DATA/%$SNAP_REVISION/current} +SNAP_CURRENT=${SNAP/%$SNAP_REVISION/current} + +# install all the config files from $SNAP/config/SERVICE/res/configuration.toml +# into $SNAP_DATA/config +mkdir -p "$SNAP_DATA/config" +if [ ! -f "$SNAP_DATA/config/device-mqtt/res/configuration.toml" ]; then + mkdir -p "$SNAP_DATA/config/device-mqtt/res" + cp "$SNAP/config/device-mqtt/res/configuration.toml" "$SNAP_DATA/config/device-mqtt/res/configuration.toml" + # do replacement of the $SNAP, $SNAP_DATA, $SNAP_COMMON environment variables in the config files + sed -i -e "s@\$SNAP_COMMON@$SNAP_COMMON@g" \ + -e "s@\$SNAP_DATA@$SNAP_DATA_CURRENT@g" \ + -e "s@\$SNAP@$SNAP_CURRENT@g" \ + "$SNAP_DATA/config/device-mqtt/res/configuration.toml" +fi + +# disable device-mqtt initially because it specific requires configuration +# with a device profile that will be specific to each installation +snapctl stop --disable "$SNAP_NAME.device-mqtt" diff --git a/device-generic-mqtt/snap/hooks/pre-refresh b/device-generic-mqtt/snap/hooks/pre-refresh new file mode 100644 index 0000000..c65796e --- /dev/null +++ b/device-generic-mqtt/snap/hooks/pre-refresh @@ -0,0 +1,6 @@ +#!/bin/bash -e + +# save this revision for when we run again in the post-refresh +snapctl set lastrev="$SNAP_REVISION" + +snapctl set release="edinburgh" \ No newline at end of file diff --git a/device-generic-mqtt/snap/local/.gitkeep b/device-generic-mqtt/snap/local/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/device-generic-mqtt/snap/snapcraft.yaml b/device-generic-mqtt/snap/snapcraft.yaml new file mode 100644 index 0000000..4ca5682 --- /dev/null +++ b/device-generic-mqtt/snap/snapcraft.yaml @@ -0,0 +1,93 @@ +name: edgex-device-mqtt +base: core18 +version: "replace-me" +license: Apache-2.0 +version-script: | + echo $(cat VERSION)-$(date +%Y%m%d)+$(git rev-parse --short HEAD) +summary: Connect data MQTT to EdgeX using device-mqtt reference Device Service +title: EdgeX MQTT Device Service +description: | + The official reference EdgeX device-mqtt Device Service built using the + device-sdk-go to interact with MQTT brokers. + Initially the daemon in the snap is disabled - a device profile must be + provisioned externally with core-metadata or provided to device-mqtt inside + "$SNAP_DATA/config/device-mqtt/res" before starting. + +# TODO: add armhf when the project supports this +architectures: + - build-on: amd64 + - build-on: arm64 + +grade: stable +confinement: strict + +# edinburgh release is epoch 1 +epoch: 1 + +apps: + device-mqtt: + adapter: none + command: bin/device-mqtt -confdir $SNAP_DATA/config/device-mqtt -profile res --registry $CONSUL_ADDR + environment: + CONSUL_ADDR: "consul://localhost:8500" + daemon: simple + plugs: [network, network-bind] + +parts: + go: + plugin: nil + source: snap/local + build-packages: [curl] + override-build: | + # use dpkg architecture to figure out our target arch + # note - we specifically don't use arch + case "$(dpkg --print-architecture)" in + amd64) + FILE_NAME=go1.11.9.linux-amd64.tar.gz + FILE_HASH=e88aa3e39104e3ba6a95a4e05629348b4a1ec82791fb3c941a493ca349730608 + ;; + arm64) + FILE_NAME=go1.11.9.linux-arm64.tar.gz + FILE_HASH=892ab6c2510c4caa5905b3b1b6a1d4c6f04e384841fec50881ca2be7e8accf05 + ;; + armhf) + FILE_NAME=go1.11.9.linux-armv6l.tar.gz + FILE_HASH=f0d7b039cae61efdc346669f3459460e3dc03b6c6de528ca107fc53970cba0d1 + ;; + i386) + FILE_NAME=go1.11.9.linux-386.tar.gz + FILE_HASH=0fa4001fcf1ef0644e261bf6dde02fc9f10ae4df6d74fda61fc4d3c3cbef1d79 + ;; + esac + # download the archive, failing on ssl cert problems + curl https://dl.google.com/go/$FILE_NAME -O + echo "$FILE_HASH $FILE_NAME" > sha256 + sha256sum -c sha256 | grep OK + tar -C $SNAPCRAFT_STAGE -xf go*.tar.gz --strip-components=1 + prime: + - "-*" + + device-mqtt: + source: . + plugin: make + build-packages: [git] + after: [go] + override-build: | + cd $SNAPCRAFT_PART_SRC + make build + + install -DT "./cmd/device-mqtt" "$SNAPCRAFT_PART_INSTALL/bin/device-mqtt" + + # FIXME: settings can't be overridden from the cmd-line! + # Override 'LogFile' and 'LoggingRemoteURL' + install -d "$SNAPCRAFT_PART_INSTALL/config/device-mqtt/res/" + + cat "./cmd/res/configuration.toml" | \ + sed -e s:\"./device-mqtt.log\":\'\$SNAP_COMMON/device-mqtt.log\': \ + -e s:'ProfilesDir = \"./res\"':'ProfilesDir = \"\$SNAP_DATA/config/device-mqtt/res\"': > \ + "$SNAPCRAFT_PART_INSTALL/config/device-mqtt/res/configuration.toml" + + install -DT "./cmd/Attribution.txt" \ + "$SNAPCRAFT_PART_INSTALL/usr/share/doc/device-mqtt/Attribution.txt" + install -DT "./LICENSE" \ + "$SNAPCRAFT_PART_INSTALL/usr/share/doc/device-mqtt/LICENSE" diff --git a/device-generic-mqtt/version.go b/device-generic-mqtt/version.go new file mode 100644 index 0000000..4b2047c --- /dev/null +++ b/device-generic-mqtt/version.go @@ -0,0 +1,10 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +// Copyright (C) 2018 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package device_generic_mqtt + +// Global version for device-sdk-go +var Version string = "to be replaced by makefile" diff --git a/device-generic-rest/.dockerignore b/device-generic-rest/.dockerignore new file mode 100644 index 0000000..3547eb8 --- /dev/null +++ b/device-generic-rest/.dockerignore @@ -0,0 +1,17 @@ +node_modules +npm-debug.log +Dockerfile* +docker-compose* +.dockerignore +.git +.gitignore +.env +*/bin +*/obj +README.md +LICENSE +.vscode +.idea/ +go.sum +res/ +!res/docker* \ No newline at end of file diff --git a/device-generic-rest/.gitignore b/device-generic-rest/.gitignore new file mode 100644 index 0000000..f85b286 --- /dev/null +++ b/device-generic-rest/.gitignore @@ -0,0 +1,29 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +*.log + +.idea/ +vendor/ +cmd/device-generic-rest +glide.lock +go.sum + +# snap files +*.snap +*.assert +prime/ +stage/ +parts/ +squashfs-root/ +coverage.out \ No newline at end of file diff --git a/device-generic-rest/Dockerfile b/device-generic-rest/Dockerfile new file mode 100644 index 0000000..be2164c --- /dev/null +++ b/device-generic-rest/Dockerfile @@ -0,0 +1,50 @@ +# +# Copyright (c) 2019 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# This file will work as is for local development. No need to use Dockerfile.build + +#build stage +ARG BASE=golang:1.15-alpine +FROM ${BASE} AS builder + +ARG ALPINE_PKG_BASE="build-base git openssh-client" +ARG ALPINE_PKG_EXTRA="" + +LABEL license='SPDX-License-Identifier: Apache-2.0' \ + copyright='Copyright (c) 2019: Intel' +RUN sed -e 's/dl-cdn[.]alpinelinux.org/nl.alpinelinux.org/g' -i~ /etc/apk/repositories +RUN apk add --no-cache ${ALPINE_PKG_BASE} ${ALPINE_PKG_EXTRA} + +WORKDIR /device-generic-rest + + +COPY . . +ARG MAKE=build +RUN make $MAKE + +#final stage +FROM scratch + +LABEL license='SPDX-License-Identifier: Apache-2.0' \ + copyright='Copyright (c) 2019: Intel' +LABEL Name=device-generic-rest Version=${VERSION} + +ENV APP_PORT=59995 +EXPOSE $APP_PORT + +COPY --from=builder /device-generic-rest/cmd / + +ENTRYPOINT ["/device-generic-rest","--confdir=/res"] diff --git a/device-generic-rest/LICENSE b/device-generic-rest/LICENSE new file mode 100644 index 0000000..7337dfa --- /dev/null +++ b/device-generic-rest/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 IOTech, Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/device-generic-rest/Makefile b/device-generic-rest/Makefile new file mode 100644 index 0000000..fb40c87 --- /dev/null +++ b/device-generic-rest/Makefile @@ -0,0 +1,35 @@ +.PHONY: build test clean prepare update docker + +GO = CGO_ENABLED=0 GO111MODULE=on go + +MICROSERVICES=cmd/device-generic-rest +.PHONY: $(MICROSERVICES) + +DOCKERS=docker_device_generic_rest +.PHONY: $(DOCKERS) + +VERSION=$(shell cat ./VERSION 2>/dev/null || echo 0.0.0) +GIT_SHA=$(shell git rev-parse HEAD) + +GOFLAGS=-ldflags "-X github.com/TIBCOSoftware/labs-air/edgexfoundry/device-generic-rest.Version=$(VERSION)" + +build: $(MICROSERVICES) + +cmd/device-generic-rest: + $(GO) build $(GOFLAGS) -o $@ ./cmd + +test: + $(GO) test ./... -coverprofile=coverage.out + +clean: + rm -f $(MICROSERVICES) + +docker: $(DOCKERS) + +docker_device_generic_rest: + docker build --no-cache \ + --build-arg http_proxy \ + --build-arg https_proxy \ + --label "git_sha=$(GIT_SHA)" \ + -t tibcosoftware/labs-air-edgex-device-generic-rest:$(VERSION) \ + . diff --git a/device-generic-rest/README.md b/device-generic-rest/README.md new file mode 100644 index 0000000..00c4469 --- /dev/null +++ b/device-generic-rest/README.md @@ -0,0 +1,138 @@ +# device-generic-rest +EdgeX device service for REST protocol + +This device service provides easy way for 3'rd party applications, such as Point of Sale, CV Analytics, etc., to push data into EdgeX via the REST protocol. + +## Runtime Requisite + +- core-data + - Mongo or Redis DB +- core-metadata + +## REST Endpoints + +This device service creates the additional parametrized `REST` endpoint: + +``` +/ap1/vi/device/{deviceName}/{resourceName} +``` + +- `deviceName` refers to the `device` defined in a `device profile` and the `configuration.toml`. +- `resourceName`refers to the `device resource` defined in the `device profile` that `deviceName` references. + +The data posted to this endpoint is type validated and type casted to the type defined by the specified `device resource`. The resulting value is then sent into EdgeX via the Device SDK's `async values` channel. + +## Configuration + +This device service use the standard configuration defined by the **Device SDK**. + +The `DeviceList` configuration is standard except that the `DeviceList.Protocols` can be empty. The following is a sample `DeviceList` that works with the sample device profiles shown below. + +```toml +[[DeviceList]] + Name = "sample-json" + Profile = "sample-json" + Description = "RESTful Device that sends in JSON data" + Labels = [ "rest", "json" ] + [DeviceList.Protocols] + [DeviceList.Protocols.other] +[[DeviceList]] + Name = "sample-numbers" + Profile = "sample-numbers" + Description = "RESTful Device that sends in number data" + Labels = [ "rest", "float", "int" ] + [DeviceList.Protocols] + [DeviceList.Protocols.other] +``` + +## Device Profile + +As with all device services the `device profile` is where the **Device Name**, **Device Resources** and **Device Commands** are define. The parameterized REST endpoint described above references these definitions. Each `Device` has it's own device profile. The following are the two sample device profiles that define the devices referenced in the above sample configuration. + +**sample-json-device.ymal**: + +```yaml +name: "sample-json" +manufacturer: "Intel Corp." +model: "Some 3rd party app sending JSON" +labels: + - "rest" + - "json" +description: "REST Device that sends in Json" + +deviceResources: + - name: json + description: "JSON message containing the details" + properties: + value: + { type: "String", readWrite: "W" , defaultValue: "" } + units: + { type: "String", readWrite: "R", defaultValue: "" } +deviceCommands: + - name: jsonCmd + get: + - { operation: "get", object: "json" } +``` + +**sample-numbers-device.yaml:** + +```yaml +name: "sample-numbers" +manufacturer: "Intel Corp." +model: "Some 3rd party App sending int & floats" +labels: + - "rest" + - "float64" + - "int64" +description: "REST Device that sends in ints and floats" + +deviceResources: + - name: int + description: "Int64 data" + properties: + value: + { type: "Int64", readWrite: "W" , defaultValue: "" } + units: + { type: "String", readWrite: "R", defaultValue: "" } + - name: float + description: "Float64 data" + properties: + value: + { type: "Float64", readWrite: "W" , defaultValue: "" } + units: + { type: "String", readWrite: "R", defaultValue: "" } + +deviceCommands: + - name: intCmd + get: + - { operation: "get", object: "int" } + - name: floatCmd + get: + - { operation: "get", object: "float"} +``` + +> *Note: `coreCommands` section is omitted since this device service does not support Commanding. See below for details.* + +> *Note: `deviceCommands` section only requires the `get` operations.* + +## Commanding + +The current implementation is meant for one-way communication into EdgeX. If future use cases determine that `commanding`, i.e. two-communication, is desirable it can be added then. + +## AutoEvents + +Since `Commanding` is not implemented, specifying `AutoEvents` in the configuration will result in errors. Thus `AutoEvents` should not be specified in the configuration. + +## Installation and Execution + +```bash +make build +make run +``` + +## Build docker image + +```bash +make docker +``` + diff --git a/device-generic-rest/VERSION b/device-generic-rest/VERSION new file mode 100644 index 0000000..f0bb29e --- /dev/null +++ b/device-generic-rest/VERSION @@ -0,0 +1 @@ +1.3.0 diff --git a/device-generic-rest/cmd/main.go b/device-generic-rest/cmd/main.go new file mode 100644 index 0000000..95fb190 --- /dev/null +++ b/device-generic-rest/cmd/main.go @@ -0,0 +1,32 @@ +// +// Copyright (c) 2019 Intel Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package main + +import ( + "github.com/TIBCOSoftware/labs-air/edgexfoundry/device-generic-rest/driver" + "github.com/edgexfoundry/device-sdk-go" + "github.com/edgexfoundry/device-sdk-go/pkg/startup" +) + +const ( + serviceName string = "edgex-generic-rest" +) + +func main() { + sd := driver.RestDriver{} + startup.Bootstrap(serviceName, device.Version, &sd) +} diff --git a/device-generic-rest/cmd/res/Generic-REST-Device.yaml b/device-generic-rest/cmd/res/Generic-REST-Device.yaml new file mode 100644 index 0000000..c978e5f --- /dev/null +++ b/device-generic-rest/cmd/res/Generic-REST-Device.yaml @@ -0,0 +1,75 @@ +name: "Generic-REST-Device" +manufacturer: "Acne" +model: "Acne XXXXXX" +labels: + - "rest" + - "json" +description: "REST Device that sends Json" + +deviceResources: + - name: "image_reading" + description: "Image from a camera" + attributes: + { Pin_Num: "A0", Interface: "AIO", Type: "IN", Visualization: "Image" } + properties: + value: + { type: "Binary", readWrite: "R", mediaType: "image/jpeg" } + units: + { type: "Binary", readWrite: "R", defaultValue: "Image" } + - name: "str_reading" + description: "sensor string reading" + properties: + value: + { type: "String", readWrite: "R" } + units: + { type: "String", readWrite: "R", defaultValue: "" } + - name: "bool_reading" + description: "sensor bool reading" + properties: + value: + { type: "Bool", readWrite: "R" } + units: + { type: "Bool", readWrite: "R" } + - name: "int16_reading" + description: "sensor integer reading" + properties: + value: + { type: "Int16", readWrite: "R", minimum: "0", maximum: "100", defaultValue: "0"} + units: + { type: "String", readWrite: "R", defaultValue: "NA" } + - name: "float32_reading" + description: "sensor float32 reading" + properties: + value: + { type: "Float32", size: "4", readWrite: "R", floatEncoding: "eNotation", defaultValue: "0.00", minimum: "0.0", maximum: "100.0" } + units: + { type: "String", readWrite: "R", defaultValue: "" } + +deviceCommands: + - name: testImageReading + get: + - { index: "1", operation: "get", deviceResource: "image_reading"} + - name: testStrReading + get: + - { index: "1", operation: "get", deviceResource: "str_reading"} + - name: testBoolReading + get: + - { index: "1", operation: "get", deviceResource: "bool_reading"} + - name: testInt16Reading + get: + - { index: "1", operation: "get", deviceResource: "int16_reading"} + - name: testFloat32Reading + get: + - { index: "1", operation: "get", deviceResource: "float32_reading"} + +coreCommands: + - name: testFloat32Reading + get: + path: "/api/v1/device/{deviceId}/testFloat32Reading" + responses: + - code: "200" + description: "get the random float32 value" + expectedValues: ["float32_reading"] + - code: "500" + description: "internal server error" + expectedValues: [] diff --git a/device-generic-rest/cmd/res/configuration.toml b/device-generic-rest/cmd/res/configuration.toml new file mode 100644 index 0000000..7f6bcda --- /dev/null +++ b/device-generic-rest/cmd/res/configuration.toml @@ -0,0 +1,66 @@ +[Writable] +LogLevel = 'DEBUG' + +[Service] +Host = "host.docker.internal" +Port = 49565 +ConnectRetries = 20 +Labels = [] +OpenMsg = "generic REST device started" +Timeout = 5000 +EnableAsyncReadings = true +AsyncBufferSize = 16 + +[Registry] +Host = "localhost" +Port = 8500 +Type = "consul" +CheckInterval = "10s" +FailLimit = 3 +FailWaitTime = 10 + +[Clients] + [Clients.Data] + Protocol = "http" + Host = "localhost" + Port = 48080 + Timeout = 5000 + + [Clients.Metadata] + Protocol = "http" + Host = "localhost" + Port = 48081 + Timeout = 5000 + + [Clients.Logging] + Protocol = "http" + Host = "localhost" + Port = 48061 + +[Device] + DataTransform = true + InitCmd = "" + InitCmdArgs = "" + MaxCmdOps = 128 + MaxCmdValueLen = 256 + RemoveCmd = "" + RemoveCmdArgs = "" + ProfilesDir = "./res" + +# Driver configs +[Driver] + +[Logging] +EnableRemote = false +File = "./device-generic-rest.log" + +# Pre-define Devices +[[DeviceList]] + Name = "RESTDevice" + Profile = "Generic-REST-Device" + Description = "Generic RESTful Device that sends JSON data" + Labels = [ "rest", "json" ] + [DeviceList.Protocols] + [DeviceList.Protocols.other] + + diff --git a/device-generic-rest/driver/restdriver.go b/device-generic-rest/driver/restdriver.go new file mode 100644 index 0000000..49a7061 --- /dev/null +++ b/device-generic-rest/driver/restdriver.go @@ -0,0 +1,90 @@ +// +// Copyright (c) 2019 Intel Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package driver + +import ( + "fmt" + + dsModels "github.com/edgexfoundry/device-sdk-go/pkg/models" + sdk "github.com/edgexfoundry/device-sdk-go/pkg/service" + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" + contract "github.com/edgexfoundry/go-mod-core-contracts/models" +) + +type RestDriver struct { + logger logger.LoggingClient + asyncValues chan<- *dsModels.AsyncValues +} + +// Initialize performs protocol-specific initialization for the device +// service. +func (driver *RestDriver) Initialize(logger logger.LoggingClient, asyncValues chan<- *dsModels.AsyncValues, deviceCh chan<- []dsModels.DiscoveredDevice) error { + driver.logger = logger + handler := NewRestHandler(sdk.RunningService(), logger, asyncValues) + return handler.Start() +} + +// HandleReadCommands triggers a protocol Read operation for the specified device. +func (driver *RestDriver) HandleReadCommands(deviceName string, protocols map[string]contract.ProtocolProperties, reqs []dsModels.CommandRequest) ([]*dsModels.CommandValue, error) { + driver.logger.Debug(fmt.Sprintf("RestDriver.HandleReadCommands called")) + + return nil, fmt.Errorf("RestDriver.HandleReadCommands; read commands not supported") +} + +// HandleWriteCommands passes a slice of CommandRequest struct each representing +// a ResourceOperation for a specific device resource. +// Since the commands are actuation commands, params provide parameters for the individual +// command. +func (driver *RestDriver) HandleWriteCommands(deviceName string, protocols map[string]contract.ProtocolProperties, reqs []dsModels.CommandRequest, + params []*dsModels.CommandValue) error { + + return fmt.Errorf("RestDriver.HandleWriteCommands; write commands not supported") +} + +// Stop the protocol-specific DS code to shutdown gracefully, or +// if the force parameter is 'true', immediately. The driver is responsible +// for closing any in-use channels, including the channel used to send async +// readings (if supported). +func (driver *RestDriver) Stop(force bool) error { + driver.logger.Debug(fmt.Sprintf("RestDriver.Stop called: force=%v", force)) + // Nothing required to do for Stop + return nil +} + +// AddDevice is a callback function that is invoked +// when a new Device associated with this Device Service is added +func (driver *RestDriver) AddDevice(deviceName string, protocols map[string]contract.ProtocolProperties, adminState contract.AdminState) error { + // Nothing required to do for AddDevice since new devices will be available + // when data is posted to REST endpoint + return nil +} + +// UpdateDevice is a callback function that is invoked +// when a Device associated with this Device Service is updated +func (driver *RestDriver) UpdateDevice(deviceName string, protocols map[string]contract.ProtocolProperties, adminState contract.AdminState) error { + // Nothing required to do for UpdateDevice since device update will be available + // when data is posted to REST endpoint. + return nil +} + +// RemoveDevice is a callback function that is invoked +// when a Device associated with this Device Service is removed +func (driver *RestDriver) RemoveDevice(deviceName string, protocols map[string]contract.ProtocolProperties) error { + // Nothing required to do for RemoveDevice since removed device will no longer be available + // when data is posted to REST endpoint. + return nil +} diff --git a/device-generic-rest/driver/resthandler.go b/device-generic-rest/driver/resthandler.go new file mode 100644 index 0000000..af489bf --- /dev/null +++ b/device-generic-rest/driver/resthandler.go @@ -0,0 +1,384 @@ +// +// Copyright (c) 2019 Intel Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package driver + +import ( + "context" + "fmt" + "io/ioutil" + "math" + "net/http" + "time" + + "github.com/gorilla/mux" + "github.com/spf13/cast" + + "github.com/edgexfoundry/device-sdk-go/pkg/models" + sdk "github.com/edgexfoundry/device-sdk-go/pkg/service" + "github.com/edgexfoundry/go-mod-core-contracts/clients" + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" +) + +const ( + deviceNameKey = "deviceName" + resourceNameKey = "resourceName" + apiResourceRoute = clients.ApiBase + "/resource/{" + deviceNameKey + "}/{" + resourceNameKey + "}" + handlerContextKey = "RestHandler" +) + +type RestHandler struct { + service *sdk.DeviceService + logger logger.LoggingClient + asyncValues chan<- *models.AsyncValues +} + +func NewRestHandler(service *sdk.DeviceService, logger logger.LoggingClient, asyncValues chan<- *models.AsyncValues) *RestHandler { + handler := RestHandler{ + service: service, + logger: logger, + asyncValues: asyncValues, + } + + return &handler +} + +func (handler RestHandler) Start() error { + if err := handler.service.AddRoute(apiResourceRoute, handler.addContext(deviceHandler), http.MethodPost); err != nil { + return fmt.Errorf("unable to add required route: %s: %s", apiResourceRoute, err.Error()) + } + + handler.logger.Info(fmt.Sprintf("Route %s added.", apiResourceRoute)) + + return nil +} + +func (handler RestHandler) addContext(next func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { + // Add the context with the handler so the endpoint handling code can get back to this handler + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), handlerContextKey, handler) + next(w, r.WithContext(ctx)) + }) +} + +func (handler RestHandler) processAsyncRequest(writer http.ResponseWriter, request *http.Request) { + vars := mux.Vars(request) + deviceName := vars[deviceNameKey] + resourceName := vars[resourceNameKey] + + handler.logger.Debug(fmt.Sprintf("Received POST for Device=%s Resource=%s", deviceName, resourceName)) + + _, err := handler.service.GetDeviceByName(deviceName) + if err != nil { + handler.logger.Error(fmt.Sprintf("Incoming reading ignored. Device '%s' not found", deviceName)) + http.Error(writer, fmt.Sprintf("Device '%s' not found", deviceName), http.StatusNotFound) + return + } + + deviceResource, ok := handler.service.DeviceResource(deviceName, resourceName, "get") + if !ok { + handler.logger.Error(fmt.Sprintf("Incoming reading ignored. Resource '%s' not found", resourceName)) + http.Error(writer, fmt.Sprintf("Resource '%s' not found", resourceName), http.StatusNotFound) + return + } + + if deviceResource.Properties.Value.MediaType != "" { + contentType := request.Header.Get(clients.ContentType) + + handler.logger.Debug(fmt.Sprintf("Content Type is '%s' & Media Type is '%s' and Type is '%s'", + contentType, deviceResource.Properties.Value.MediaType, deviceResource.Properties.Value.Type)) + + if contentType != deviceResource.Properties.Value.MediaType { + handler.logger.Error(fmt.Sprintf("Incoming reading ignored. Content Type '%s' doesn't match %s resource's Media Type '%s'", + contentType, resourceName, deviceResource.Properties.Value.MediaType)) + + http.Error(writer, "Wrong Content-Type", http.StatusBadRequest) + return + } + } + + var reading interface{} + readingType := models.ParseValueType(deviceResource.Properties.Value.Type) + + if readingType == models.Binary { + reading, err = handler.readBodyAsBinary(writer, request) + } else { + reading, err = handler.readBodyAsString(writer, request) + } + + if err != nil { + handler.logger.Error(fmt.Sprintf("Incoming reading ignored. Unable to read request body: %s", err.Error())) + http.Error(writer, err.Error(), http.StatusBadRequest) + return + } + + value, err := handler.newCommandValue(resourceName, reading, readingType) + if err != nil { + handler.logger.Error( + fmt.Sprintf("Incoming reading ignored. Unable to create Command Value for Device=%s Command=%s: %s", + deviceName, resourceName, err.Error())) + http.Error(writer, err.Error(), http.StatusBadRequest) + return + } + + asyncValues := &models.AsyncValues{ + DeviceName: deviceName, + CommandValues: []*models.CommandValue{value}, + } + + handler.logger.Debug(fmt.Sprintf("Incoming reading received: Device=%s Resource=%s", deviceName, resourceName)) + + handler.asyncValues <- asyncValues +} + +func (handler RestHandler) readBodyAsString(writer http.ResponseWriter, request *http.Request) (string, error) { + defer request.Body.Close() + body, err := ioutil.ReadAll(request.Body) + if err != nil { + return "", err + } + + if len(body) == 0 { + return "", fmt.Errorf("no request body provided") + } + + return string(body), nil +} + +func (handler RestHandler) readBodyAsBinary(writer http.ResponseWriter, request *http.Request) ([]byte, error) { + defer request.Body.Close() + body, err := ioutil.ReadAll(request.Body) + if err != nil { + return nil, err + } + + if len(body) == 0 { + return nil, fmt.Errorf("no request body provided") + } + + return body, nil +} + +func deviceHandler(writer http.ResponseWriter, request *http.Request) { + handler, ok := request.Context().Value(handlerContextKey).(RestHandler) + if !ok { + writer.WriteHeader(http.StatusBadRequest) + writer.Write([]byte("Bad context pass to handler")) + return + } + + handler.processAsyncRequest(writer, request) +} + +func (handler RestHandler) newCommandValue(resourceName string, reading interface{}, valueType models.ValueType) (*models.CommandValue, error) { + var result = &models.CommandValue{} + var err error + var timestamp = time.Now().UnixNano() / int64(time.Millisecond) + castError := "fail to parse %v reading, %v" + + if !checkValueInRange(valueType, reading) { + err = fmt.Errorf("parse reading fail. Reading %v is out of the value type(%v)'s range", reading, valueType) + handler.logger.Error(err.Error()) + return result, err + } + + switch valueType { + case models.Binary: + val, ok := reading.([]byte) + if !ok { + return nil, fmt.Errorf(castError, resourceName, "not []byte") + } + result, err = models.NewCommandValue(resourceName, timestamp, val, valueType) + + case models.Bool: + val, err := cast.ToBoolE(reading) + if err != nil { + return nil, fmt.Errorf(castError, resourceName, err) + } + result, err = models.NewCommandValue(resourceName, timestamp, val, valueType) + + case models.String: + val, err := cast.ToStringE(reading) + if err != nil { + return nil, fmt.Errorf(castError, resourceName, err) + } + result, err = models.NewCommandValue(resourceName, timestamp, val, valueType) + + case models.Uint8: + val, err := cast.ToUint8E(reading) + if err != nil { + return nil, fmt.Errorf(castError, resourceName, err) + } + result, err = models.NewCommandValue(resourceName, timestamp, val, valueType) + + case models.Uint16: + val, err := cast.ToUint16E(reading) + if err != nil { + return nil, fmt.Errorf(castError, resourceName, err) + } + result, err = models.NewCommandValue(resourceName, timestamp, val, valueType) + + case models.Uint32: + val, err := cast.ToUint32E(reading) + if err != nil { + return nil, fmt.Errorf(castError, resourceName, err) + } + result, err = models.NewCommandValue(resourceName, timestamp, val, valueType) + + case models.Uint64: + val, err := cast.ToUint64E(reading) + if err != nil { + return nil, fmt.Errorf(castError, resourceName, err) + } + result, err = models.NewCommandValue(resourceName, timestamp, val, valueType) + + case models.Int8: + val, err := cast.ToInt8E(reading) + if err != nil { + return nil, fmt.Errorf(castError, resourceName, err) + } + result, err = models.NewCommandValue(resourceName, timestamp, val, valueType) + + case models.Int16: + val, err := cast.ToInt16E(reading) + if err != nil { + return nil, fmt.Errorf(castError, resourceName, err) + } + result, err = models.NewCommandValue(resourceName, timestamp, val, valueType) + + case models.Int32: + val, err := cast.ToInt32E(reading) + if err != nil { + return nil, fmt.Errorf(castError, resourceName, err) + } + result, err = models.NewCommandValue(resourceName, timestamp, val, valueType) + + case models.Int64: + val, err := cast.ToInt64E(reading) + if err != nil { + return nil, fmt.Errorf(castError, resourceName, err) + } + result, err = models.NewCommandValue(resourceName, timestamp, val, valueType) + + case models.Float32: + val, err := cast.ToFloat32E(reading) + if err != nil { + return nil, fmt.Errorf(castError, resourceName, err) + } + result, err = models.NewCommandValue(resourceName, timestamp, val, valueType) + + case models.Float64: + val, err := cast.ToFloat64E(reading) + if err != nil { + return nil, fmt.Errorf(castError, resourceName, err) + } + result, err = models.NewCommandValue(resourceName, timestamp, val, valueType) + + default: + err = fmt.Errorf("return result fail, none supported value type: %v", valueType) + } + + return result, err +} + +func checkValueInRange(valueType models.ValueType, reading interface{}) bool { + isValid := false + + if valueType == models.String || valueType == models.Bool || valueType == models.Binary { + return true + } + + if valueType == models.Int8 || valueType == models.Int16 || + valueType == models.Int32 || valueType == models.Int64 { + val := cast.ToInt64(reading) + isValid = checkIntValueRange(valueType, val) + } + + if valueType == models.Uint8 || valueType == models.Uint16 || + valueType == models.Uint32 || valueType == models.Uint64 { + val := cast.ToUint64(reading) + isValid = checkUintValueRange(valueType, val) + } + + if valueType == models.Float32 || valueType == models.Float64 { + val := cast.ToFloat64(reading) + isValid = checkFloatValueRange(valueType, val) + } + + return isValid +} + +func checkUintValueRange(valueType models.ValueType, val uint64) bool { + var isValid = false + switch valueType { + case models.Uint8: + if val >= 0 && val <= math.MaxUint8 { + isValid = true + } + case models.Uint16: + if val >= 0 && val <= math.MaxUint16 { + isValid = true + } + case models.Uint32: + if val >= 0 && val <= math.MaxUint32 { + isValid = true + } + case models.Uint64: + maxiMum := uint64(math.MaxUint64) + if val >= 0 && val <= maxiMum { + isValid = true + } + } + return isValid +} + +func checkIntValueRange(valueType models.ValueType, val int64) bool { + var isValid = false + switch valueType { + case models.Int8: + if val >= math.MinInt8 && val <= math.MaxInt8 { + isValid = true + } + case models.Int16: + if val >= math.MinInt16 && val <= math.MaxInt16 { + isValid = true + } + case models.Int32: + if val >= math.MinInt32 && val <= math.MaxInt32 { + isValid = true + } + case models.Int64: + if val >= math.MinInt64 && val <= math.MaxInt64 { + isValid = true + } + } + return isValid +} + +func checkFloatValueRange(valueType models.ValueType, val float64) bool { + var isValid = false + switch valueType { + case models.Float32: + if math.Abs(val) >= math.SmallestNonzeroFloat32 && math.Abs(val) <= math.MaxFloat32 { + isValid = true + } + case models.Float64: + if math.Abs(val) >= math.SmallestNonzeroFloat64 && math.Abs(val) <= math.MaxFloat64 { + isValid = true + } + } + return isValid +} \ No newline at end of file diff --git a/device-generic-rest/go.mod b/device-generic-rest/go.mod new file mode 100644 index 0000000..9f7116d --- /dev/null +++ b/device-generic-rest/go.mod @@ -0,0 +1,10 @@ +module github.com/TIBCOSoftware/labs-air/edgexfoundry/device-generic-rest + +go 1.15 + +require ( + github.com/edgexfoundry/device-sdk-go v1.4.0 + github.com/edgexfoundry/go-mod-core-contracts v0.1.115 + github.com/gorilla/mux v1.8.0 + github.com/spf13/cast v1.3.0 +) diff --git a/device-generic-rest/version.go b/device-generic-rest/version.go new file mode 100644 index 0000000..bd51808 --- /dev/null +++ b/device-generic-rest/version.go @@ -0,0 +1,20 @@ +// +// Copyright (c) 2019 Intel Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package device_generic_rest + +// Global version for device-sdk-go +var Version string = "to be replaced by makefile" diff --git a/scripts/arm64/basicdemo/.env b/scripts/arm64/basicdemo/.env new file mode 100644 index 0000000..2a1f24b --- /dev/null +++ b/scripts/arm64/basicdemo/.env @@ -0,0 +1,21 @@ +GATEWAY_ID=changeme +GATEWAY_DESCRIPTION=changeme +GATEWAY_HOSTNAME=localhost +GATEWAY_LATITUDE=36.0 +GATEWAY_LONGITUDE=-98.0 +GATEWAY_ACCESS_TOKEN=changeme +GATEWAY_METADATA_PUBLISH_INTERVAL_SECS=30 +AIR_MQTT_HOSTNAME=changeme +AIR_MQTT_PORT=changeme +AIR_MQTT_USER=changeme +AIR_MQTT_PASSWORD=changeme +AIR_MQTT_DATA_TOPIC=EdgexGatewayData +AIR_MQTT_NOTIFICATION_TOPIC=EdgexGatewayNotification +AIR_KAFKA_BROKER_URL=changeme +AIR_KAFKA_TOPIC=EdgexGatewayData +AIR_KAFKA_USER=changeme +AIR_KAFKA_PASSWORD=changeme +EDGE_MQTT_HOSTNAME=changeme +EDGE_MQTT_PORT=changeme +EDGE_MQTT_USER=changeme +EDGE_MQTT_PASSWORD=changeme diff --git a/scripts/arm64/basicdemo/docker-compose.yml b/scripts/arm64/basicdemo/docker-compose.yml new file mode 100644 index 0000000..2d518f0 --- /dev/null +++ b/scripts/arm64/basicdemo/docker-compose.yml @@ -0,0 +1,795 @@ +# /******************************************************************************* +# * Copyright 2020 Redis Labs +# * Copyright 2020 Intel Corporation. +# * +# * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# * in compliance with the License. You may obtain a copy of the License at +# * +# * http://www.apache.org/licenses/LICENSE-2.0 +# * +# * Unless required by applicable law or agreed to in writing, software distributed under the License +# * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# * or implied. See the License for the specific language governing permissions and limitations under +# * the License. +# * +# * @author: Andre Srinivasan, Redis Labs +# * @author: Leonard Goodell, Intel +# * EdgeX Foundry, Geneva, version 1.2.0 +# * added: May 14, 2020 +# *******************************************************************************/ + +version: '3.4' + +# all common shared environment variables defined here: +x-common-env-variables: &common-variables + Registry_Host: edgex-core-consul + Clients_CoreData_Host: edgex-core-data + Clients_Data_Host: edgex-core-data # For device Services + Clients_Notifications_Host: edgex-support-notifications + Clients_Metadata_Host: edgex-core-metadata + Clients_Command_Host: edgex-core-command + Clients_Scheduler_Host: edgex-support-scheduler + Clients_RulesEngine_Host: edgex-kuiper + Clients_VirtualDevice_Host: edgex-device-virtual + Databases_Primary_Type: redisdb + Databases_Primary_Host: edgex-redis + Databases_Primary_Port: 6379 + SecretStore_Host: edgex-vault + SecretStore_ServerName: edgex-vault + SecretStore_RootCaCertPath: /tmp/edgex/secrets/ca/ca.pem + # Required in case old configuration from previous release used. + # Change to "true" if re-enabling logging service for remote logging + Logging_EnableRemote: "false" + # Clients_Logging_Host: edgex-support-logging # un-comment if re-enabling logging service for remote logging + +# REDIS5_PASSWORD_PATHNAME must have the same value as +# security-secretstore-read/res/configuration.toml SecretStore.Passwordfile. Note edgex-go issue +# #2503 that will address this. +x-redis5-env-variables: &redis5-variables + REDIS5_PASSWORD_PATHNAME: /tmp/edgex/secrets/edgex-redis/redis5-password + +volumes: + db-data: + log-data: + consul-config: + consul-data: + consul-scripts: + vault-init: + vault-config: + vault-file: + vault-logs: + # non-shared volumes + secrets-setup-cache: + +services: + consul: + image: edgexfoundry/docker-edgex-consul-arm64:1.2.0 + ports: + - "127.0.0.1:8400:8400" + - "127.0.0.1:8500:8500" + container_name: edgex-core-consul + hostname: edgex-core-consul + networks: + - edgex-network + volumes: + - consul-config:/consul/config:z + - consul-data:/consul/data:z + - consul-scripts:/consul/scripts:z + - /tmp/edgex/secrets/ca:/tmp/edgex/secrets/ca:ro,z + - /tmp/edgex/secrets/edgex-consul:/tmp/edgex/secrets/edgex-consul:ro,z + - /tmp/edgex/secrets/edgex-vault:/tmp/edgex/secrets/edgex-vault:ro,z + - /tmp/edgex/secrets/edgex-kong:/tmp/edgex/secrets/edgex-kong:ro,z + environment: + - "SECRETSTORE_SETUP_DONE_FLAG=/tmp/edgex/secrets/edgex-consul/.secretstore-setup-done" + - EDGEX_DB=redis + - EDGEX_SECURE=true + depends_on: + - security-secrets-setup + + vault: + image: vault:1.3.1 + container_name: edgex-vault + hostname: edgex-vault + networks: + - edgex-network + ports: + - "127.0.0.1:8200:8200" + cap_add: + - "IPC_LOCK" + tmpfs: + - /vault/config + entrypoint: ["/vault/init/start_vault.sh"] + environment: + - VAULT_ADDR=https://edgex-vault:8200 + - VAULT_CONFIG_DIR=/vault/config + - VAULT_UI=true + volumes: + - vault-file:/vault/file:z + - vault-logs:/vault/logs:z + - vault-init:/vault/init:ro,z + - /tmp/edgex/secrets/edgex-vault:/tmp/edgex/secrets/edgex-vault:ro,z + depends_on: + - consul + - security-secrets-setup + + security-secrets-setup: + image: edgexfoundry/docker-edgex-secrets-setup-go-arm64:1.2.1 + container_name: edgex-secrets-setup + hostname: edgex-secrets-setup + environment: + <<: *redis5-variables + tmpfs: + - /tmp + - /run + command: "generate" + volumes: + - secrets-setup-cache:/etc/edgex/pki + - vault-init:/vault/init:z + - /tmp/edgex/secrets:/tmp/edgex/secrets:z + + vault-worker: + image: edgexfoundry/docker-edgex-security-secretstore-setup-go-arm64:1.2.1 + container_name: edgex-vault-worker + hostname: edgex-vault-worker + environment: + <<: *redis5-variables + SECRETSTORE_SETUP_DONE_FLAG: /tmp/edgex/secrets/edgex-consul/.secretstore-setup-done + networks: + - edgex-network + tmpfs: + - /run + volumes: + - vault-config:/vault/config:z + - consul-scripts:/consul/scripts:ro,z + - /tmp/edgex/secrets:/tmp/edgex/secrets:z + depends_on: + - security-secrets-setup + - consul + - vault + +# containers for reverse proxy + kong-db: + image: postgres:12.1-alpine + container_name: kong-db + hostname: kong-db + networks: + - edgex-network + ports: + - "127.0.0.1:5432:5432" + environment: + - 'POSTGRES_DB=kong' + - 'POSTGRES_USER=kong' + - 'POSTGRES_PASSWORD=${KONG_POSTGRES_PASSWORD:-kong}' + depends_on: + - security-secrets-setup + + kong-migrations: + image: kong:${KONG_VERSION:-2.0.1}-ubuntu + container_name: kong-migrations + networks: + - edgex-network + environment: + - 'KONG_DATABASE=postgres' + - 'KONG_PG_HOST=kong-db' + - 'KONG_PG_PASSWORD=${KONG_POSTGRES_PASSWORD:-kong}' + command: > + /bin/sh -cx + 'until /consul/scripts/consul-svc-healthy.sh kong-db; + do sleep 1; + done && kong migrations bootstrap; + kong migrations list; + code=$$?; + if [ $$code -eq 5 ]; then + kong migrations up && kong migrations finish; + fi' + volumes: + - consul-scripts:/consul/scripts:ro,z + depends_on: + - consul + - kong-db + + kong: + image: kong:${KONG_VERSION:-2.0.1}-ubuntu + container_name: kong + hostname: kong + networks: + - edgex-network + ports: + - "8000:8000" + - "127.0.0.1:8001:8001" + - "8443:8443" + - "127.0.0.1:8444:8444" + tty: true + environment: + - 'KONG_DATABASE=postgres' + - 'KONG_PG_HOST=kong-db' + - 'KONG_PG_PASSWORD=${KONG_POSTGRES_PASSWORD:-kong}' + - 'KONG_PROXY_ACCESS_LOG=/dev/stdout' + - 'KONG_ADMIN_ACCESS_LOG=/dev/stdout' + - 'KONG_PROXY_ERROR_LOG=/dev/stderr' + - 'KONG_ADMIN_ERROR_LOG=/dev/stderr' + - 'KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl' + restart: on-failure + command: > + /bin/sh -c + "until /consul/scripts/consul-svc-healthy.sh kong-migrations; do sleep 1; done; + /docker-entrypoint.sh kong docker-start" + volumes: + - consul-scripts:/consul/scripts:ro,z + depends_on: + - consul + - kong-db + - kong-migrations + + edgex-proxy: + image: edgexfoundry/docker-edgex-security-proxy-setup-go-arm64:1.2.1 + container_name: edgex-proxy + hostname: edgex-proxy + entrypoint: > + /bin/sh -c + "until /consul/scripts/consul-svc-healthy.sh kong; do sleep 1; done; + until /consul/scripts/consul-svc-healthy.sh security-secretstore-setup; do sleep 1; done; + /edgex/security-proxy-setup --init=true" + networks: + - edgex-network + environment: + <<: *common-variables + KongURL_Server: kong + SecretService_Server: edgex-vault + SecretService_TokenPath: /tmp/edgex/secrets/edgex-security-proxy-setup/secrets-token.json + SecretService_CACertPath: /tmp/edgex/secrets/ca/ca.pem + SecretService_SNIS: "edgex-kong" + volumes: + - consul-scripts:/consul/scripts:ro,z + - /tmp/edgex/secrets/ca:/tmp/edgex/secrets/ca:ro,z + - /tmp/edgex/secrets/edgex-security-proxy-setup:/tmp/edgex/secrets/edgex-security-proxy-setup:ro,z + depends_on: + - consul + - vault-worker + - kong + +# end of containers for reverse proxy + + redis: + image: arm64v8/redis:5.0.8-alpine + ports: + - "127.0.0.1:6379:6379" + container_name: edgex-redis + hostname: edgex-redis + environment: + <<: *redis5-variables + command: | + /bin/sh -c " + until [ -r $${REDIS5_PASSWORD_PATHNAME} ] && [ -s $${REDIS5_PASSWORD_PATHNAME} ]; do sleep 1; done + exec /usr/local/bin/docker-entrypoint.sh --requirepass `cat $${REDIS5_PASSWORD_PATHNAME}` \ + --dir /data \ + --save 900 1 \ + --save 300 10 \ + --save 60 10000 + " + networks: + - edgex-network + volumes: + - db-data:/data:z + - /tmp/edgex/secrets/edgex-redis:/tmp/edgex/secrets/edgex-redis:z + depends_on: + - vault-worker + +# The logging service has been deprecated in Geneva release and will be removed in the Hanoi release. +# All services are configure to send logging to STDOUT, i.e. not remote which requires this logging service +# If you still must use remote logging, un-comment the block below, all the related depends that have been commented out +# and the related global override that are commented out at the top. +# +# logging: +# image: edgexfoundry/docker-support-logging-go-arm64:1.2.1 +# ports: +# - "127.0.0.1:48061:48061" +# container_name: edgex-support-logging +# hostname: edgex-support-logging +# networks: +# - edgex-network +# environment: +# <<: *common-variables +# SecretStore_TokenFile: /tmp/edgex/secrets/edgex-support-logging/secrets-token.json +# Service_Host: edgex-support-logging +# Writable_Persistence: file +# Databases_Primary_Type: file +# Logging_EnableRemote: "false" +# volumes: +# - /tmp/edgex/secrets/ca:/tmp/edgex/secrets/ca:ro,z +# - /tmp/edgex/secrets/edgex-support-logging:/tmp/edgex/secrets/edgex-support-logging:ro,z +# depends_on: +# - consul +# - vault-worker + + system: + image: edgexfoundry/docker-sys-mgmt-agent-go-arm64:1.2.1 + ports: + - "127.0.0.1:48090:48090" + container_name: edgex-sys-mgmt-agent + hostname: edgex-sys-mgmt-agent + networks: + - edgex-network + environment: + <<: *common-variables + Service_Host: edgex-sys-mgmt-agent + ExecutorPath: /sys-mgmt-executor + MetricsMechanism: executor + volumes: + - /var/run/docker.sock:/var/run/docker.sock:z + depends_on: + - consul +# - logging # uncomment if re-enabled remote logging + - scheduler + - notifications + - metadata + - data + - command + + notifications: + image: edgexfoundry/docker-support-notifications-go-arm64:1.2.1 + ports: + - "127.0.0.1:48060:48060" + container_name: edgex-support-notifications + hostname: edgex-support-notifications + networks: + - edgex-network + environment: + <<: *common-variables + Service_Host: edgex-support-notifications + SecretStore_TokenFile: /tmp/edgex/secrets/edgex-support-notifications/secrets-token.json + volumes: + - /tmp/edgex/secrets/ca:/tmp/edgex/secrets/ca:ro,z + - /tmp/edgex/secrets/edgex-support-notifications:/tmp/edgex/secrets/edgex-support-notifications:ro,z + depends_on: + - consul +# - logging # uncomment if re-enabled remote logging + - redis + - vault-worker + + metadata: + image: edgexfoundry/docker-core-metadata-go-arm64:1.2.1 + ports: + - "127.0.0.1:48081:48081" + container_name: edgex-core-metadata + hostname: edgex-core-metadata + networks: + - edgex-network + environment: + <<: *common-variables + Service_Host: edgex-core-metadata + Service_Timeout: "20000" + Notifications_Sender: edgex-core-metadata + SecretStore_TokenFile: /tmp/edgex/secrets/edgex-core-metadata/secrets-token.json + volumes: + - /tmp/edgex/secrets/ca:/tmp/edgex/secrets/ca:ro,z + - /tmp/edgex/secrets/edgex-core-metadata:/tmp/edgex/secrets/edgex-core-metadata:ro,z + depends_on: + - consul +# - logging # uncomment if re-enabled remote logging + - redis + - notifications + - vault-worker + + data: + image: edgexfoundry/docker-core-data-go-arm64:1.2.1 + ports: + - "127.0.0.1:48080:48080" + - "127.0.0.1:5563:5563" + container_name: edgex-core-data + hostname: edgex-core-data + networks: + - edgex-network + environment: + <<: *common-variables + Service_Host: edgex-core-data + SecretStore_TokenFile: /tmp/edgex/secrets/edgex-core-data/secrets-token.json + volumes: + - /tmp/edgex/secrets/ca:/tmp/edgex/secrets/ca:ro,z + - /tmp/edgex/secrets/edgex-core-data:/tmp/edgex/secrets/edgex-core-data:ro,z + depends_on: + - consul +# - logging # uncomment if re-enabled remote logging + - redis + - metadata + - vault-worker + + command: + image: edgexfoundry/docker-core-command-go-arm64:1.2.1 + ports: + - "127.0.0.1:48082:48082" + container_name: edgex-core-command + hostname: edgex-core-command + networks: + - edgex-network + environment: + <<: *common-variables + Service_Host: edgex-core-command + SecretStore_TokenFile: /tmp/edgex/secrets/edgex-core-command/secrets-token.json + volumes: + - /tmp/edgex/secrets/ca:/tmp/edgex/secrets/ca:ro,z + - /tmp/edgex/secrets/edgex-core-command:/tmp/edgex/secrets/edgex-core-command:ro,z + depends_on: + - consul +# - logging # uncomment if re-enabled remote logging + - redis + - metadata + - vault-worker + + scheduler: + image: edgexfoundry/docker-support-scheduler-go-arm64:1.2.1 + ports: + - "127.0.0.1:48085:48085" + container_name: edgex-support-scheduler + hostname: edgex-support-scheduler + networks: + - edgex-network + environment: + <<: *common-variables + Service_Host: edgex-support-scheduler + IntervalActions_ScrubPushed_Host: edgex-core-data + IntervalActions_ScrubAged_Host: edgex-core-data + SecretStore_TokenFile: /tmp/edgex/secrets/edgex-support-scheduler/secrets-token.json + volumes: + - /tmp/edgex/secrets/ca:/tmp/edgex/secrets/ca:ro,z + - /tmp/edgex/secrets/edgex-support-scheduler:/tmp/edgex/secrets/edgex-support-scheduler:ro,z + depends_on: + - consul +# - logging # uncomment if re-enabled remote logging + - redis + - vault-worker + +# app-service-rules: +# image: edgexfoundry/docker-app-service-configurable-arm64:1.2.0 +# ports: +# - "127.0.0.1:48100:48100" +# container_name: edgex-app-service-configurable-rules +# hostname: edgex-app-service-configurable-rules +# networks: +# - edgex-network +# environment: +# <<: *common-variables +# EDGEX_SECURITY_SECRET_STORE: "false" # required since SecretStore not used and not configured +# edgex_profile: rules-engine +# Service_Host: edgex-app-service-configurable-rules +# Service_Port: 48100 +# MessageBus_SubscribeHost_Host: edgex-core-data +# Binding_PublishTopic: events +# depends_on: +# - consul +# # - logging # uncomment if re-enabled remote logging +# - data +# - vault-worker + +# rulesengine: +# image: emqx/kuiper:0.4.2-alpine +# ports: +# - "127.0.0.1:48075:48075" +# - "127.0.0.1:20498:20498" +# container_name: edgex-kuiper +# hostname: edgex-kuiper +# networks: +# - edgex-network +# environment: +# # KUIPER_DEBUG: "true" +# KUIPER_CONSOLE_LOG: "true" +# KUIPER_REST_PORT: 48075 +# EDGEX_SERVER: edgex-app-service-configurable-rules +# EDGEX_SERVICE_SERVER: http://edgex-core-data:48080 +# EDGEX_TOPIC: events +# EDGEX_PROTOCOL: tcp +# EDGEX_PORT: 5566 +# depends_on: +# - app-service-rules + + # Support RulesEngine has been deprecated in the Geneva (1.2.0) release + # If still required, simply uncomment the block below and comment out the block above. + # + # rulesengine: + # image: edgexfoundry/docker-support-rulesengine-arm64:1.2.1 + # ports: + # - "127.0.0.1:48075:48075" + # container_name: edgex-support-rulesengine + # hostname: edgex-support-rulesengine + # networks: + # - edgex-network + # depends_on: + # - app-service-rules + +################################################################# +# Device Services +################################################################# + +# device-virtual: +# image: edgexfoundry/docker-device-virtual-go-arm64:1.2.1 +# ports: +# - "127.0.0.1:49990:49990" +# container_name: edgex-device-virtual +# hostname: edgex-device-virtual +# networks: +# - edgex-network +# environment: +# <<: *common-variables +# Service_Host: edgex-device-virtual +# depends_on: +# - consul +# # - logging # uncomment if re-enabled remote logging +# - data +# - metadata + + # device-rest: + # image: edgexfoundry/docker-device-rest-go-arm64:1.1.1 + # ports: + # - "127.0.0.1:49986:49986" + # container_name: edgex-device-rest + # hostname: edgex-device-rest + # networks: + # - edgex-network + # environment: + # <<: *common-variables + # Service_Host: edgex-device-rest + # depends_on: + # - data + # - command + # # - logging # uncomment if re-enabled remote logging + +# device-random: +# image: edgexfoundry/docker-device-random-go-arm64:1.2.1 +# ports: +# - "127.0.0.1:49988:49988" +# container_name: edgex-device-random +# hostname: edgex-device-random +# networks: +# - edgex-network +# environment: +# <<: *common-variables +# Service_Host: edgex-device-random +# depends_on: +# - data +# - command +# +# device-mqtt: +# image: edgexfoundry/docker-device-mqtt-go-arm64:1.2.1 +# ports: +# - "127.0.0.1:49982:49982" +# container_name: edgex-device-mqtt +# hostname: edgex-device-mqtt +# networks: +# - edgex-network +# environment: +# <<: *common-variables +# Service_Host: edgex-device-mqtt +# depends_on: +# - data +# - command +# +# device-modbus: +# image: edgexfoundry/docker-device-modbus-go-arm64:1.2.1 +# ports: +# - "127.0.0.1:49991:49991" +# container_name: edgex-device-modbus +# hostname: edgex-device-modbus +# networks: +# - edgex-network +# environment: +# <<: *common-variables +# Service_Host: edgex-device-modbus +# depends_on: +# - data +# - command +# +# device-snmp: +# image: edgexfoundry/docker-device-snmp-go-arm64:1.2.1 +# ports: +# - "127.0.0.1:49993:49993" +# container_name: edgex-device-snmp +# hostname: edgex-device-snmp +# networks: +# - edgex-network +# environment: +# <<: *common-variables +# Service_Host: edgex-device-snmp +# depends_on: +# - data +# - command + +################################################################# +# TIBCO Labs Air +################################################################# + + service-export-mqtt: + image: tibcosoftware/labs-air-edgex-app-service-export-mqtt-arm64:0.2.0 + ports: + - "48500:48500" + container_name: air-app-service-export-mqtt + hostname: air-app-service-export-mqtt + networks: + - edgex-network + environment: + <<: *common-variables + EDGEX_SECURITY_SECRET_STORE: "false" # required since SecretStore not used and not configured + ApplicationSettings_MqttHostname: ${AIR_MQTT_HOSTNAME} + ApplicationSettings_MqttPort: ${AIR_MQTT_PORT} + ApplicationSettings_MqttUser: ${AIR_MQTT_USER} + ApplicationSettings_MqttPassword: ${AIR_MQTT_PASSWORD} + ApplicationSettings_MqttTopic: ${AIR_MQTT_DATA_TOPIC} + ApplicationSettings_GatewayId: ${GATEWAY_ID} + ApplicationSettings_GatewayDescription: ${GATEWAY_DESCRIPTION} + ApplicationSettings_GatewayHostname: ${GATEWAY_HOSTNAME} + ApplicationSettings_GatewayLatitude: ${GATEWAY_LATITUDE} + ApplicationSettings_GatewayLongitude: ${GATEWAY_LONGITUDE} + ApplicationSettings_GatewayAccessToken: ${GATEWAY_ACCESS_TOKEN} + ApplicationSettings_MetadataPublishIntervalSecs: ${GATEWAY_METADATA_PUBLISH_INTERVAL_SECS} + volumes: + - db-data:/data/db + - log-data:/edgex/logs + - consul-config:/consul/config + - consul-data:/consul/data + depends_on: + - data + - command + + service-export-kafka: + image: tibcosoftware/labs-air-edgex-app-service-export-kafka-arm64:0.2.0 + ports: + - "48505:48505" + container_name: air-app-service-export-kafka + hostname: air-app-service-export-kafka + networks: + - edgex-network + environment: + <<: *common-variables + EDGEX_SECURITY_SECRET_STORE: "false" # required since SecretStore not used and not configured + ApplicationSettings_KafkaBrokerUrl: ${AIR_KAFKA_BROKER_URL} + ApplicationSettings_KafkaUser: ${AIR_KAFKA_USER} + ApplicationSettings_KafkaPassword: ${AIR_KAFKA_PASSWORD} + ApplicationSettings_KafkaTopic: ${AIR_KAFKA_TOPIC} + ApplicationSettings_GatewayId: ${GATEWAY_ID} + ApplicationSettings_GatewayDescription: ${GATEWAY_DESCRIPTION} + ApplicationSettings_GatewayHostname: ${GATEWAY_HOSTNAME} + ApplicationSettings_GatewayLatitude: ${GATEWAY_LATITUDE} + ApplicationSettings_GatewayLongitude: ${GATEWAY_LONGITUDE} + ApplicationSettings_GatewayAccessToken: ${GATEWAY_ACCESS_TOKEN} + ApplicationSettings_MetadataPublishIntervalSecs: ${GATEWAY_METADATA_PUBLISH_INTERVAL_SECS} + volumes: + - db-data:/data/db + - log-data:/edgex/logs + - consul-config:/consul/config + - consul-data:/consul/data + depends_on: + - data + - command + + service-flogo-rules: + image: tibcosoftware/labs-air-edgex-app-service-flogo-rules-arm64:0.2.0 + ports: + - "48095:48095" + container_name: air-app-service-flogo-rules + hostname: air-app-service-flogo-rules + networks: + - edgex-network + environment: + <<: *common-variables + EDGEX_SECURITY_SECRET_STORE: "false" # required since SecretStore not used and not configured + ApplicationSettings_MqttHostname: ${AIR_MQTT_HOSTNAME} + ApplicationSettings_MqttPort: ${AIR_MQTT_PORT} + ApplicationSettings_MqttUser: ${AIR_MQTT_USER} + ApplicationSettings_MqttPassword: ${AIR_MQTT_PASSWORD} + ApplicationSettings_MqttTopic: ${AIR_MQTT_NOTIFICATION_TOPIC} + ApplicationSettings_GatewayId: ${GATEWAY_ID} + volumes: + - db-data:/data/db + - log-data:/edgex/logs + - consul-config:/consul/config + - consul-data:/consul/data + depends_on: + - data + + device-stm32l475-mqtt: + image: tibcosoftware/labs-air-edgex-device-stm32l475-mqtt-arm64:0.2.0 + ports: + - "48988:48988" + container_name: labsair-device-stm32l475-mqtt + hostname: labsair-device-stm32l475-mqtt + networks: + - edgex-network + environment: + <<: *common-variables + EDGEX_SECURITY_SECRET_STORE: "false" # required since SecretStore not used and not configured + Driver_IncomingHost: ${EDGE_MQTT_HOSTNAME} + Driver_IncomingPort: ${EDGE_MQTT_PORT} + Driver_IncomingUser: ${EDGE_MQTT_USER} + Driver_IncomingPassword: ${EDGE_MQTT_PASSWORD} + volumes: + - db-data:/data/db + - log-data:/edgex/logs + - consul-config:/consul/config + - consul-data:/consul/data + depends_on: + - data + - command + + device-particle-mqtt: + image: tibcosoftware/labs-air-edgex-device-particle-mqtt-arm64:0.2.0 + ports: + - "48988:48988" + container_name: labsair-device-particle-mqtt + hostname: labsair-device-particle-mqtt + networks: + - edgex-network + environment: + <<: *common-variables + EDGEX_SECURITY_SECRET_STORE: "false" # required since SecretStore not used and not configured + Driver_IncomingHost: ${EDGE_MQTT_HOSTNAME} + Driver_IncomingPort: ${EDGE_MQTT_PORT} + Driver_IncomingUser: ${EDGE_MQTT_USER} + Driver_IncomingPassword: ${EDGE_MQTT_PASSWORD} + volumes: + - db-data:/data/db + - log-data:/edgex/logs + - consul-config:/consul/config + - consul-data:/consul/data + depends_on: + - data + - command + + device-vehicle-simulator: + image: tibcosoftware/labs-air-edgex-device-vehicle-simulator-arm64:0.2.0 + ports: + - "48992:48992" + container_name: labsair-device-vehicle-simulator + hostname: labsair-device-vehicle-simulator + networks: + - edgex-network + environment: + <<: *common-variables + volumes: + - db-data:/data/db + - log-data:/edgex/logs + - consul-config:/consul/config + - consul-data:/consul/data + depends_on: + - data + - command + + device-siemens-simulator: + image: tibcosoftware/labs-air-edgex-device-siemens-simulator-arm64:0.2.0 + ports: + - "48995:48995" + container_name: labsair-device-siemens-simulator + hostname: labsair-device-siemens-simulator + networks: + - edgex-network + environment: + <<: *common-variables + volumes: + - db-data:/data/db + - log-data:/edgex/logs + - consul-config:/consul/config + - consul-data:/consul/data + depends_on: + - data + - command + + device-smartwires-simulator: + image: tibcosoftware/labs-air-edgex-device-smartwires-simulator-arm64:0.2.0 + ports: + - "48998:48998" + container_name: labsair-device-smartwires-simulator + hostname: labsair-device-smartwires-simulator + networks: + - edgex-network + environment: + <<: *common-variables + volumes: + - db-data:/data/db + - log-data:/edgex/logs + - consul-config:/consul/config + - consul-data:/consul/data + depends_on: + - data + - command + +networks: + edgex-network: + driver: "bridge" \ No newline at end of file diff --git a/scripts/arm64/basicdemo/getSecurityToken.sh b/scripts/arm64/basicdemo/getSecurityToken.sh new file mode 100755 index 0000000..8fe3b01 --- /dev/null +++ b/scripts/arm64/basicdemo/getSecurityToken.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +docker-compose run --rm --entrypoint /edgex/security-proxy-setup edgex-proxy --init=false --useradd=tibuser --group=admin \ No newline at end of file diff --git a/scripts/arm64/basicdemo/startEdgeAIR.sh b/scripts/arm64/basicdemo/startEdgeAIR.sh new file mode 100755 index 0000000..0f61018 --- /dev/null +++ b/scripts/arm64/basicdemo/startEdgeAIR.sh @@ -0,0 +1,87 @@ +#!/bin/sh + +# Copyright 2017 Konrad Zapalowicz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Start EdgeX Foundry services in right order, as described: +# https://wiki.edgexfoundry.org/display/FA/Get+EdgeX+Foundry+-+Users + +COMPOSE_FILE=${1:-docker-compose.yml} +echo "Using compose file: $COMPOSE_FILE" + +run_service () { + echo "\n" + echo -e "\033[0;32mStarting.. $1\033[0m" + docker-compose -f "$COMPOSE_FILE" up -d $1 + + if [ "$1" = "config-seed" ] + then + while [ -z "$(curl -s http://localhost:8500/v1/kv/config/device-virtual\;docker/app.open.msg)" ] + do + sleep 1 + done + echo "$1 has been completely started !" + return + fi + + if [ -z "$2" ] + then + sleep 1 + echo "$1 has been completely started !" + return + fi + + if [ -n "$2" ] + then + sleep $2 + echo "$1 has been completely started !" + return + fi +} + + + + +run_service service-export-mqtt 5 + +# run_service service-export-kafka 48505 + +run_service service-flogo-rules + +# run_service device-mqtt + +# run_service device-particle-mqtt + +# run_service device-stm32l475-mqtt + +# run_service device-enersys-mqtt + +# run_service device-jabil-rest + +run_service device-vehicle-simulator + +run_service device-siemens-simulator + +# sssssrun_service device-smartwires-simulator + +# run_service device-siemens-rest + +# run_service device-virtual + +# run_service device-random + +echo "\n" +echo -e "\033[0;32m All services started. Edgex is ready\033[0m" + diff --git a/scripts/arm64/basicdemo/startEdgex.sh b/scripts/arm64/basicdemo/startEdgex.sh new file mode 100755 index 0000000..e81378a --- /dev/null +++ b/scripts/arm64/basicdemo/startEdgex.sh @@ -0,0 +1,88 @@ +#!/bin/sh + +# Copyright 2017 Konrad Zapalowicz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Start EdgeX Foundry services in right order, as described: +# https://wiki.edgexfoundry.org/display/FA/Get+EdgeX+Foundry+-+Users + +COMPOSE_FILE=${1:-docker-compose.yml} +echo "Using compose file: $COMPOSE_FILE" + +run_service () { + echo "\n" + echo -e "\033[0;32mStarting.. $1\033[0m" + docker-compose -f "$COMPOSE_FILE" up -d $1 + + if [ "$1" = "config-seed" ] + then + while [ -z "$(curl -s http://localhost:8500/v1/kv/config/device-virtual\;docker/app.open.msg)" ] + do + sleep 1 + done + echo "$1 has been completely started !" + return + fi + + if [ -z "$2" ] + then + sleep 1 + echo "$1 has been completely started !" + return + fi + + if [ -n "$2" ] + then + sleep $2 + echo "$1 has been completely started !" + return + fi +} + + +run_service consul + +run_service vault + +run_service security-secrets-setup + +run_service vault-worker + +run_service kong-db + +run_service kong-migrations 2 + +run_service kong 5 + +run_service edgex-proxy + +run_service redis + +run_service system + +run_service notifications + +run_service metadata + +run_service data + +run_service command + +run_service scheduler + + +echo "\n" +echo -e "\033[0;32m All services started. Edgex is ready\033[0m" + diff --git a/scripts/arm64/basicdemo/stopEdgex.sh b/scripts/arm64/basicdemo/stopEdgex.sh new file mode 100755 index 0000000..c872144 --- /dev/null +++ b/scripts/arm64/basicdemo/stopEdgex.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +docker-compose down -v diff --git a/scripts/build_air_edgex_component.sh b/scripts/build_air_edgex_component.sh new file mode 100755 index 0000000..0d06295 --- /dev/null +++ b/scripts/build_air_edgex_component.sh @@ -0,0 +1,18 @@ +. scripts/tools.sh + +image_name=$1 +image_tag=$2 +image_url=$3 +component_name=$4 +target_name=$5 + +echo "in build air edgex component" +echo ${image_url} +echo ${component_name} + + +pushd ./${component_name} > /dev/null + +build_image $image_name "local_image_tag" $image_url "Dockerfile" $target_name + +tag_image $image_name "local_image_tag" $image_name $image_tag $image_url \ No newline at end of file diff --git a/scripts/build_installer.sh b/scripts/build_installer.sh new file mode 100755 index 0000000..8d57b0a --- /dev/null +++ b/scripts/build_installer.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +installer_target_path="dist" + +if [ -d $installer_target_path ]; then + rm -rf $installer_target_path +fi +mkdir -p $installer_target_path + +cp -r scripts/linux $installer_target_path +cp -r scripts/arm64 $installer_target_path \ No newline at end of file diff --git a/scripts/delete_local_image.sh b/scripts/delete_local_image.sh new file mode 100755 index 0000000..1b399af --- /dev/null +++ b/scripts/delete_local_image.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +. scripts/tools.sh + +image_name=$1 +image_tag=$2 +image_url=$3 + +delete_local_image $image_name $image_tag $image_url \ No newline at end of file diff --git a/scripts/linux/basicdemo/.env b/scripts/linux/basicdemo/.env new file mode 100644 index 0000000..f5fd1ad --- /dev/null +++ b/scripts/linux/basicdemo/.env @@ -0,0 +1,26 @@ +GATEWAY_ID=HelloWorldGroup +GATEWAY_DESCRIPTION=Hello_World_Device_Group +GATEWAY_HOSTNAME=localhost +GATEWAY_LATITUDE=36.0 +GATEWAY_LONGITUDE=-98.0 +GATEWAY_ACCESS_TOKEN=${EDGEX_TOKEN} +GATEWAY_METADATA_PUBLISH_INTERVAL_SECS=30 +GATEWAY_ROUTER= +GATEWAY_ROUTER_PORT=22222 +GATEWAY_DEPLOY_NETWORK=basicdemo_edgex-network +GATEWAY_USERNAME= +GATEWAY_PLATFORM=linux/amd64 +GATEWAY_METADATA_CLIENT=http://edgex-core-metadata:48081 +AIR_MQTT_HOSTNAME=my_mqtt_hostname +AIR_MQTT_PORT=31883 +AIR_MQTT_USER=mqtt_admin +AIR_MQTT_PASSWORD=mqtt_admin +AIR_MQTT_DATA_TOPIC=EdgexGatewayData +AIR_MQTT_NOTIFICATION_TOPIC=EdgexGatewayNotification +AIR_MQTT_DATA_METADATA_PUBLISHER=EdgexDataMetadataClient +AIR_GENERIC_MQTT_CLIENT_ID=GenericMQTTClient +AIR_GENERIC_MQTT_RESPONSE_CLIENT_ID=GenericMQTTResponseClient +EDGE_MQTT_HOSTNAME=localhost +EDGE_MQTT_PORT=1883 +EDGE_MQTT_USER= +EDGE_MQTT_PASSWORD= diff --git a/scripts/linux/basicdemo/docker-compose.yml b/scripts/linux/basicdemo/docker-compose.yml new file mode 100644 index 0000000..c4b797f --- /dev/null +++ b/scripts/linux/basicdemo/docker-compose.yml @@ -0,0 +1,861 @@ +version: '3.7' +networks: + edgex-network: + driver: bridge +volumes: + consul-config: {} + consul-data: {} + consul-scripts: {} + db-data: {} + kong: {} + log-data: {} + postgres-data: {} + secrets-setup-cache: {} + vault-config: {} + vault-file: {} + vault-init: {} + vault-logs: {} +services: + app-service-rules: + container_name: edgex-app-service-configurable-rules + depends_on: + consul: + condition: service_started + data: + condition: service_started + mqtt-broker: + condition: service_started + environment: + BINDING_PUBLISHTOPIC: rules-events + CLIENTS_COMMAND_HOST: edgex-core-command + CLIENTS_COREDATA_HOST: edgex-core-data + CLIENTS_DATA_HOST: edgex-core-data + CLIENTS_METADATA_HOST: edgex-core-metadata + CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications + CLIENTS_RULESENGINE_HOST: edgex-kuiper + CLIENTS_SCHEDULER_HOST: edgex-support-scheduler + CLIENTS_VIRTUALDEVICE_HOST: edgex-device-virtual + DATABASES_PRIMARY_HOST: edgex-redis + EDGEX_PROFILE: rules-engine-mqtt + EDGEX_SECURITY_SECRET_STORE: "false" + MESSAGEBUS_PUBLISHHOST_HOST: edgex-mqtt-broker + MESSAGEBUS_SUBSCRIBEHOST_HOST: edgex-mqtt-broker + REGISTRY_HOST: edgex-core-consul + SERVICE_HOST: edgex-app-service-configurable-rules + SERVICE_PORT: 48100 + hostname: edgex-app-service-configurable-rules + image: edgexfoundry/docker-app-service-configurable:1.3.0 + networks: + edgex-network: {} + ports: + - 127.0.0.1:48100:48100/tcp + read_only: true + command: + container_name: edgex-core-command + depends_on: + consul: + condition: service_started + database: + condition: service_started + metadata: + condition: service_started + security-bootstrap-database: + condition: service_started + vault-worker: + condition: service_started + environment: + CLIENTS_COMMAND_HOST: edgex-core-command + CLIENTS_COREDATA_HOST: edgex-core-data + CLIENTS_DATA_HOST: edgex-core-data + CLIENTS_METADATA_HOST: edgex-core-metadata + CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications + CLIENTS_RULESENGINE_HOST: edgex-kuiper + CLIENTS_SCHEDULER_HOST: edgex-support-scheduler + CLIENTS_VIRTUALDEVICE_HOST: edgex-device-virtual + DATABASES_PRIMARY_HOST: edgex-redis + EDGEX_SECURITY_SECRET_STORE: "true" + REGISTRY_HOST: edgex-core-consul + SECRETSTORE_HOST: edgex-vault + SECRETSTORE_ROOTCACERTPATH: /tmp/edgex/secrets/ca/ca.pem + SECRETSTORE_SERVERNAME: edgex-vault + SECRETSTORE_TOKENFILE: /tmp/edgex/secrets/edgex-core-command/secrets-token.json + SERVICE_HOST: edgex-core-command + hostname: edgex-core-command + image: edgexfoundry/docker-core-command-go:1.3.0 + networks: + edgex-network: {} + ports: + - 127.0.0.1:48082:48082/tcp + read_only: true + volumes: + - /tmp/edgex/secrets/ca:/tmp/edgex/secrets/ca:ro,z + - /tmp/edgex/secrets/edgex-core-command:/tmp/edgex/secrets/edgex-core-command:ro,z + consul: + container_name: edgex-core-consul + depends_on: + security-secrets-setup: + condition: service_started + environment: + EDGEX_DB: redis + EDGEX_SECURE: "true" + SECRETSTORE_SETUP_DONE_FLAG: /tmp/edgex/secrets/edgex-consul/.secretstore-setup-done + hostname: edgex-core-consul + image: edgexfoundry/docker-edgex-consul:1.3.0 + networks: + edgex-network: {} + ports: + - 127.0.0.1:8500:8500/tcp + read_only: true + volumes: + - consul-config:/consul/config:z + - consul-data:/consul/data:z + - consul-scripts:/consul/scripts:z + - /tmp/edgex/secrets/ca:/tmp/edgex/secrets/ca:ro,z + - /tmp/edgex/secrets/edgex-consul:/tmp/edgex/secrets/edgex-consul:ro,z + - /tmp/edgex/secrets/edgex-kong:/tmp/edgex/secrets/edgex-kong:ro,z + - /tmp/edgex/secrets/edgex-vault:/tmp/edgex/secrets/edgex-vault:ro,z + data: + container_name: edgex-core-data + depends_on: + consul: + condition: service_started + database: + condition: service_started + metadata: + condition: service_started + mqtt-broker: + condition: service_started + security-bootstrap-database: + condition: service_started + vault-worker: + condition: service_started + environment: + CLIENTS_COMMAND_HOST: edgex-core-command + CLIENTS_COREDATA_HOST: edgex-core-data + CLIENTS_DATA_HOST: edgex-core-data + CLIENTS_METADATA_HOST: edgex-core-metadata + CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications + CLIENTS_RULESENGINE_HOST: edgex-kuiper + CLIENTS_SCHEDULER_HOST: edgex-support-scheduler + CLIENTS_VIRTUALDEVICE_HOST: edgex-device-virtual + DATABASES_PRIMARY_HOST: edgex-redis + EDGEX_SECURITY_SECRET_STORE: "true" + MESSAGEQUEUE_HOST: edgex-mqtt-broker + MESSAGEQUEUE_OPTIONAL_CLIENTID: edgex-core-data + MESSAGEQUEUE_PORT: 1883 + MESSAGEQUEUE_TYPE: mqtt + REGISTRY_HOST: edgex-core-consul + SECRETSTORE_HOST: edgex-vault + SECRETSTORE_ROOTCACERTPATH: /tmp/edgex/secrets/ca/ca.pem + SECRETSTORE_SERVERNAME: edgex-vault + SECRETSTORE_TOKENFILE: /tmp/edgex/secrets/edgex-core-data/secrets-token.json + SERVICE_HOST: edgex-core-data + hostname: edgex-core-data + image: edgexfoundry/docker-core-data-go:1.3.0 + networks: + edgex-network: {} + ports: + - 127.0.0.1:5563:5563/tcp + - 127.0.0.1:48080:48080/tcp + read_only: true + volumes: + - /tmp/edgex/secrets/ca:/tmp/edgex/secrets/ca:ro,z + - /tmp/edgex/secrets/edgex-core-data:/tmp/edgex/secrets/edgex-core-data:ro,z + database: + container_name: edgex-redis + environment: + CLIENTS_COMMAND_HOST: edgex-core-command + CLIENTS_COREDATA_HOST: edgex-core-data + CLIENTS_DATA_HOST: edgex-core-data + CLIENTS_METADATA_HOST: edgex-core-metadata + CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications + CLIENTS_RULESENGINE_HOST: edgex-kuiper + CLIENTS_SCHEDULER_HOST: edgex-support-scheduler + CLIENTS_VIRTUALDEVICE_HOST: edgex-device-virtual + DATABASES_PRIMARY_HOST: edgex-redis + EDGEX_SECURITY_SECRET_STORE: "false" + REGISTRY_HOST: edgex-core-consul + hostname: edgex-redis + image: redis:6.0.9-alpine + networks: + edgex-network: {} + ports: + - 127.0.0.1:6379:6379/tcp + read_only: true + volumes: + - db-data:/data:z + device-mqtt: + container_name: edgex-device-mqtt + depends_on: + consul: + condition: service_started + data: + condition: service_started + metadata: + condition: service_started + environment: + CLIENTS_COMMAND_HOST: edgex-core-command + CLIENTS_COREDATA_HOST: edgex-core-data + CLIENTS_DATA_HOST: edgex-core-data + CLIENTS_METADATA_HOST: edgex-core-metadata + CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications + CLIENTS_RULESENGINE_HOST: edgex-kuiper + CLIENTS_SCHEDULER_HOST: edgex-support-scheduler + CLIENTS_VIRTUALDEVICE_HOST: edgex-device-virtual + DATABASES_PRIMARY_HOST: edgex-redis + DRIVER_INCOMINGHOST: edgex-mqtt-broker + DRIVER_RESPONSEHOST: edgex-mqtt-broker + EDGEX_SECURITY_SECRET_STORE: "false" + REGISTRY_HOST: edgex-core-consul + SERVICE_HOST: edgex-device-mqtt + hostname: edgex-device-mqtt + image: edgexfoundry/docker-device-mqtt-go:1.3.0 + networks: + edgex-network: {} + ports: + - 127.0.0.1:49982:49982/tcp + read_only: true + device-virtual: + container_name: edgex-device-virtual + depends_on: + consul: + condition: service_started + data: + condition: service_started + metadata: + condition: service_started + environment: + CLIENTS_COMMAND_HOST: edgex-core-command + CLIENTS_COREDATA_HOST: edgex-core-data + CLIENTS_DATA_HOST: edgex-core-data + CLIENTS_METADATA_HOST: edgex-core-metadata + CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications + CLIENTS_RULESENGINE_HOST: edgex-kuiper + CLIENTS_SCHEDULER_HOST: edgex-support-scheduler + CLIENTS_VIRTUALDEVICE_HOST: edgex-device-virtual + DATABASES_PRIMARY_HOST: edgex-redis + EDGEX_SECURITY_SECRET_STORE: "false" + REGISTRY_HOST: edgex-core-consul + SERVICE_HOST: edgex-device-virtual + hostname: edgex-device-virtual + image: edgexfoundry/docker-device-virtual-go:1.3.0 + networks: + edgex-network: {} + ports: + - 127.0.0.1:49990:49990/tcp + edgex-proxy: + container_name: edgex-proxy + depends_on: + consul: + condition: service_started + kong: + condition: service_started + vault-worker: + condition: service_started + entrypoint: '/bin/sh -c "until /consul/scripts/consul-svc-healthy.sh kong; do + sleep 1; done; until /consul/scripts/consul-svc-healthy.sh security-secretstore-setup; + do sleep 1; done; /edgex/security-proxy-setup --init=true" + + ' + environment: + CLIENTS_COMMAND_HOST: edgex-core-command + CLIENTS_COREDATA_HOST: edgex-core-data + CLIENTS_DATA_HOST: edgex-core-data + CLIENTS_METADATA_HOST: edgex-core-metadata + CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications + CLIENTS_RULESENGINE_HOST: edgex-kuiper + CLIENTS_SCHEDULER_HOST: edgex-support-scheduler + CLIENTS_VIRTUALDEVICE_HOST: edgex-device-virtual + DATABASES_PRIMARY_HOST: edgex-redis + EDGEX_SECURITY_SECRET_STORE: "true" + KONGURL_SERVER: kong + REGISTRY_HOST: edgex-core-consul + SECRETSERVICE_CACERTPATH: /tmp/edgex/secrets/ca/ca.pem + SECRETSERVICE_SERVER: edgex-vault + SECRETSERVICE_SNIS: edgex-kong + SECRETSERVICE_TOKENPATH: /tmp/edgex/secrets/edgex-security-proxy-setup/secrets-token.json + SECRETSTORE_HOST: edgex-vault + SECRETSTORE_ROOTCACERTPATH: /tmp/edgex/secrets/ca/ca.pem + SECRETSTORE_SERVERNAME: edgex-vault + ADD_PROXY_ROUTE: filtering.http://air-app-service-filtering:48530,flogorules.http://air-app-service-flogo-rules:48095,inferencing.http://air-app-service-inferencing:48525 + hostname: edgex-proxy + image: edgexfoundry/docker-security-proxy-setup-go:1.3.0 + networks: + edgex-network: {} + read_only: true + volumes: + - consul-scripts:/consul/scripts:ro,z + - /tmp/edgex/secrets/ca:/tmp/edgex/secrets/ca:ro,z + - /tmp/edgex/secrets/edgex-security-proxy-setup:/tmp/edgex/secrets/edgex-security-proxy-setup:ro,z + kong: + command: '/bin/sh -c "until /consul/scripts/consul-svc-healthy.sh kong-migrations; + do sleep 1; done; /docker-entrypoint.sh kong docker-start" + + ' + container_name: kong + depends_on: + consul: + condition: service_started + kong-db: + condition: service_started + kong-migrations: + condition: service_started + environment: + KONG_ADMIN_ACCESS_LOG: /dev/stdout + KONG_ADMIN_ERROR_LOG: /dev/stderr + KONG_ADMIN_LISTEN: 0.0.0.0:8001, 0.0.0.0:8444 ssl + KONG_DATABASE: postgres + KONG_PG_HOST: kong-db + KONG_PG_PASSWORD: kong + KONG_PROXY_ACCESS_LOG: /dev/stdout + KONG_PROXY_ERROR_LOG: /dev/stderr + hostname: kong + image: kong:2.0.5 + networks: + edgex-network: {} + ports: + - published: 8000 + target: 8000 + - 127.0.0.1:8001:8001/tcp + - published: 8443 + target: 8443 + - 127.0.0.1:8444:8444/tcp + read_only: true + restart: on-failure + tmpfs: + - /run + - /tmp + tty: true + volumes: + - consul-scripts:/consul/scripts:ro,z + - kong:/usr/local/kong:rw + kong-db: + container_name: kong-db + depends_on: + security-secrets-setup: + condition: service_started + environment: + POSTGRES_DB: kong + POSTGRES_PASSWORD: kong + POSTGRES_USER: kong + hostname: kong-db + image: postgres:12.3-alpine + networks: + edgex-network: {} + ports: + - 127.0.0.1:5432:5432/tcp + read_only: true + tmpfs: + - /var/run + - /tmp + - /run + volumes: + - postgres-data:/var/lib/postgresql/data:z + kong-migrations: + command: "/bin/sh -cx 'until /consul/scripts/consul-svc-healthy.sh kong-db;\n\ + \ do sleep 1;\ndone && kong migrations bootstrap; kong migrations list; code=$$?;\ + \ if [ $$code -eq 5 ]; then\n kong migrations up && kong migrations finish;\n\ + fi'\n" + container_name: kong-migrations + depends_on: + consul: + condition: service_started + kong-db: + condition: service_started + environment: + KONG_DATABASE: postgres + KONG_PG_HOST: kong-db + KONG_PG_PASSWORD: kong + image: kong:2.0.5 + networks: + edgex-network: {} + read_only: true + tmpfs: + - /tmp + volumes: + - consul-scripts:/consul/scripts:ro,z + metadata: + container_name: edgex-core-metadata + depends_on: + consul: + condition: service_started + database: + condition: service_started + notifications: + condition: service_started + security-bootstrap-database: + condition: service_started + vault-worker: + condition: service_started + environment: + CLIENTS_COMMAND_HOST: edgex-core-command + CLIENTS_COREDATA_HOST: edgex-core-data + CLIENTS_DATA_HOST: edgex-core-data + CLIENTS_METADATA_HOST: edgex-core-metadata + CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications + CLIENTS_RULESENGINE_HOST: edgex-kuiper + CLIENTS_SCHEDULER_HOST: edgex-support-scheduler + CLIENTS_VIRTUALDEVICE_HOST: edgex-device-virtual + DATABASES_PRIMARY_HOST: edgex-redis + EDGEX_SECURITY_SECRET_STORE: "true" + NOTIFICATIONS_SENDER: edgex-core-metadata + REGISTRY_HOST: edgex-core-consul + SECRETSTORE_HOST: edgex-vault + SECRETSTORE_ROOTCACERTPATH: /tmp/edgex/secrets/ca/ca.pem + SECRETSTORE_SERVERNAME: edgex-vault + SECRETSTORE_TOKENFILE: /tmp/edgex/secrets/edgex-core-metadata/secrets-token.json + SERVICE_HOST: edgex-core-metadata + hostname: edgex-core-metadata + image: edgexfoundry/docker-core-metadata-go:1.3.0 + networks: + edgex-network: {} + ports: + - 127.0.0.1:48081:48081/tcp + read_only: true + volumes: + - /tmp/edgex/secrets/ca:/tmp/edgex/secrets/ca:ro,z + - /tmp/edgex/secrets/edgex-core-metadata:/tmp/edgex/secrets/edgex-core-metadata:ro,z + mqtt-broker: + container_name: edgex-mqtt-broker + hostname: edgex-mqtt-broker + image: eclipse-mosquitto:1.6.3 + networks: + edgex-network: {} + ports: + - published: 1883 + target: 1883 + read_only: true + # mqtt-broker: + # container_name: edgex-mqtt-broker + # hostname: edgex-mqtt-broker + # image: emqx/emqx:4.2.7 + # networks: + # edgex-network: {} + # ports: + # - published: 1883 + # target: 1883 + # - published: 8083 + # target: 8083 + # - published: 8883 + # target: 8883 + # - published: 8084 + # target: 8084 + # - published: 18083 + # target: 18083 + # environment: + # EMQX_NAME: emqx + # EMQX_LOG__LEVEL: debug + # EMQX_HOST: edgex-mqtt-broker + + notifications: + container_name: edgex-support-notifications + depends_on: + consul: + condition: service_started + database: + condition: service_started + security-bootstrap-database: + condition: service_started + vault-worker: + condition: service_started + environment: + CLIENTS_COMMAND_HOST: edgex-core-command + CLIENTS_COREDATA_HOST: edgex-core-data + CLIENTS_DATA_HOST: edgex-core-data + CLIENTS_METADATA_HOST: edgex-core-metadata + CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications + CLIENTS_RULESENGINE_HOST: edgex-kuiper + CLIENTS_SCHEDULER_HOST: edgex-support-scheduler + CLIENTS_VIRTUALDEVICE_HOST: edgex-device-virtual + DATABASES_PRIMARY_HOST: edgex-redis + EDGEX_SECURITY_SECRET_STORE: "true" + REGISTRY_HOST: edgex-core-consul + SECRETSTORE_HOST: edgex-vault + SECRETSTORE_ROOTCACERTPATH: /tmp/edgex/secrets/ca/ca.pem + SECRETSTORE_SERVERNAME: edgex-vault + SECRETSTORE_TOKENFILE: /tmp/edgex/secrets/edgex-support-notifications/secrets-token.json + SERVICE_HOST: edgex-support-notifications + hostname: edgex-support-notifications + image: edgexfoundry/docker-support-notifications-go:1.3.0 + networks: + edgex-network: {} + ports: + - 127.0.0.1:48060:48060/tcp + read_only: true + volumes: + - /tmp/edgex/secrets/ca:/tmp/edgex/secrets/ca:ro,z + - /tmp/edgex/secrets/edgex-support-notifications:/tmp/edgex/secrets/edgex-support-notifications:ro,z + rulesengine: + container_name: edgex-kuiper + depends_on: + app-service-rules: + condition: service_started + mqtt-broker: + condition: service_started + environment: + EDGEX__DEFAULT__OPTIONAL__CLIENTID: kuiper-rules-engine + EDGEX__DEFAULT__PORT: 1883 + EDGEX__DEFAULT__PROTOCOL: tcp + EDGEX__DEFAULT__SERVER: edgex-mqtt-broker + EDGEX__DEFAULT__SERVICESERVER: http://edgex-core-data:48080 + EDGEX__DEFAULT__TOPIC: rules-events + EDGEX__DEFAULT__TYPE: mqtt + KUIPER__BASIC__CONSOLELOG: "true" + KUIPER__BASIC__RESTPORT: 48075 + hostname: edgex-kuiper + image: emqx/kuiper:1.0.0-alpine + networks: + edgex-network: {} + ports: + - 127.0.0.1:20498:20498/tcp + - 127.0.0.1:48075:48075/tcp + scheduler: + container_name: edgex-support-scheduler + depends_on: + consul: + condition: service_started + database: + condition: service_started + security-bootstrap-database: + condition: service_started + vault-worker: + condition: service_started + environment: + CLIENTS_COMMAND_HOST: edgex-core-command + CLIENTS_COREDATA_HOST: edgex-core-data + CLIENTS_DATA_HOST: edgex-core-data + CLIENTS_METADATA_HOST: edgex-core-metadata + CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications + CLIENTS_RULESENGINE_HOST: edgex-kuiper + CLIENTS_SCHEDULER_HOST: edgex-support-scheduler + CLIENTS_VIRTUALDEVICE_HOST: edgex-device-virtual + DATABASES_PRIMARY_HOST: edgex-redis + EDGEX_SECURITY_SECRET_STORE: "true" + INTERVALACTIONS_SCRUBAGED_HOST: edgex-core-data + INTERVALACTIONS_SCRUBPUSHED_HOST: edgex-core-data + REGISTRY_HOST: edgex-core-consul + SECRETSTORE_HOST: edgex-vault + SECRETSTORE_ROOTCACERTPATH: /tmp/edgex/secrets/ca/ca.pem + SECRETSTORE_SERVERNAME: edgex-vault + SECRETSTORE_TOKENFILE: /tmp/edgex/secrets/edgex-support-scheduler/secrets-token.json + SERVICE_HOST: edgex-support-scheduler + hostname: edgex-support-scheduler + image: edgexfoundry/docker-support-scheduler-go:1.3.0 + networks: + edgex-network: {} + ports: + - 127.0.0.1:48085:48085/tcp + read_only: true + volumes: + - /tmp/edgex/secrets/ca:/tmp/edgex/secrets/ca:ro,z + - /tmp/edgex/secrets/edgex-support-scheduler:/tmp/edgex/secrets/edgex-support-scheduler:ro,z + security-bootstrap-database: + container_name: edgex-security-bootstrap-database + depends_on: + database: + condition: service_started + vault-worker: + condition: service_started + environment: + CLIENTS_COMMAND_HOST: edgex-core-command + CLIENTS_COREDATA_HOST: edgex-core-data + CLIENTS_DATA_HOST: edgex-core-data + CLIENTS_METADATA_HOST: edgex-core-metadata + CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications + CLIENTS_RULESENGINE_HOST: edgex-kuiper + CLIENTS_SCHEDULER_HOST: edgex-support-scheduler + CLIENTS_VIRTUALDEVICE_HOST: edgex-device-virtual + DATABASES_PRIMARY_HOST: edgex-redis + EDGEX_SECURITY_SECRET_STORE: "true" + REGISTRY_HOST: edgex-core-consul + SECRETSTORE_HOST: edgex-vault + SECRETSTORE_ROOTCACERTPATH: /tmp/edgex/secrets/ca/ca.pem + SECRETSTORE_SERVERNAME: edgex-vault + SECRETSTORE_TOKENFILE: /tmp/edgex/secrets/edgex-security-bootstrap-redis/secrets-token.json + SERVICE_HOST: edgex-security-bootstrap-database + hostname: edgex-security-bootstrap-database + image: edgexfoundry/docker-security-bootstrap-redis-go:1.3.0 + networks: + edgex-network: {} + read_only: true + tmpfs: + - /run + - /vault + volumes: + - /tmp/edgex/secrets/ca:/tmp/edgex/secrets/ca:ro,z + - /tmp/edgex/secrets/edgex-security-bootstrap-redis:/tmp/edgex/secrets/edgex-security-bootstrap-redis:ro,z + security-secrets-setup: + command: generate + container_name: edgex-secrets-setup + hostname: edgex-secrets-setup + image: edgexfoundry/docker-security-secrets-setup-go:1.3.0 + read_only: true + tmpfs: + - /tmp + - /run + volumes: + - secrets-setup-cache:/etc/edgex/pki:rw + - /tmp/edgex/secrets:/tmp/edgex/secrets:z + - vault-init:/vault/init:z + system: + container_name: edgex-sys-mgmt-agent + depends_on: + command: + condition: service_started + consul: + condition: service_started + data: + condition: service_started + metadata: + condition: service_started + notifications: + condition: service_started + scheduler: + condition: service_started + environment: + CLIENTS_COMMAND_HOST: edgex-core-command + CLIENTS_COREDATA_HOST: edgex-core-data + CLIENTS_DATA_HOST: edgex-core-data + CLIENTS_METADATA_HOST: edgex-core-metadata + CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications + CLIENTS_RULESENGINE_HOST: edgex-kuiper + CLIENTS_SCHEDULER_HOST: edgex-support-scheduler + CLIENTS_VIRTUALDEVICE_HOST: edgex-device-virtual + DATABASES_PRIMARY_HOST: edgex-redis + EDGEX_SECURITY_SECRET_STORE: "false" + EXECUTORPATH: /sys-mgmt-executor + METRICSMECHANISM: executor + REGISTRY_HOST: edgex-core-consul + SERVICE_HOST: edgex-sys-mgmt-agent + hostname: edgex-sys-mgmt-agent + image: edgexfoundry/docker-sys-mgmt-agent-go:1.3.0 + networks: + edgex-network: {} + ports: + - 127.0.0.1:48090:48090/tcp + read_only: true + volumes: + - /var/run/docker.sock:/var/run/docker.sock:z + vault: + cap_add: + - IPC_LOCK + container_name: edgex-vault + depends_on: + consul: + condition: service_started + security-secrets-setup: + condition: service_started + entrypoint: + - /vault/init/start_vault.sh + environment: + VAULT_ADDR: https://edgex-vault:8200 + VAULT_CONFIG_DIR: /vault/config + VAULT_UI: "true" + hostname: edgex-vault + image: vault:1.5.3 + networks: + edgex-network: {} + ports: + - 127.0.0.1:8200:8200/tcp + tmpfs: + - /vault/config + volumes: + - /tmp/edgex/secrets/edgex-vault:/tmp/edgex/secrets/edgex-vault:ro,z + - vault-file:/vault/file:z + - vault-init:/vault/init:ro,z + - vault-logs:/vault/logs:z + vault-worker: + container_name: edgex-vault-worker + depends_on: + consul: + condition: service_started + security-secrets-setup: + condition: service_started + vault: + condition: service_started + environment: + SECRETSTORE_SETUP_DONE_FLAG: /tmp/edgex/secrets/edgex-consul/.secretstore-setup-done + hostname: edgex-vault-worker + image: edgexfoundry/docker-security-secretstore-setup-go:1.3.0 + networks: + edgex-network: {} + read_only: true + tmpfs: + - /run + - /vault + volumes: + - consul-scripts:/consul/scripts:ro,z + - /tmp/edgex/secrets:/tmp/edgex/secrets:z + - vault-config:/vault/config:z + +################################################################# +# TIBCO Labs Air +################################################################# + + service-metadata: + image: public.ecr.aws/tibcolabs/labs-air-edgex-app-service-metadata:1.3.0 + ports: + - "48530:48530" + container_name: air-app-service-metadata + hostname: air-app-service-metadata + networks: + - edgex-network + environment: + REGISTRY_HOST: edgex-core-consul + CLIENTS_COREDATA_HOST: edgex-core-data + CLIENTS_DATA_HOST: edgex-core-data + CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications + CLIENTS_METADATA_HOST: edgex-core-metadata + CLIENTS_COMMAND_HOST: edgex-core-command + CLIENTS_SCHEDULER_HOST: edgex-support-scheduler + CLIENTS_VIRTUALDEVICE_HOST: edgex-device-virtual + DATABASES_PRIMARY_HOST: edgex-redis + EDGEX_SECURITY_SECRET_STORE: "false" + BINDING_TYPE: edgex-messagebus + BINDING_SUBSCRIBETOPIC: events + BINDING_PUBLISHTOPIC: edgexevents + MESSAGEBUS_TYPE: mqtt + MESSAGEBUS_SUBSCRIBEHOST_HOST: edgex-mqtt-broker + MESSAGEBUS_SUBSCRIBEHOST_PORT: 1883 + MESSAGEBUS_PUBLISHHOST_HOST: edgex-mqtt-broker + MESSAGEBUS_PUBLISHHOST_PORT: 1883 + MESSAGEBUS_OPTIONAL_USERNAME: + MESSAGEBUS_OPTIONAL_PASSWORD: + MESSAGEBUS_OPTIONAL_CLIENTID: air-app-service-metadata + SERVICE_HOST: air-app-service-metadata + SERVICE_PORT: 48530 + WRITABLE_LOGLEVEL: DEBUG + ApplicationSettings_MqttHostname: ${AIR_MQTT_HOSTNAME} + ApplicationSettings_MqttPort: ${AIR_MQTT_PORT} + ApplicationSettings_MqttUser: ${AIR_MQTT_USER} + ApplicationSettings_MqttPassword: ${AIR_MQTT_PASSWORD} + ApplicationSettings_MqttTopic: ${AIR_MQTT_DATA_TOPIC} + ApplicationSettings_MqttPublisher: ${AIR_MQTT_DATA_METADATA_PUBLISHER} + ApplicationSettings_GatewayId: ${GATEWAY_ID} + ApplicationSettings_GatewayDescription: ${GATEWAY_DESCRIPTION} + ApplicationSettings_GatewayHostname: ${GATEWAY_HOSTNAME} + ApplicationSettings_GatewayRouter: ${GATEWAY_ROUTER} + ApplicationSettings_GatewayRouterPort: ${GATEWAY_ROUTER_PORT} + ApplicationSettings_GatewayDeployNetwork: ${GATEWAY_DEPLOY_NETWORK} + ApplicationSettings_GatewayLatitude: ${GATEWAY_LATITUDE} + ApplicationSettings_GatewayLongitude: ${GATEWAY_LONGITUDE} + ApplicationSettings_GatewayAccessToken: ${EDGEX_TOKEN} + ApplicationSettings_GatewayUsername: ${GATEWAY_USERNAME} + ApplicationSettings_GatewayPlatform: ${GATEWAY_PLATFORM} + ApplicationSettings_MetadataClient: ${GATEWAY_METADATA_CLIENT} + ApplicationSettings_MetadataPublishIntervalSecs: ${GATEWAY_METADATA_PUBLISH_INTERVAL_SECS} + volumes: + - db-data:/data/db + - log-data:/edgex/logs + - consul-config:/consul/config + - consul-data:/consul/data + depends_on: + - data + - command + - mqtt-broker + + device-siemens-simulator: + image: public.ecr.aws/tibcolabs/labs-air-edgex-device-siemens-simulator:1.3.0 + ports: + - "48995:48995" + container_name: labsair-device-siemens-simulator + hostname: labsair-device-siemens-simulator + networks: + - edgex-network + environment: + REGISTRY_HOST: edgex-core-consul + CLIENTS_COREDATA_HOST: edgex-core-data + CLIENTS_DATA_HOST: edgex-core-data + CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications + CLIENTS_METADATA_HOST: edgex-core-metadata + CLIENTS_COMMAND_HOST: edgex-core-command + CLIENTS_SCHEDULER_HOST: edgex-support-scheduler + CLIENTS_VIRTUALDEVICE_HOST: edgex-device-virtual + DATABASES_PRIMARY_HOST: edgex-redis + EDGEX_SECURITY_SECRET_STORE: "false" + SERVICE_HOST: labsair-device-siemens-simulator + SERVICE_PORT: 48995 + volumes: + - db-data:/data/db + - log-data:/edgex/logs + - consul-config:/consul/config + - consul-data:/consul/data + depends_on: + data: + condition: service_started + command: + condition: service_started + + device-generic-mqtt: + image: public.ecr.aws/tibcolabs/labs-air-edgex-device-generic-mqtt:1.3.0 + ports: + - "49560:49560" + container_name: labsair-device-generic-mqtt + hostname: labsair-device-generic-mqtt + networks: + - edgex-network + environment: + REGISTRY_HOST: edgex-core-consul + CLIENTS_COREDATA_HOST: edgex-core-data + CLIENTS_DATA_HOST: edgex-core-data + CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications + CLIENTS_METADATA_HOST: edgex-core-metadata + CLIENTS_COMMAND_HOST: edgex-core-command + DATABASES_PRIMARY_HOST: edgex-redis + EDGEX_SECURITY_SECRET_STORE: "false" + SERVICE_HOST: labsair-device-generic-mqtt + SERVICE_PORT: 49560 + DEVICE_PROFILESDIR: /res + Driver_IncomingHost: ${AIR_MQTT_HOSTNAME} + Driver_IncomingPort: ${AIR_MQTT_PORT} + Driver_IncomingClientId: ${AIR_GENERIC_MQTT_CLIENT_ID} + Driver_ResponseHost: ${AIR_MQTT_HOSTNAME} + Driver_ResponsePort: ${AIR_MQTT_PORT} + Driver_ResponseClientId: ${AIR_GENERIC_MQTT_RESPONSE_CLIENT_ID} + volumes: + - db-data:/data/db + - log-data:/edgex/logs + - consul-config:/consul/config + - consul-data:/consul/data + depends_on: + data: + condition: service_started + command: + condition: service_started + metadata: + condition: service_started + + device-generic-rest: + image: public.ecr.aws/tibcolabs/labs-air-edgex-device-generic-rest:1.3.0 + ports: + - "49565:49565" + container_name: labsair-device-generic-rest + hostname: labsair-device-generic-rest + networks: + - edgex-network + environment: + REGISTRY_HOST: edgex-core-consul + CLIENTS_COREDATA_HOST: edgex-core-data + CLIENTS_DATA_HOST: edgex-core-data + CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications + CLIENTS_METADATA_HOST: edgex-core-metadata + CLIENTS_COMMAND_HOST: edgex-core-command + DATABASES_PRIMARY_HOST: edgex-redis + EDGEX_SECURITY_SECRET_STORE: "false" + SERVICE_HOST: labsair-device-generic-rest + SERVICE_PORT: 49565 + DEVICE_PROFILESDIR: /res + volumes: + - db-data:/data/db + - log-data:/edgex/logs + - consul-config:/consul/config + - consul-data:/consul/data + depends_on: + data: + condition: service_started + command: + condition: service_started + metadata: + condition: service_started + + diff --git a/scripts/linux/basicdemo/getSecurityToken.sh b/scripts/linux/basicdemo/getSecurityToken.sh new file mode 100755 index 0000000..3e0dcb3 --- /dev/null +++ b/scripts/linux/basicdemo/getSecurityToken.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +docker-compose run --rm --entrypoint /edgex/security-proxy-setup edgex-proxy --init=false --useradd=tibuser --group=admin \ No newline at end of file diff --git a/scripts/linux/basicdemo/start.cmd b/scripts/linux/basicdemo/start.cmd new file mode 100644 index 0000000..9ce283c --- /dev/null +++ b/scripts/linux/basicdemo/start.cmd @@ -0,0 +1,86 @@ +@echo off +set COMPOSE_FILE=docker-compose.yml +echo "Using compose file: %COMPOSE_FILE%" + + + +set EDGEX_TOKEN="" + +echo " Starting.. %COMPOSE_FILE%" +docker-compose -f "%COMPOSE_FILE%" up -d security-secrets-setup + +docker-compose -f "%COMPOSE_FILE%" up -d consul + +docker-compose -f "%COMPOSE_FILE%" up -d database + +docker-compose -f "%COMPOSE_FILE%" up -d notifications + +docker-compose -f "%COMPOSE_FILE%" up -d metadata + +docker-compose -f "%COMPOSE_FILE%" up -d vault-worker + +docker-compose -f "%COMPOSE_FILE%" up -d security-bootstrap-database + +docker-compose -f "%COMPOSE_FILE%" up -d mqtt-broker + +docker-compose -f "%COMPOSE_FILE%" up -d vault + +docker-compose -f "%COMPOSE_FILE%" up -d kong-db + +docker-compose -f "%COMPOSE_FILE%" up -d kong-migration +timeout 2 + +docker-compose -f "%COMPOSE_FILE%" up -d kong +timeout 5 + +docker-compose -f "%COMPOSE_FILE%" up -d edgex-proxy + +docker-compose -f "%COMPOSE_FILE%" up -d scheduler + +docker-compose -f "%COMPOSE_FILE%" up -d system + +docker-compose -f "%COMPOSE_FILE%" up -d data + +docker-compose -f "%COMPOSE_FILE%" up -d command + + +echo "Replacing token!!" +docker-compose run --rm --entrypoint /edgex/security-proxy-setup edgex-proxy --init=false --useradd=tibuser --group=admin > secToken.txt +findstr /B /C:"the access token for user tibuser is" secToken.txt > setTokenLine.txt +FOR /F "tokens=8 delims= " %%G IN (setTokenLine.txt) DO (set token=%%G) +set EDGEX_TOKEN=%token% + + +docker-compose -f "%COMPOSE_FILE%" up -d service-metadata +timeout 5 + +::KEEP DISSABLED: run_service device-siemens-simulator + +docker-compose -f "%COMPOSE_FILE%" up -d device-generic-mqtt + +docker-compose -f "%COMPOSE_FILE%" up -device-generic-rest + +echo "\n" +echo "Installing cors plugin" +curl -X POST http://localhost:8001/plugins/ ^ +--data "name=cors" ^ +--data "config.origins=http://ui.oss.labs.air" ^ +--data "config.methods=GET" ^ +--data "config.methods=POST" ^ +--data "config.methods=OPTIONS" ^ +--data "config.headers=Accept" ^ +--data "config.headers=Accept-Version" ^ +--data "config.headers=Content-Length" ^ +--data "config.headers=Content-MD5" ^ +--data "config.headers=Content-Type" ^ +--data "config.headers=Date" ^ +--data "config.headers=X-Auth-Token" ^ +--data "config.headers=Authorization" ^ +--data "config.exposed_headers=X-Auth-Token" ^ +--data "config.credentials=true" ^ +--data "config.max_age=3600" + + +echo "\n" +echo -e " All services started. Edgex is ready " + diff --git a/scripts/linux/basicdemo/start.sh b/scripts/linux/basicdemo/start.sh new file mode 100755 index 0000000..1516ab4 --- /dev/null +++ b/scripts/linux/basicdemo/start.sh @@ -0,0 +1,140 @@ +#!/bin/bash + +# Copyright 2017 Konrad Zapalowicz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Start EdgeX Foundry services in right order, as described: +# https://wiki.edgexfoundry.org/display/FA/Get+EdgeX+Foundry+-+Users + +COMPOSE_FILE=${1:-docker-compose.yml} +echo "Using compose file: $COMPOSE_FILE" + +run_service () { + echo " \n " + echo -e "\033[0;32mStarting.. $1\033[0m" + docker-compose -f "$COMPOSE_FILE" up -d $1 + + if [ "$1" = "config-seed" ] + then + while [ -z "$(curl -s http://localhost:8500/v1/kv/config/device-virtual\;docker/app.open.msg)" ] + do + sleep 1 + done + echo "$1 has been completely started !" + return + fi + + if [ -z "$2" ] + then + sleep 1 + echo "$1 has been completely started !" + return + fi + + if [ -n "$2" ] + then + sleep $2 + echo "$1 has been completely started !" + return + fi +} + +replace_token () { + echo "Replacing token!!" + docker-compose run --rm --entrypoint /edgex/security-proxy-setup edgex-proxy --init=false --useradd=tibuser --group=admin > secToken.txt + awk '/the access token for user tibuser is: / {print $8}' secToken.txt | cut -c1-164 > secTokenString.txt + value=`cat secTokenString.txt` + export EDGEX_TOKEN=${value} +} + + + +install_cors_plugin () { + echo "\n" + echo "Installing cors plugin" + curl -X POST http://localhost:8001/plugins/ \ + --data "name=cors" \ + --data "config.origins=http://ui.oss.labs.air" \ + --data "config.methods=GET" \ + --data "config.methods=POST" \ + --data "config.methods=OPTIONS" \ + --data "config.headers=Accept" \ + --data "config.headers=Accept-Version" \ + --data "config.headers=Content-Length" \ + --data "config.headers=Content-MD5" \ + --data "config.headers=Content-Type" \ + --data "config.headers=Date" \ + --data "config.headers=X-Auth-Token" \ + --data "config.headers=Authorization" \ + --data "config.exposed_headers=X-Auth-Token" \ + --data "config.credentials=true" \ + --data "config.max_age=3600" +} + +export EDGEX_TOKEN="" + +run_service security-secrets-setup + +run_service consul + +run_service database + +run_service notifications + +run_service metadata + +run_service vault-worker + +run_service security-bootstrap-database + +run_service mqtt-broker + +run_service vault + +run_service kong-db + +run_service kong-migrations 2 + +run_service kong 5 + +run_service edgex-proxy + +run_service scheduler + +run_service system + + +run_service data + +run_service command + + + +replace_token + +run_service service-metadata 5 + +#run_service device-siemens-simulator + +run_service device-generic-mqtt + +run_service device-generic-rest + +install_cors_plugin + + +echo "\n" +echo -e "\033[0;32m All services started. Edgex is ready\033[0m" + diff --git a/scripts/linux/basicdemo/startEdgeAIR.sh b/scripts/linux/basicdemo/startEdgeAIR.sh new file mode 100755 index 0000000..b3916bd --- /dev/null +++ b/scripts/linux/basicdemo/startEdgeAIR.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +# Copyright 2017 Konrad Zapalowicz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Start EdgeX Foundry services in right order, as described: +# https://wiki.edgexfoundry.org/display/FA/Get+EdgeX+Foundry+-+Users + +COMPOSE_FILE=${1:-docker-compose.yml} +echo "Using compose file: $COMPOSE_FILE" + +run_service () { + echo "\n" + echo -e "\033[0;32mStarting.. $1\033[0m" + docker-compose -f "$COMPOSE_FILE" up -d $1 + + if [ "$1" = "config-seed" ] + then + while [ -z "$(curl -s http://localhost:8500/v1/kv/config/device-virtual\;docker/app.open.msg)" ] + do + sleep 1 + done + echo "$1 has been completely started !" + return + fi + + if [ -z "$2" ] + then + sleep 1 + echo "$1 has been completely started !" + return + fi + + if [ -n "$2" ] + then + sleep $2 + echo "$1 has been completely started !" + return + fi +} + + + + +run_service service-metadata 5 + +run_service device-siemens-simulator + + +echo "\n" +echo -e "\033[0;32m All services started. Edgex is ready\033[0m" + diff --git a/scripts/linux/basicdemo/startEdgex.sh b/scripts/linux/basicdemo/startEdgex.sh new file mode 100755 index 0000000..ab5dcbb --- /dev/null +++ b/scripts/linux/basicdemo/startEdgex.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# Copyright 2017 Konrad Zapalowicz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Start EdgeX Foundry services in right order, as described: +# https://wiki.edgexfoundry.org/display/FA/Get+EdgeX+Foundry+-+Users + +COMPOSE_FILE=${1:-docker-compose.yml} +echo "Using compose file: $COMPOSE_FILE" + +run_service () { + echo "\n" + echo -e "\033[0;32mStarting.. $1\033[0m" + docker-compose -f "$COMPOSE_FILE" up -d $1 + + if [ "$1" = "config-seed" ] + then + while [ -z "$(curl -s http://localhost:8500/v1/kv/config/device-virtual\;docker/app.open.msg)" ] + do + sleep 1 + done + echo "$1 has been completely started !" + return + fi + + if [ -z "$2" ] + then + sleep 1 + echo "$1 has been completely started !" + return + fi + + if [ -n "$2" ] + then + sleep $2 + echo "$1 has been completely started !" + return + fi +} + +run_service security-secrets-setup + +run_service consul + +run_service database + +run_service notifications + +run_service metadata + +run_service vault-worker + +run_service security-bootstrap-database + +run_service mqtt-broker + +run_service vault + +run_service kong-db + +run_service kong-migrations 2 + +run_service kong 5 + +run_service edgex-proxy + +run_service scheduler + +run_service system + + +run_service data + +run_service command + + +echo "\n" +echo -e "\033[0;32m All services started. Edgex is ready\033[0m" + diff --git a/scripts/linux/basicdemo/start_minikube.cmd b/scripts/linux/basicdemo/start_minikube.cmd new file mode 100644 index 0000000..f3692a2 --- /dev/null +++ b/scripts/linux/basicdemo/start_minikube.cmd @@ -0,0 +1,4 @@ +@echo off +FOR /F "tokens=1 delims= " %%G IN ('minikube ip') DO (set minikube_ip=%%G) +set AIR_MQTT_HOSTNAME=%minikube_ip% +call start.cmd diff --git a/scripts/linux/basicdemo/start_minikube.sh b/scripts/linux/basicdemo/start_minikube.sh new file mode 100755 index 0000000..15ae5d4 --- /dev/null +++ b/scripts/linux/basicdemo/start_minikube.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +minikube_ip=$(minikube ip) +export AIR_MQTT_HOSTNAME=${MQTT_HOSTNAME:-$minikube_ip} +source ./start.sh diff --git a/scripts/linux/basicdemo/stop.cmd b/scripts/linux/basicdemo/stop.cmd new file mode 100644 index 0000000..09209a9 --- /dev/null +++ b/scripts/linux/basicdemo/stop.cmd @@ -0,0 +1,3 @@ +@echo off + +docker-compose down -v diff --git a/scripts/linux/basicdemo/stop.sh b/scripts/linux/basicdemo/stop.sh new file mode 100755 index 0000000..62f0ac9 --- /dev/null +++ b/scripts/linux/basicdemo/stop.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +docker-compose down -v diff --git a/scripts/linux/basicdemo/stopEdgex.sh b/scripts/linux/basicdemo/stopEdgex.sh new file mode 100755 index 0000000..62f0ac9 --- /dev/null +++ b/scripts/linux/basicdemo/stopEdgex.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +docker-compose down -v diff --git a/scripts/push_image.sh b/scripts/push_image.sh new file mode 100755 index 0000000..4cd9543 --- /dev/null +++ b/scripts/push_image.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +. scripts/tools.sh + +image_name=$1 +image_tag=$2 +image_url=$3 + +push_image $image_name $image_tag $image_url \ No newline at end of file diff --git a/scripts/tools.sh b/scripts/tools.sh new file mode 100755 index 0000000..a70f8dc --- /dev/null +++ b/scripts/tools.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +function build_image(){ + local build_image_name=$1 + local build_image_tag=$2 + local build_image_url=$3 + local build_docker_file=$4 + local build_target_name=$5 + echo "Building image ${build_image_name}:${build_image_tag}..." + if [ -z ${build_target_name} ]; then + docker build --no-cache -t ${build_image_name}:${build_image_tag} -f ${build_docker_file} . || exit 1 + else + docker build --target ${build_target_name} --no-cache -t ${build_image_name}:${build_image_tag} -f ${build_docker_file} . || exit 1 + fi +} + + +function tag_image(){ + local tag_local_image_name=$1 + local tag_local_image_tag=$2 + local tag_new_image_name=$3 + local tag_new_image_tag=$4 + local tag_new_image_url=$5 + echo "Inside tag_image ${tag_new_image_tag}" + for i in $(echo $tag_new_image_tag | sed "s/,/ /g") + do + echo "Tagging image ${tag_local_image_name}:${tag_local_image_tag} to ${tag_new_image_url}/${tag_new_image_name}:${i}" + docker tag ${tag_local_image_name}:${tag_local_image_tag} ${tag_new_image_url}/${tag_new_image_name}:${i} || exit 1 + done +} + +function push_image(){ + local push_image_name=$1 + local push_image_tag=$2 + local push_image_url=$3 + for i in $(echo $push_image_tag | sed "s/,/ /g") + do + echo "Pushing image ${push_image_url}/${push_image_name}:${i}..." + docker push ${push_image_url}/${push_image_name}:${i} || exit 1 + echo "Pushing image finished ..." + done +} + + +function delete_local_image(){ + local delete_image_name=$1 + local delete_image_tag=$2 + local delete_image_url=$3 + echo "Deleting image ..." + docker images -a | grep "${delete_image_name}" | awk '{print $3}' | xargs docker rmi -f || exit 1 + docker rmi -f $(docker images -f "dangling=true" -q) + echo "Deleting image finished ..." +} \ No newline at end of file