Compare commits

..
123 Commits
Author SHA1 Message Date
fengzeroz 746e010e7e Merge pull request #1289 from fengzeroz/v2.4
version: 2.4.7
2023-05-26 15:29:07 +08:00
fengzero 2a292c11f1 version: 2.4.7 2023-05-26 07:26:26 +00:00
hxy7yx b7c35347e2 Merge pull request #1285 from hxy7yx/v2.4
Add node type to /api/v2/metrics
2023-05-24 09:22:32 +08:00
hxy7yx 8dfdcee740 Add node type to /api/v2/metrics 2023-05-23 18:26:44 +08:00
fengzeroz 7166b68293 Merge pull request #1278 from lixiumei123/v2.4
chore(file plugin):update log level
2023-05-19 16:44:11 +08:00
liyuxi c48aebce8d chore(file plugin):update log level 2023-05-19 08:34:33 +00:00
fengzeroz 7dd05b453b Merge pull request #1280 from fengzeroz/v2.4
update log level
2023-05-19 16:14:08 +08:00
fengzero c88b60c8c3 fix(daemon): log format 2023-05-19 08:06:05 +00:00
fengzero 0b75c2afb0 chore(adapter): log level 2023-05-19 08:06:05 +00:00
fengzero 58960f4be6 chore(base): log level 2023-05-19 08:06:05 +00:00
fengzero c9c35e02c6 chore(connection): log level 2023-05-19 08:06:05 +00:00
fengzero e5e421f257 chore(manager): log level 2023-05-19 08:06:05 +00:00
fengzero 4d4887a6e3 chore(event): log level 2023-05-19 08:06:05 +00:00
fengzero d57b95b0a1 chore(log): default notice 2023-05-19 08:06:05 +00:00
fengzeroz c1cafe3fc7 Merge pull request #1276 from hxy7yx/v2.4
chore(modbus):improve log
2023-05-19 16:05:31 +08:00
hxy7yx cdbb511140 chore(modbus):improve log 2023-05-18 15:50:55 +08:00
hxy7yx d2b96f5ca8 Revert "chore(modbus):improve log"
This reverts commit 369e49f7ce.
2023-05-18 15:34:09 +08:00
hxy7yx 6bdf31b660 chore(modbus):improve log 2023-05-18 14:55:31 +08:00
hxy7yx 369e49f7ce chore(modbus):improve log 2023-05-18 14:30:32 +08:00
fengzeroz a2c81d3572 Merge pull request #1273 from eeff/v2.4
Pick to 2.4: update mqtt, monitor, ekuiper, restful log level
2023-05-18 12:32:06 +08:00
fengzeroz d3f0b52fb2 Merge pull request #1274 from fengzeroz/v2.4
chore(log): limit string length
2023-05-17 18:27:10 +08:00
fengzero 82fe13876f chore(log): limit string length 2023-05-17 10:02:00 +00:00
eeff ed01dd060a chore(restful): update log level 2023-05-17 17:34:58 +08:00
eeff f69328e01a chore(ekuiper): update log level 2023-05-17 17:34:58 +08:00
eeff e33d331ee3 chore(monitor): update log level 2023-05-17 17:34:55 +08:00
eeff 542624189a chore(mqtt): update log level 2023-05-17 17:34:11 +08:00
fengzeroz 90e7b0ec03 Merge pull request #1271 from fengzeroz/v2.4
chore: log type & value
2023-05-17 16:44:00 +08:00
fengzero 30579ef578 chore: log type & value 2023-05-17 08:36:31 +00:00
fengzeroz bc44bfeda1 Merge pull request #1268 from fengzeroz/v2.4
type: get value
2023-05-17 11:39:16 +08:00
fengzero 73efd03cfa type: get value 2023-05-17 03:38:30 +00:00
fengzeroz 60c707cdde Merge pull request #1267 from fengzeroz/v2.4
packed neu_value_u
2023-05-16 20:23:29 +08:00
fengzero 10b317c0d7 packed neu_value_u 2023-05-16 12:22:55 +00:00
fengzeroz 3d9ac43d42 Merge pull request #1263 from fengzeroz/v2.4
version: 2.4.6
2023-05-15 14:54:42 +08:00
fengzero 3605e132cc version: 2.4.6 2023-05-15 06:54:11 +00:00
fengzeroz 163475f9a9 Merge pull request #1262 from fengzeroz/v2.4
manager nng: set sendtimeout
2023-05-15 10:46:14 +08:00
fengzero d2cd9267bc manager nng: set sendtimeout 2023-05-15 02:45:00 +00:00
fengzeroz 6d6af8be93 Merge pull request #1261 from fengzeroz/v2.4
improve: nng buf size
2023-05-12 18:21:26 +08:00
fengzero fddad11a5f improve: nng buf size 2023-05-12 10:20:13 +00:00
fengzeroz 56e45ae77e Merge pull request #1260 from fengzeroz/v2.4
improve(driver): drive reads and writes in a single thread
2023-05-12 16:05:36 +08:00
fengzero edd1d5eee0 improve(driver): drive reads and writes in a single thread 2023-05-12 08:04:51 +00:00
fengzeroz ebf9d92315 Merge pull request #1258 from eeff/v2.4
Pick to 2.4 chore: fix mqtt log
2023-05-11 14:27:26 +08:00
eeff 5fcd8d2d03 chore: fix mqtt log 2023-05-11 10:51:25 +08:00
fengzeroz 6ad096315f Merge pull request #1256 from fengzeroz/v2.4
protocol buf: unpack buf size
2023-05-10 16:30:45 +08:00
fengzero 8f98ab9d6f protocol buf: unpack buf size 2023-05-10 08:30:13 +00:00
fengzeroz ebfb31d672 Merge pull request #1255 from fengzeroz/v2.4
protocol buf: use all buf
2023-05-10 16:26:23 +08:00
fengzero e691099640 protocol buf: use all buf 2023-05-10 08:24:45 +00:00
fengzeroz e9505e14cd Merge pull request #1252 from lixiumei123/v2.4
fix(dlt645):fix the connection status
2023-05-08 15:38:10 +08:00
liyuxi 2555525445 fix(dlt645):fix the connection status 2023-05-08 07:16:51 +00:00
fengzeroz ab8da34a6d Merge pull request #1250 from eeff/v2.4
Pick to 2.4 feat: add SQL schema migrating MQTT upload-topic to subscription parameter
2023-05-08 14:52:17 +08:00
eeff 899081fc03 fix(core): subscription parameter memory leak 2023-05-08 14:41:39 +08:00
eeff ebb969af8f feat: add SQL schema migrating MQTT upload-topic to subscription parameters 2023-05-08 14:41:39 +08:00
fengzeroz 4a56ba1a3a Merge pull request #1249 from fengzeroz/v2.4
fix(connection): recv errorno check
2023-05-08 14:22:18 +08:00
fengzero 069786f6c0 fix(connection): recv errorno check 2023-05-08 03:35:31 +00:00
fengzeroz a548a57d11 Merge pull request #1245 from fengzeroz/v2.4
version: 2.4.5
2023-05-06 14:37:38 +08:00
fengzero 37f45d4b3a version: 2.4.5 2023-05-06 06:37:15 +00:00
fengzeroz 3a1a0fab3a Merge pull request #1244 from fengzeroz/v2.4
fix clang format
2023-05-06 14:21:47 +08:00
fengzero 10eec9446a fix clang format 2023-05-06 06:21:24 +00:00
fengzeroz 2df3f79536 Merge pull request #1243 from fengzeroz/v2.4
fix(mips): link atomic
2023-05-06 14:20:47 +08:00
fengzero f23c5a5cb8 fix(mips): link atomic 2023-05-06 06:20:24 +00:00
fengzeroz eadcdf5fa1 Merge pull request #1242 from fengzeroz/v2.4
fix(mips): compile error
2023-05-06 14:00:15 +08:00
fengzero d1dd7008e6 fix(mips): compile error 2023-05-06 05:59:50 +00:00
fengzeroz b0b6a08702 Merge pull request #1241 from eeff/v2.4
Pick to 2.4 feat(mqtt): use write-req-topic and write-resp-topic
2023-05-06 13:52:00 +08:00
eeff 77e25d532b feat(mqtt): use write-req-topic and write-resp-topic 2023-05-06 10:43:22 +08:00
eeff d1d5b95a6a feat: add mqtt client unsubscribe method 2023-05-06 10:43:22 +08:00
eeff 125324546f feat(mqtt): add write request/response topic config 2023-05-06 10:43:22 +08:00
fengzeroz 0556e30343 Merge pull request #1238 from fengzeroz/v2.4
chore(error): device not response
2023-05-04 11:41:33 +08:00
fengzero 97e8febf91 chore(error): device not response 2023-05-04 03:22:30 +00:00
fengzeroz 97e986e14f Merge pull request #1233 from fengzeroz/v2.4
version: 2.4.4
2023-04-28 13:55:49 +08:00
fengzero 64e265770e version: 2.4.4 2023-04-28 05:54:36 +00:00
fengzeroz e919af04b0 Merge pull request #1227 from eeff/eeff/2.4
fix: global metrics OS info
2023-04-27 10:35:54 +08:00
eeff 5b00c22e70 fix: global metrics OS info 2023-04-26 17:52:24 +08:00
lxm c83b0498d6 Merge pull request #1225 from lixiumei123/v2.4
fix(file plugin):read error
2023-04-26 14:47:30 +08:00
liyuxi b2c1c45c01 fix(file plugin):read error 2023-04-26 06:26:58 +00:00
eeff f1db3782b9 Merge pull request #1222 from eeff/eeff/2.4
Pick to 2.4: static tag value expired error in read_report_group
2023-04-26 14:19:27 +08:00
eeff 5e19ff78ff fix: static tag value expired error in read_report_group 2023-04-26 11:44:08 +08:00
lxm 8fce63ae7b Merge pull request #1218 from lixiumei123/v2.4
refactor:replace plugin name before starting
2023-04-24 14:24:00 +08:00
liyuxi dc16ca62d3 refactor:replace plugin name before starting 2023-04-24 03:39:24 +00:00
fengzeroz 76bbb2cbce Merge pull request #1213 from hxy7yx/v2.4
Revert "Open source Modbus TCP/RTU, delete existing Modbus TCP commun…
2023-04-21 15:50:57 +08:00
hxy7yx 1079af396e Revert "Open source Modbus TCP/RTU, delete existing Modbus TCP community"
This reverts commit faaef6c2e7.
2023-04-21 15:41:09 +08:00
fengzeroz fed03422b3 Merge pull request #1212 from fengzeroz/v2.4
version: 2.4.3
2023-04-21 11:10:53 +08:00
fengzero c8a06bfc30 version: 2.4.3 2023-04-21 03:10:07 +00:00
eeff fc12d7fbff Merge pull request #1210 from eeff/eeff/2.4
Pick to 2.4 feat(mqtt): make offline cache setting more explicit
2023-04-20 18:09:29 +08:00
eeff 92f387ae7b feat(mqtt): make offline cache setting more explicit
1. Add `offline-cache` bool parameter.
  2. Require `cache-mem-size` and `cache-disk-size` only when
     `offline-cache` is enabled. We provide no defaults for
     `cache-mem-size` and `cache-disk-size`, so the user must provide
     values explicitly.
2023-04-20 16:43:45 +08:00
eeff 7cb61f6fa5 Merge pull request #1207 from eeff/eeff/2.4
feat: update global memory metrics
2023-04-20 13:48:19 +08:00
eeff 72eb9c4219 feat: update global memory metrics 2023-04-20 12:05:33 +08:00
hxy7yx faaef6c2e7 Open source Modbus TCP/RTU, delete existing Modbus TCP community 2023-04-20 11:15:06 +08:00
fengzeroz ff22e3413e Merge pull request #1204 from eeff/eeff/2.4
Pick to 2.4: make monitor schema description more clear
2023-04-20 09:55:15 +08:00
eeff 393d85741a chore: make monitor schema description more clear 2023-04-18 16:31:44 +08:00
fengzeroz 680a2839c2 Merge pull request #1201 from eeff/eeff/2.4
fix(restful): global config json parsing on empty array
2023-04-18 10:36:06 +08:00
eeff 5ee46fa972 fix(restful): global config json parsing on empty array 2023-04-18 10:14:19 +08:00
fengzeroz ae94644430 Merge pull request #1196 from fengzeroz/v2.4
version: 2.4.2
2023-04-14 15:39:59 +08:00
fengzero f5c730dcfc version: 2.4.2 2023-04-14 07:38:23 +00:00
eeff 488cccf521 Merge pull request #1185 from eeff/eeff/2.4
Pick to 2.4: data race when close mqtt client
2023-04-12 17:43:19 +08:00
eeff a0d5851719 fix: data race when close mqtt client 2023-04-12 11:10:58 +08:00
eeff c518d6c196 Merge pull request #1181 from eeff/eeff/2.4
Pick to 2.4: sql schema add default setting for data-strream-processing node
2023-04-11 16:22:20 +08:00
eeff c385103c9c feat: sql schema add default setting for data-strream-processing node 2023-04-11 16:03:47 +08:00
eeff f216b3f4e9 Merge pull request #1180 from eeff/eeff/2.4
Pick to 2.4: update mqtt schema
2023-04-11 12:14:56 +08:00
eeff c768f45e9e chore: update mqtt schema 2023-04-10 17:29:45 +08:00
eeff fa55497b55 chore: make mqtt configuration parameter description more clear 2023-04-10 17:29:41 +08:00
fengzeroz 5336d1c250 Merge pull request #1178 from fengzeroz/v2.4
improve(connection): replace old client
2023-04-10 14:08:35 +08:00
fengzero dede8ee8f4 fix(connection): replace client 2023-04-10 03:33:54 +00:00
fengzero 17e2015a9c fix compile warning 2023-04-10 03:26:17 +00:00
fengzero 3361284252 improve(connection): replace old client 2023-04-10 03:12:12 +00:00
fengzeroz 8f6d9d1c41 Merge pull request #1174 from fengzeroz/v2.4
version: 2.4.1
2023-04-07 16:52:38 +08:00
fengzero 3f75222410 version: 2.4.1 2023-04-07 08:52:01 +00:00
fengzeroz ddf5d252f6 Merge pull request #1173 from ZhouBox/feature-build-2.4
[v2.4]feature:use ghcr.io and use corresponding tag
2023-04-07 16:06:42 +08:00
zhouxiang 6e467f5f27 fix:use tag directly 2023-04-07 15:41:36 +08:00
zhouxiang fd1266bc9e [v2.4]feature:use ghcr.io and use corresponding tag 2023-04-07 14:39:44 +08:00
fengzeroz d8b605ef1c Merge pull request #1172 from fengzeroz/v2.4
serial: more baudrate 300/200/150
2023-04-07 14:23:16 +08:00
fengzero c9122b5b7c serial: more baudrate 300/200/150 2023-04-07 06:21:52 +00:00
fengzeroz 8c11cb3466 Merge pull request #1170 from gc87/spb-float-fix-v2.4
[v2.4]fix: determined f32 type data is treated as d64
2023-04-07 11:07:41 +08:00
gc87 80680c55f5 fix: determined f32 type data is treated as d64 2023-04-07 10:51:14 +08:00
fengzero 132c25b4ad serial: more baudrate 1800/1200/600 2023-04-06 08:14:38 +00:00
fengzeroz cc7e5d092a Merge pull request #1162 from lixiumei123/v2.4
fix(log debug):error reporting that the node does not exist
2023-04-06 14:38:56 +08:00
liyuxi 2e3f6d6db2 fix(log debug):error reporting that the node does not exist 2023-04-06 06:20:56 +00:00
fengzeroz 43a95b7910 Merge pull request #1156 from eeff/eeff/2.4
Pick to 2.4: schema use drop drown selection button for QoS setting
2023-04-06 13:01:09 +08:00
fengzeroz 053be086b0 Merge pull request #1158 from lixiumei123/v2.4
fix(file plugin):fix delay
2023-04-06 13:00:26 +08:00
fengzeroz 66dba0f69a Merge pull request #1160 from fengzeroz/v2.4
limit log size
2023-04-06 12:59:12 +08:00
fengzero c6752c5761 limit log size 2023-04-06 04:57:46 +00:00
liyuxi 53f5c496ea fix(file plugin):fix delay 2023-04-06 02:26:46 +00:00
eeff 1e28e4bc0e fix(mqtt): schema use drop drown selection button for QoS setting 2023-04-04 19:03:41 +08:00
fengzeroz 2bbcc6a154 Merge pull request #1154 from fengzeroz/v2.4
ci: branch v2.4
2023-03-31 17:34:16 +08:00
fengzero 00d6418288 ci: branch v2.4 2023-03-31 09:33:43 +00:00
598 changed files with 446002 additions and 220066 deletions
+1
View File
@@ -12,3 +12,4 @@ PointerAlignment: Right
SpaceAfterCStyleCast: true
AllowShortFunctionsOnASingleLine: Inline
BreakBeforeBinaryOperators: None
-68
View File
@@ -1,68 +0,0 @@
# Security Policy
We take the security of EMQX Neuron and NeuronEX seriously and appreciate the
efforts of security researchers who help keep our users safe.
## Reporting a Vulnerability
**Please do not report security vulnerabilities through public GitHub issues,
discussions, or pull requests.** Public disclosure before a fix is available puts
users at risk.
Instead, report privately through GitHub's **Private Vulnerability Reporting**:
1. Go to the **[Security](https://github.com/emqx/neuron/security)** tab of this
repository.
2. Click **Report a vulnerability**.
3. Fill in the advisory form with the details below.
This opens a private channel visible only to the maintainers.
### What to include
A good report helps us triage quickly. Where possible, please provide:
- The affected product and version(s) (e.g. Neuron 2.15.0, NeuronEX 3.9.0), and
the commit or image digest you tested.
- The component / code path (file and function, if known).
- A clear description of the vulnerability and its impact.
- Step-by-step reproduction instructions, a proof-of-concept, or the exact
payloads. A reproduction package is welcome.
- The exact configuration required to trigger it (network binding, TLS mode,
broker setup, plugins/nodes configured, etc.), and any conditions under which
the issue does **not** occur.
- Your assessment of severity, if you have one (e.g. a CVSS vector).
## Supported Versions
Security fixes are provided for the active release tracks. Older versions should
be upgraded to a fixed release.
| Product | Supported tracks |
|----------------|------------------|
| EMQX Neuron | 2.12.x, 2.13.x, 2.14.x, 2.15.x |
| EMQX NeuronEX | 3.6.x, 3.7.x, 3.8.x, 3.9.x |
## Our Process
1. **Acknowledgement** — we aim to acknowledge your report within a few business
days.
2. **Assessment** — we validate the issue and assess severity (CVSS for the
published advisory, plus our internal reward tiering where applicable).
3. **Fix** — we develop and backport the fix across the affected supported tracks.
4. **Advisory & CVE** — we publish a GitHub Security Advisory (GHSA) and request a
CVE identifier.
5. **Disclosure** — to give users time to upgrade, we generally publish the
advisory **90 days** after the fixed releases are available. We are happy to
coordinate the disclosure timeline with you.
6. **Credit** — we credit reporters in the advisory by their preferred name or
GitHub handle, unless you prefer to remain anonymous.
## Scope
Vulnerabilities in the Neuron / NeuronEX code and its official plugins are in
scope. When assessing impact we consider the intended deployment model (for
example, control-plane and broker interfaces are typically deployed on private,
trusted networks); please describe the exposure your report assumes.
Thank you for helping make the Neuron ecosystem safer.
+28
View File
@@ -0,0 +1,28 @@
name: Check helm version
on:
push:
paths:
- deploy/charts/neuron/templates/**
pull_request:
paths:
- deploy/charts/neuron/templates/**
jobs:
check-helm-version:
runs-on: ubuntu-latest
steps:
- run: minikube start
- uses: actions/checkout@v2
- name: Update helm
run: |
helm repo add emqx https://repos.emqx.io/charts
helm repo update
- name: Check helm version
run: |
old=$(helm show chart emqx/neuron |egrep "^version" | sed -r 's|^version:[[:space:]]([0-9]+.[0-9]+.[0-9]+)$|\1|g')
now=$(egrep "^version" deploy/charts/neuron/Chart.yaml | sed -r 's|^version:[[:space:]]([0-9]+.[0-9]+.[0-9]+)$|\1|g')
if [ "$old" = "$now" ];then
echo "Need update version for Chart.yaml"
exit 1
fi
+43 -213
View File
@@ -7,57 +7,48 @@ on:
types:
- published
env:
BRANCH: v2.4
jobs:
clang_format_check:
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
steps:
# checkout
- uses: actions/checkout@v6
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Run clang-format style check for C/C++ programs.
uses: DoozyX/clang-format-lint-action@v0.18
uses: DoozyX/clang-format-lint-action@v0.12
with:
source: 'src plugins include tests'
exclude: 'include/neuron/utils/uthash.h include/neuron/utils/utarray.h include/neuron/utils/utlist.h include/neuron/utils/zlog.h src/otel/trace.pb-c.h src/otel/trace.pb-c.c src/otel/resource.pb-c.h src/otel/resource.pb-c.c src/otel/common.pb-c.c src/otel/common.pb-c.h plugins/mqtt/ptformat.pb-c.h plugins/mqtt/ptformat.pb-c.c'
style: file
exclude: 'include/neuron/utils/uthash.h include/neuron/utils/utarray.h include/neuron/utils/utlist.h include/neuron/utils/zlog.h'
clangFormatVersion: 10
style: file
cppcheck:
runs-on: ubuntu-24.04
container: ghcr.io/neugates/build:x86_64-main
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
with:
submodules: recursive
- name: cppcheck
run: |
sudo apt install -y cppcheck
cppcheck --enable=all --error-exitcode=2 --inline-suppr --suppress=missingInclude --suppress=unusedFunction --suppress=*:src/otel/trace.pb-c.c --suppress=*:src/otel/common.pb-c.c --suppress=*:src/otel/resource.pb-c.c ./plugins ./src
cppcheck --enable=all --error-exitcode=2 --inline-suppr --suppress=missingInclude --suppress=variableScope ./simulator
sudo apt install cppcheck
cppcheck --enable=all --error-exitcode=2 --inline-suppr --suppress=missingInclude --suppress=unusedFunction ./plugins ./simulator ./src
flawfinder:
name: Flawfinder Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: flawfinder_scan
uses: david-a-wheeler/flawfinder@2.0.20
with:
arguments: '--minlevel=3 .'
build:
runs-on: ubuntu-24.04
container: ghcr.io/neugates/build:x86_64-main
ut:
runs-on: ubuntu-20.04
container: ghcr.io/neugates/build:x86_64-v2.4
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
with:
submodules: recursive
@@ -65,204 +56,43 @@ jobs:
run: |
git config --global --add safe.directory $(pwd)
mkdir -p build && cd build
cmake -DUSE_GCOV=1 .. -DCMAKE_TOOLCHAIN_FILE=../cmake/x86_64-linux-gnu.cmake
make -j4
- uses: actions/upload-artifact@v4
with:
name: neuron-build
path: |
build
ut:
runs-on: ubuntu-24.04
container: ghcr.io/neugates/build:x86_64-main
needs: [build]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/download-artifact@v4
with:
name: neuron-build
path: ./build
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/x86_64-linux-gnu.cmake
make -j4
- name: unit test
run: |
chmod -R +x ./build/
cd build
cmake -DUSE_GCOV=1 .. -DCMAKE_TOOLCHAIN_FILE=../cmake/x86_64-linux-gnu.cmake
ctest --output-on-failure
- name: create cov report
run: |
sudo apt-get update
sudo apt-get -y install lcov
mkdir -p cov_report
./create_cov_report.sh ut
ft:
runs-on: ubuntu-20.04
container: ghcr.io/neugates/build:x86_64-v2.4
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
verbose: true
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
directory: ./cov_report/
files: ./cov_report/cov-ut.info
pft:
runs-on: ubuntu-24.04
container: ghcr.io/neugates/build:x86_64-main
needs: [build]
strategy:
matrix:
plugin: [core, ekuiper, modbus, mqtt, metrics]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
with:
submodules: recursive
- name: compile
run: |
git config --global --add safe.directory $(pwd)
mkdir -p build && cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/x86_64-linux-gnu.cmake
make -j4
- name: setup dependencies
- name: function test
run: |
sudo apt-get install -y mosquitto
pip install -U pytest requests prometheus-client gmqtt pynng sniffio
sudo apt-get install -y socat
mosquitto -v &
python3 -m pip install -U pip
python3 -m pip install -r ft/requirements.txt
python3 -m robot --maxerrorlines=600 -P ft/ -d ft/reports ft
- uses: actions/download-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: neuron-build
path: ./build
- name: function test
run: |
chmod -R +x ./build/
cd build
cmake -DUSE_GCOV=1 .. -DCMAKE_TOOLCHAIN_FILE=../cmake/x86_64-linux-gnu.cmake
cd ..
if [ "${{ matrix.plugin }}" = "core" ]; then
pytest -s -v tests/ft \
--ignore=tests/ft/app/test_ekuiper.py \
--ignore=tests/ft/app/test_mqtt.py \
--ignore=tests/ft/app/test_azure.py \
--ignore=tests/ft/app/test_kafka.py \
--ignore=tests/ft/driver/test_modbus.py \
--ignore=tests/ft/metrics/test_metrics.py \
--ignore=tests/ft/login/test_launch.py \
--ignore=tests/ft/login/test_filter_error.py \
--ignore=tests/ft/login/test_login.py
elif [ "${{ matrix.plugin }}" = "ekuiper" ]; then
pytest -s -v tests/ft/app/"test_ekuiper.py"
elif [ "${{ matrix.plugin }}" = "modbus" ]; then
pytest -s -v tests/ft/driver/"test_modbus.py"
elif [ "${{ matrix.plugin }}" = "mqtt" ]; then
pytest -s -v tests/ft/app/"test_mqtt.py"
else
pytest -s -v tests/ft/metrics/"test_metrics.py"
fi
- name: create cov report
run: |
sudo apt-get update
sudo apt-get -y install lcov
mkdir -p cov_report
./create_cov_report.sh ${{ matrix.plugin }}
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
verbose: true
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
directory: ./cov_report/
files: ./cov_report/cov-${{ matrix.plugin }}.info
pft_kafka:
runs-on: ubuntu-24.04
container: ghcr.io/neugates/build:x86_64-main
needs: [build]
services:
kafka:
image: apache/kafka:3.7.0
env:
KAFKA_NODE_ID: "1"
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@localhost:9093
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: "1"
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: "0"
CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk
ports:
- 9092:9092
options: >-
--health-cmd "bash -c 'echo > /dev/tcp/localhost/9092'"
--health-interval 10s
--health-timeout 5s
--health-retries 10
--health-start-period 30s
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: setup dependencies
run: |
python3 -m pip install --upgrade pip
pip install -U pytest requests confluent-kafka==2.3.0
- uses: actions/download-artifact@v4
with:
name: neuron-build
path: ./build
- name: debug kafka plugin deps
run: |
echo "=== libs in staging prefix ==="
ls -la /home/neuron/main/libs/x86_64-linux-gnu/lib/librdkafka* || echo "no librdkafka"
ls -la /home/neuron/main/libs/x86_64-linux-gnu/lib/libzstd* || echo "no libzstd"
ls -la /home/neuron/main/libs/x86_64-linux-gnu/lib/libsasl2* || echo "no libsasl2"
echo "=== libs in /usr/local/lib ==="
ls -la /usr/local/lib/librdkafka* /usr/local/lib/libzstd* /usr/local/lib/libsasl2* 2>/dev/null || echo "none yet"
echo "=== ldd librdkafka from staging ==="
ldd /home/neuron/main/libs/x86_64-linux-gnu/lib/librdkafka.so.1 || true
echo "=== ldd plugin-kafka from build ==="
chmod -R +x ./build/ 2>/dev/null || true
ldd ./build/plugins/libplugin-kafka.so || true
- name: function test
env:
KAFKA_BROKER: kafka:9092
run: |
chmod -R +x ./build/
cd build
cmake -DUSE_GCOV=1 .. -DCMAKE_TOOLCHAIN_FILE=../cmake/x86_64-linux-gnu.cmake
cd ..
ldconfig
echo "=== post-ldconfig ldd check ==="
ldd ./build/plugins/libplugin-kafka.so || true
ldd /usr/local/lib/librdkafka.so.1 || true
echo "=== try manual dlopen ==="
python3 -c "import ctypes; ctypes.CDLL('./build/plugins/libplugin-kafka.so')" 2>&1 || true
pytest -s -v tests/ft/app/test_kafka.py
- name: create cov report
run: |
sudo apt-get update
sudo apt-get -y install lcov
mkdir -p cov_report
./create_cov_report.sh kafka
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
verbose: true
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
directory: ./cov_report/
files: ./cov_report/cov-kafka.info
name: ft_result
path: |
ft/reports/log.html
ft/reports/output.xml
ft/reports/report.html
build/neuron.log
+168 -27
View File
@@ -8,21 +8,23 @@ on:
- published
env:
VERSION: 2.15.0
UI_VERSION: 2.6.1
BRANCH: main
VERSION: 2.4.7
UI_VERSION: 2.4.4
BRANCH: v2.4
jobs:
cross-build:
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
strategy:
matrix:
arch:
- [aarch64-linux-gnu, aarch64, arm64]
- [arm-linux-gnueabihf, armv4, armhf]
- [x86_64-linux-gnu, x86_64, amd64]
container: ghcr.io/neugates/build:${{ matrix.arch[1] }}-main
container: ghcr.io/neugates/build:${{ matrix.arch[1] }}-v2.4
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
submodules: recursive
@@ -50,22 +52,22 @@ jobs:
run: |
./package-sdk.sh -p ${{ matrix.arch[2] }} -n neuron-sdk-$VERSION
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: neuron-sdk-${{ matrix.arch[2] }}
path: |
neuron-sdk-*.tar.gz
build-debug-sdk:
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
strategy:
matrix:
arch:
- [x86_64-linux-gnu, x86_64, amd64]
container: ghcr.io/neugates/build:${{ matrix.arch[1] }}-main
container: ghcr.io/neugates/build:${{ matrix.arch[1] }}-v2.4
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
submodules: recursive
@@ -81,16 +83,17 @@ jobs:
run: |
./package-sdk.sh -p ${{ matrix.arch[2] }} -n neuron-sdk-debug-$VERSION
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: neuron-sdk-debug
name: neuron-sdk-debug-${{ matrix.arch[2] }}
path: |
neuron-sdk-debug-*.tar.gz
create_update_daily_release:
runs-on: ubuntu-latest
create_daily_release:
runs-on: ubuntu-20.04
if: github.event_name == 'push'
needs: [cross-build, build-debug-sdk]
steps:
- name: delete old daily-pre-release
uses: dev-drprasad/delete-tag-and-release@v0.2.1
@@ -100,21 +103,159 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/download-artifact@v4
with:
name: neuron-sdk-debug
- uses: actions/download-artifact@v4
with:
name: neuron-sdk-amd64
- uses: softprops/action-gh-release@v1
- name: create daily-pre-release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: Daily Release(${{ env.BRANCH }})
tag_name: ${{ env.BRANCH }}-daily
release_name: Daily Release(${{ env.BRANCH }})
draft: false
prerelease: true
files: |
neuron-sdk-*
- shell: bash
run: |
echo "${{ steps.create_release.outputs.upload_url }}" > release_url.txt
- uses: actions/upload-artifact@v3
with:
name: release_url
path: release_url.txt
upload_daily_release_asset:
runs-on: ubuntu-20.04
if: github.event_name == 'push'
needs: create_daily_release
strategy:
matrix:
arch: [arm64, armhf, amd64]
type: [neuron-sdk]
package: [tar.gz]
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: actions/download-artifact@v2
with:
name: release_url
- id: release_url
run: |
value=`cat release_url.txt`
echo "name=$value" >> $GITHUB_OUTPUT
- uses: actions/download-artifact@v2
with:
name: ${{ matrix.type }}-${{ matrix.arch }}
- id: package
run: echo "name=${{ matrix.type }}-${{ env.VERSION }}-${{ matrix.arch }}.${{ matrix.package }}" >> $GITHUB_OUTPUT
- name: upload daily release asset
id: upload-release-asset
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.release_url.outputs.name }}
asset_path: ./${{ steps.package.outputs.name }}
asset_name: ${{ matrix.type }}-${{ env.VERSION }}-daily-linux-${{ matrix.arch }}.${{ matrix.package }}
asset_content_type: application/octet-stream
upload_daily_release_debug_asset:
runs-on: ubuntu-20.04
if: github.event_name == 'push'
needs: create_daily_release
strategy:
matrix:
arch: [amd64]
type: [neuron-sdk-debug]
package: [tar.gz]
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: actions/download-artifact@v2
with:
name: release_url
- id: release_url
run: |
value=`cat release_url.txt`
echo "name=$value" >> $GITHUB_OUTPUT
- uses: actions/download-artifact@v2
with:
name: ${{ matrix.type }}-${{ matrix.arch }}
- id: package
run: echo "name=${{ matrix.type }}-${{ env.VERSION }}-${{ matrix.arch }}.${{ matrix.package }}" >> $GITHUB_OUTPUT
- name: upload daily release asset
id: upload-release-asset
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.release_url.outputs.name }}
asset_path: ./${{ steps.package.outputs.name }}
asset_name: ${{ matrix.type }}-${{ env.VERSION }}-daily-linux-${{ matrix.arch }}.${{ matrix.package }}
asset_content_type: application/octet-stream
clean:
runs-on: ubuntu-20.04
needs: [upload_daily_release_asset, upload_daily_release_debug_asset]
steps:
- uses: geekyeggo/delete-artifact@v1
with:
name: release_url
release:
runs-on: ubuntu-20.04
if: github.event_name == 'release'
needs: cross-build
strategy:
matrix:
arch: [arm64, armhf, amd64]
type: [neuron-sdk]
package: [tar.gz]
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: actions/download-artifact@v2
with:
name: ${{ matrix.type }}-${{ matrix.arch }}
- id: package
run: echo "name=${{ matrix.type }}-${{ env.VERSION }}-${{ matrix.arch }}.${{ matrix.package }}" >> $GITHUB_OUTPUT
- name: gen packages sha256
run: sudo bash -c "echo $(sha256sum ./${{ steps.package.outputs.name }} | awk '{print $1}') > ./${{ steps.package.outputs.name }}.sha256"
- uses: actions/upload-release-asset@v1
if: github.event_name == 'release'
env:
GITHUB_TOKEN: ${{ github.token }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ./${{ steps.package.outputs.name}}.sha256
asset_name: ${{ matrix.type }}-${{ env.VERSION }}-linux-${{ matrix.arch }}.${{ matrix.package }}.sha256
asset_content_type: application/octet-stream
- uses: actions/upload-release-asset@v1
if: github.event_name == 'release'
env:
GITHUB_TOKEN: ${{ github.token }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ./${{ steps.package.outputs.name}}
asset_name: ${{ matrix.type }}-${{ env.VERSION }}-linux-${{ matrix.arch }}.${{ matrix.package }}
asset_content_type: application/octet-stream
-22
View File
@@ -1,22 +0,0 @@
name: Translate Issue from Chinese to English
on:
workflow_dispatch:
inputs:
issue_number:
description: 'The issue number to translate'
required: true
type: string
issues:
types: [opened]
jobs:
translate:
runs-on: ubuntu-latest
permissions:
issues: write # Grant permission to edit issues
steps:
- uses: emqx/translate-issue-action@ee63ec619dfc5808ee2093dba93dfd7ac3beb437 # v1.0.2
with:
issue_number: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.issue_number || github.event.issue.number }}
gemini_api_key: ${{ secrets.GEMINI_API_KEY }}
@@ -1,71 +0,0 @@
name: manual check
on:
workflow_dispatch:
inputs:
plugins:
description: 'Comma separated list of plugins to test (default: all unused plugins)'
required: false
default: 'azure,launch'
jobs:
build:
runs-on: ubuntu-24.04
container: ghcr.io/neugates/build:x86_64-main
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: compile
run: |
git config --global --add safe.directory $(pwd)
mkdir -p build && cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/x86_64-linux-gnu.cmake
make -j4
- uses: actions/upload-artifact@v4
with:
name: neuron-build-unused-plugins
path: |
build
unused_plugins_test:
runs-on: ubuntu-24.04
container: ghcr.io/neugates/build:x86_64-main
needs: [build]
strategy:
matrix:
plugin: [azure, launch]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: setup dependencies
run: |
sudo apt-get install -y mosquitto
pip install -U pytest requests prometheus-client gmqtt pynng sniffio
sudo apt-get install -y socat
- uses: actions/download-artifact@v4
with:
name: neuron-build-unused-plugins
path: ./build
- name: function test
run: |
chmod -R +x ./build/
cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/x86_64-linux-gnu.cmake
cd ..
if [ "${{ matrix.plugin }}" = "azure" ]; then
pytest -s -v tests/ft/app/"test_azure.py"
elif [ "${{ matrix.plugin }}" = "launch" ]; then
pytest -s -v tests/ft/login
fi
- name: Test completed
run: |
echo "Testing for ${{ matrix.plugin }} completed successfully"
+44
View File
@@ -0,0 +1,44 @@
name: Release neuron
on:
push:
tags:
- "*"
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Check appVersion for helm
run: |
appVersion=$(egrep "^appVersion" deploy/charts/neuron/Chart.yaml | sed -r 's|^appVersion:[[:space:]]([0-9]+.[0-9]+.[0-9]+)$|\1|g')
if [ "$appVersion" != "${GITHUB_REF##*/}" ]; then
echo "Need update appVersion for Chart.yaml"
exit 1
fi
helm:
runs-on: self-hosted
if: github.repository_owner == 'emqx'
needs:
- release
steps:
- uses: actions/checkout@v2
- name: Check appVersion for helm
working-directory: deploy/charts/neuron
run: |
appVersion=$(egrep "^appVersion" Chart.yaml | sed -r 's|^appVersion:[[:space:]]([0-9]+.[0-9]+.[0-9]+)$|\1|g')
if [ "$appVersion" != "${GITHUB_REF##*/}" ]; then
echo "Need update appVersion for Chart.yaml"
exit 1
fi
- name: Update helm repo
uses: emqx/push-helm-action@v1
with:
charts_dir: "${{ github.workspace }}/deploy/charts/neuron"
version: ${{ github.ref_name }}
aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws_region: "us-west-2"
aws_bucket_name: "repos-emqx-io"
+1 -2
View File
@@ -19,5 +19,4 @@ neuron-sdk
.vscode
log.html
output.xml
report.html
*.cache
report.html
-50
View File
@@ -1,50 +0,0 @@
# Neuron Engineering and Review Guide
## Repository context
Neuron is the open-source C gateway/core and built-in plugin repository. `plugins/restful/` contains management handlers; `plugins/mqtt/` handles broker interactions; `src/utils/`, `src/adapter/`, `src/core/`, and `src/persist/` contain shared decoding, runtime, plugin, and storage logic. This guide covers code maintained in this repository.
Read [the threat model](docs/threat-model.md) and [security policy](SECURITY.md). Preserve more specific directory instructions and generated-file workflows. Match existing naming, ownership conventions, and error handling. Do not manually edit generated parsers when their generation workflow is authoritative.
## Code Review Rules
### Establish the actual security boundary
- Establish the effective management binding, authentication configuration, and access path. The checked-in loopback/auth defaults are configurable. When a reverse proxy is present, inspect forwarding and authorization without assuming it preserves the gateway's security boundary. Trace the reachable transport before claiming or excluding remote exposure.
- For each finding, identify attacker-controlled input, privileges, enabling configuration, changed lines, sink, and concrete effect. Review introduced regressions and impacted callers, not merely suspicious API names. Separate verified behavior from assumptions about releases or packaging.
- An authenticated caller is not automatically authorized for user management, credential changes, plugin operations, or device writes. Check each operation's actual identity policy and alternate route/method. Derive roles and authority from Neuron's own supported identity model.
### Fail closed at authentication and trust boundaries
- Require appropriate JWT signature, algorithm, issuer/key association, audience, time, and identity validation. External tokens and missing claims must not gain authority through an undocumented fallback. Handle all validation errors, not one named mismatch case.
- Account deletion, password change, and token revocation are separate mechanisms. Test the intended behavior without assuming signed tokens instantly disappear.
- Treat configured credentials and key material as secrets. Validate TLS peer identity and certificate trust; never admit a shared production private key merely because it ships with the application.
### Validate memory and lifetime at the consuming boundary
- Check actual source bytes and destination capacity separately. Validate counts/types, arithmetic, signedness, narrowing, termination, and allocation/iteration agreement before copying or dereferencing. Include singular/batch, malformed, empty, partial, and oversized inputs.
- Trace ownership across message queues, callbacks, reconnection, configuration replacement, and shutdown. Retain/copy borrowed data as needed; stop callbacks before releasing state. Include allocation and partial-failure cleanup.
- Configuration, imports, restored databases and protocol files remain parsing inputs. Constrain paths and symlinks, preserve valid state on failure, and avoid secrets in errors/logs/exports. UI/schema validation is not sufficient runtime validation.
### Review intended capabilities accurately
- Metrics intentionally permits unauthenticated Prometheus scraping. Report added sensitive disclosure, control behavior, or resource abuse—not simply the documented lack of JWT enforcement.
- Native plugins share service-process privileges. Authorized extension loading is not itself host escape. Lower-trust access to loading or memory corruption still warrants review. Prove actual privileges and distinguish service execution, container-root execution, and host compromise.
- Broker ACLs and device-write rights are independent from REST authentication. Follow publish/subscribe directions and configured write targets; do not infer arbitrary downstream takeover from a scoped action.
- Supervision is deployment-dependent. Recovery may shorten a crash but does not eliminate repeated DoS, persistent corruption, or RCE. Do not infer RCE from an unweaponized crash or assume historical binary hardening still applies.
### Findings and regression evidence
State prerequisites and unaffected cases. Cite the narrow code location and reachable call chain; avoid generic severity boilerplate. A safe fix should cover shared helpers and sibling paths, not just the reported route. Add targeted boundary/negative tests, including failure branches and ownership during teardown. Keep private reports, unpublished exploit details, customer data, credentials and reward discussions out of public review comments and repository documentation.
## Build and validation
Use the documented supported build environment and dependencies. From the repository root, a normal CMake build can use:
```sh
cmake -S . -B build
cmake --build build -j4
ctest --test-dir build --output-on-failure
```
Check `CMakeLists.txt` and CI for the target toolchain and options. Current Debug builds enable ASan unless disabled; do not assume Release does. Functional pytest suites under `tests/ft/` require gateway/broker/simulator dependencies and change runtime state. Run only targeted authorized lab tests; never point them at production OT equipment. Report commands actually run and any missing prerequisites rather than claiming validation from source inspection alone.
+16 -95
View File
@@ -9,11 +9,6 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Datalayers is no longer part of the default NeuronEX package. Keep the
# implementation available for standalone/legacy builds, but avoid pulling in
# Apache Arrow unless it is explicitly requested.
option(ENABLE_DATALAYERS "Build the Datalayers northbound plugin" OFF)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
endif()
@@ -36,22 +31,8 @@ if(NOT DISABLE_WERROR)
set(CMAKE_C_FLAGS "$ENV{CFLAGS} -Werror")
endif()
if(USE_GCOV)
message(STATUS "using gcov")
SET(GCC_COVERAGE_COMPILE_FLAGS "-fprofile-arcs -ftest-coverage -O0")
SET(GCC_COVERAGE_LINK_FLAGS "-lgcov")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS}")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS}")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS}")
endif(USE_GCOV)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -g")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error=format-truncation")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} -O0")
add_compile_options(-fstack-protector-strong)
add_link_options(-fstack-protector-strong)
add_link_options(-pie -Wl,-z,relro,-z,now)
if(NOT DISABLE_ASAN)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} -fsanitize=address")
@@ -64,38 +45,27 @@ add_custom_target(neuron-version
${CMAKE_CURRENT_SOURCE_DIR}/cmake/neuron-version.cmake)
find_package(nng CONFIG REQUIRED)
find_package(LibXml2 REQUIRED)
find_package(Threads)
set(OPENSSL_USE_STATIC_LIBS TRUE)
find_package(OpenSSL REQUIRED)
if (CMAKE_STAGING_PREFIX)
include_directories(${CMAKE_STAGING_PREFIX}/include)
link_directories(${CMAKE_STAGING_PREFIX}/lib)
include_directories(${CMAKE_STAGING_PREFIX}/include/libxml2)
else()
include_directories(/usr/local/include)
link_directories(/usr/local/lib)
include_directories(/usr/local/include/libxml2)
endif()
set(PERSIST_SOURCES
src/persist/persist.c
src/persist/sqlite.c
src/persist/json/persist_json_plugin.c)
aux_source_directory(src/parser NEURON_SRC_PARSE)
aux_source_directory(src/otel NEURON_SRC_OTEL)
if(NOT ENABLE_DATALAYERS)
list(FILTER NEURON_SRC_PARSE EXCLUDE REGEX "neu_json_datalayers\\.c$")
endif()
set(NEURON_BASE_SOURCES
src/base/property.c
src/base/tag.c
src/base/neu_plugin_common.c
src/base/tag_sort.c
src/base/group.c
src/base/metrics.c
src/base/msg.c
src/base/template.c
src/connection/connection.c
src/connection/connection_eth.c
src/connection/mqtt_client.c
@@ -105,46 +75,20 @@ set(NEURON_BASE_SOURCES
src/utils/json.c
src/utils/http.c
src/utils/http_handler.c
src/utils/http_proxy.c
src/utils/neu_jwt.c
src/utils/base64.c
src/utils/async_queue.c
src/utils/log.c
src/utils/cid.c
src/utils/ede.c
src/utils/tpy.c
src/utils/neu_path.c
src/utils/mem_cache.c
${PERSIST_SOURCES})
if (SMART_LINK)
message(STATUS "using smart link")
add_definitions(-DNEU_SMART_LINK)
set(NEURON_BASE_SOURCES ${NEURON_BASE_SOURCES} src/connection/smart_link.c)
endif()
if (CLIB)
message(STATUS "set clib")
add_definitions(-DNEU_CLIB=${CLIB})
endif()
if (USE_MQTT_SM)
message(STATUS "use mqtt sm")
add_definitions(-DNEU_USE_MQTT_SM)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Release")
message(STATUS "build release")
add_definitions(-DNEU_RELEASE)
endif()
add_library(neuron-base SHARED)
target_sources(neuron-base PRIVATE ${NEURON_BASE_SOURCES} ${NEURON_SRC_PARSE} ${NEURON_SRC_OTEL})
target_sources(neuron-base PRIVATE ${NEURON_BASE_SOURCES} ${NEURON_SRC_PARSE})
target_include_directories(neuron-base
PRIVATE include/neuron src)
target_link_libraries(neuron-base OpenSSL::SSL OpenSSL::Crypto)
target_link_libraries(neuron-base nng libzlog.so jansson jwt xml2
${CMAKE_THREAD_LIBS_INIT} -lm protobuf-c)
target_link_libraries(neuron-base libssl.a libcrypto.a)
target_link_libraries(neuron-base nng libzlog.so jansson jwt
${CMAKE_THREAD_LIBS_INIT})
add_dependencies(neuron-base neuron-version)
# dependency imposed by nng
@@ -160,16 +104,15 @@ set(NEURON_SOURCES
src/core/subscribe.c
src/core/plugin_manager.c
src/core/node_manager.c
src/core/template_manager.c
src/core/storage.c
src/adapter/msg_q.c
src/adapter/storage.c
src/adapter/adapter.c
src/adapter/driver/cache.c
src/adapter/driver/driver.c
plugins/restful/cert_handle.c
plugins/restful/handle.c
plugins/restful/log_handle.c
plugins/restful/metric_handle.c
plugins/restful/file_handle.c
plugins/restful/normal_handle.c
plugins/restful/rw_handle.c
plugins/restful/adapter_handle.c
@@ -177,38 +120,25 @@ set(NEURON_SOURCES
plugins/restful/global_config_handle.c
plugins/restful/group_config_handle.c
plugins/restful/plugin_handle.c
plugins/restful/template_handle.c
plugins/restful/version_handle.c
plugins/restful/scan_handle.c
plugins/restful/otel_handle.c
plugins/restful/cid_handle.c
plugins/restful/ede_handle.c
plugins/restful/tpy_handle.c
plugins/restful/rest.c
plugins/restful/user.c
plugins/restful/simulator_handle.c
simulator/modbus_tcp_simulator.c)
if(ENABLE_DATALAYERS)
list(APPEND NEURON_SOURCES plugins/restful/datalayers_handle.c)
endif()
plugins/restful/user.c)
set(CMAKE_BUILD_RPATH ./)
add_executable(neuron)
target_sources(neuron PRIVATE ${NEURON_SOURCES})
target_include_directories(neuron PRIVATE include/neuron src plugins)
target_link_libraries(neuron dl neuron-base sqlite3 -lm xml2)
target_link_libraries(neuron dl neuron-base sqlite3 -lm)
target_link_options(neuron PRIVATE "LINKER:--dynamic-list-data")
if(ENABLE_DATALAYERS)
target_compile_definitions(neuron PRIVATE NEU_ENABLE_DATALAYERS)
endif()
#copy file for run
file(COPY ${CMAKE_SOURCE_DIR}/zlog.conf DESTINATION ${CMAKE_BINARY_DIR}/config)
file(COPY ${CMAKE_SOURCE_DIR}/dev.conf DESTINATION ${CMAKE_BINARY_DIR}/config)
file(COPY ${CMAKE_SOURCE_DIR}/neuron.json DESTINATION ${CMAKE_BINARY_DIR}/config)
file(COPY ${CMAKE_SOURCE_DIR}/.gitkeep DESTINATION ${CMAKE_BINARY_DIR}/logs)
file(COPY ${CMAKE_SOURCE_DIR}/.gitkeep DESTINATION ${CMAKE_BINARY_DIR}/persistence)
file(COPY ${CMAKE_SOURCE_DIR}/neuron.key DESTINATION ${CMAKE_BINARY_DIR}/config)
file(COPY ${CMAKE_SOURCE_DIR}/neuron.pem DESTINATION ${CMAKE_BINARY_DIR}/config)
file(GLOB SQL_SCHEMAS ${CMAKE_SOURCE_DIR}/persistence/*.sql)
file(COPY ${SQL_SCHEMAS} DESTINATION ${CMAKE_BINARY_DIR}/config)
file(COPY ${CMAKE_SOURCE_DIR}/default_plugins.json DESTINATION ${CMAKE_BINARY_DIR}/config)
@@ -216,24 +146,15 @@ file(COPY ${CMAKE_SOURCE_DIR}/default_plugins.json DESTINATION ${CMAKE_BINARY_DI
add_subdirectory(plugins/modbus)
add_subdirectory(plugins/mqtt)
add_subdirectory(plugins/ekuiper)
add_subdirectory(plugins/file)
add_subdirectory(plugins/monitor)
if(ENABLE_DATALAYERS)
add_subdirectory(plugins/datalayers)
endif()
add_subdirectory(plugins/kafka)
add_subdirectory(plugins/file)
add_subdirectory(simulator)
if(NOT DISABLE_UT)
add_subdirectory(tests/ut)
add_subdirectory(tests)
endif()
add_subdirectory(tests/plugins/c1)
add_subdirectory(tests/plugins/s1)
add_subdirectory(tests/plugins/sc1)
add_subdirectory(tests/plugins/demo_app)
# Set sane defaults for multi-lib linux systems
include(GNUInstallDirs)
if(UNIX)
+1 -72
View File
@@ -28,7 +28,7 @@ $ cmake -DUSE_SHARED_MBEDTLS_LIBRARY=OFF -DENABLE_TESTING=OFF -DCMAKE_POSITION_I
[NanoSDK](https://github.com/neugates/NanoSDK.git)
```shell
$ git clone https://github.com/neugates/NanoSDK.git
$ git clone -b neuron https://github.com/neugates/NanoSDK.git
$ cd NanoSDK && mkdir build && cd build
$ cmake -DBUILD_SHARED_LIBS=OFF -DNNG_TESTS=OFF -DNNG_ENABLE_SQLITE=ON -DNNG_ENABLE_TLS=ON .. && make && sudo make install
```
@@ -55,74 +55,3 @@ $ tar xzf sqlite3.tar.gz --strip-components=1 -C sqlite3
$ cd sqlite3
$ ./configure CFLAGS=-fPIC && make && sudo make install
```
[protobuf](https://github.com/protocolbuffers/protobuf)
```shell
$ wget --no-check-certificate --content-disposition https://github.com/protocolbuffers/protobuf/releases/download/v3.20.1/protobuf-cpp-3.20.1.tar.gz
$ tar -xzvf protobuf-cpp-3.20.1.tar.gz
$ cd protobuf-3.20.1
$ ./configure --enable-shared=no CFLAGS=-fPIC CXXFLAGS=-fPIC
$ make && sudo make install
```
[protobuf-c](https://github.com/protobuf-c/protobuf-c.git)
```shell
$ git clone -b v1.4.0 https://github.com/protobuf-c/protobuf-c.git
$ cd protobuf-c
$ ./autogen.sh
$ ./configure --disable-protoc --enable-shared=no CFLAGS=-fPIC CXXFLAGS=-fPIC
$ make && sudo make install
```
[cyrus-sasl](https://github.com/cyrusimap/cyrus-sasl.git)
```shell
$ git clone -b cyrus-sasl-2.1.28 https://github.com/cyrusimap/cyrus-sasl.git
$ cd cyrus-sasl
$ autoreconf -fi
$ ./configure --enable-shared --disable-static --disable-sample \
--disable-checkapop --disable-cram --disable-digest --disable-otp \
--disable-srp --disable-gssapi --disable-sql \
--enable-plain --enable-scram --enable-login \
--with-dblib=none --without-saslauthd --without-authdaemond
$ make && sudo make install
```
[zstd](https://github.com/facebook/zstd.git)
```shell
$ git clone -b v1.5.6 https://github.com/facebook/zstd.git
$ cd zstd/build/cmake && mkdir builddir && cd builddir
$ cmake -DZSTD_BUILD_PROGRAMS=OFF -DZSTD_BUILD_TESTS=OFF -DZSTD_BUILD_SHARED=ON -DZSTD_BUILD_STATIC=OFF -DCMAKE_BUILD_TYPE=Release .. && make && sudo make install
```
[librdkafka](https://github.com/confluentinc/librdkafka.git)
```shell
$ git clone -b v2.6.1 https://github.com/confluentinc/librdkafka.git
$ cd librdkafka && mkdir build && cd build
$ cmake -DRDKAFKA_BUILD_STATIC=OFF -DRDKAFKA_BUILD_EXAMPLES=OFF -DRDKAFKA_BUILD_TESTS=OFF -DWITH_SSL=ON -DWITH_ZLIB=ON -DWITH_SASL=ON -DWITH_ZSTD=ON -DWITH_CURL=OFF -DCMAKE_BUILD_TYPE=Release .. && make && sudo make install
```
[libxml2](https://github.com/GNOME/libxml2.git)
```shell
$ git clone -b v2.9.14 https://github.com/GNOME/libxml2.git
$ cd libxml2
$ ./autogen.sh
$ ./configure --enable-shared=no CFLAGS=-fPIC CXXFLAGS=-fPIC
$ make && sudo make install
```
[arrow](https://github.com/apache/arrow)
To build Apache Arrow C++ for the datalayers plugin, please refer to the [official Arrow](https://github.com/apache/arrow) for the most up-to-date information on dependencies, configuration, and build steps.
You must enable the following features at a minimum for the datalayers plugin:
```cmake
-DARROW_FLIGHT=ON \
-DARROW_FLIGHT_SQL=ON
```
Required Dependencies:
When enabling DARROW_FLIGHT and DARROW_FLIGHT_SQL, you must ensure the following dependencies are installed on your system at least: \
gRPC \
Boost \
Thrift
+146
View File
@@ -0,0 +1,146 @@
# Neuron
[![GitHub Release](https://img.shields.io/github/release/neugates/neuron?color=brightgreen&label=Release)](https://github.com/neugates/neuron/releases)
[![Docker Pulls](https://img.shields.io/docker/pulls/emqx/neuron?label=Docker%20Pulls)](https://hub.docker.com/r/emqx/neuron)
[![Slack](https://img.shields.io/badge/Slack-Neuron-39AE85?logo=slack)](https://slack-invite.emqx.io/)
[![Discord](https://img.shields.io/discord/931086341838622751?label=Discord&logo=discord)](https://discord.gg/xYGf3fQnES)
[![Twitter](https://img.shields.io/badge/Follow-EMQ-1DA1F2?logo=twitter)](https://twitter.com/EMQTech)
[![YouTube](https://img.shields.io/badge/Subscribe-EMQ-FF0000?logo=youtube)](https://www.youtube.com/channel/UC5FjR77ErAxvZENEWzQaO5Q)
[English](https://github.com/neugates/neuron/blob/main/README.md) | 简体中文
Neuron 是一款开源的、轻量级工业协议网关软件,支持数十种工业协议的一站式设备连接、数据接入、MQTT 协议转换,为工业设备赋予工业 4.0 时代关键的物联网连接能力。
![neuron-overview](docs/pictures/neuron-final.png)
以下是 Neuron 的一些重要特性:
- 具有实时能力的边缘原生应用程序可以利用边缘端的低延迟网络。
- 松耦合模块化 [架构设计](https://neugates.io/docs/en/latest/architecture/architecture.html) 通过可插拔模块扩展更多功能服务。
- 支持可以在运行时更新设备和应用程序模块的热插件。
- 支持多种工业设备协议,包括 Modbus、OPCUA、Ethernet/IP、IEC104、BACnet 等 [更多协议](https://neugates.io/docs/en/latest/introduction/module-list/module-list.html)。
- 支持同时连接大量不同协议的工业设备。
- 结合[eKuiper](https://www.lfedge.org/projects/ekuiper)提供的规则引擎功能,快速实现基于规则的设备控制或 AI/ML 分析。
- 通过 [SparkplugB](https://neugates.io/docs/en/latest/use-cases/use_cases.html) 解决方案支持对工业应用程序的数据访问,例如 MES 或 ERP、SCADA、historian 和数据分析软件。
- 具有非常低的内存占用,小于 10M 的内存占用和 CPU 使用率,可以在 ARM、x86 和 RISC-V 等资源有限的硬件上运行。
- 支持在本地安装可执行文件或部署在容器化环境中。
- 控制工业设备,通过[API](https://neugates.io/docs/en/latest/http-api/configuration.html)和[MQTT](https://neugates.io/docs/en/latest/north-apps/mqtt/api.html) 服务更改参数和数据标签。
- 与其他 EMQ 产品高度集成,包括 [EMQX](https://www.emqx.com/zh/products/emqx)、[NanoMQ](https://nanomq.io/zh)、[eKuiper](https://ekuiper.org/zh)。
- 核心框架和 Modbus、MQTT 和 eKuiper 的代码在 LGPLv3 的许可下开源。商业模块需要 [EMQ 许可证](https://neugates.io/docs/en/latest/introduction/license-describe.html) 才能运行。
获取更多信息,请访问 [Neuron 官网](https://neugates.io/zh)。
## 快速开始
#### 使用 Docker 运行 Neuron
```
docker run -d --name neuron -p 7000:7000 -p 7001:7001 -p 9081:9081 --privileged=true --restart=always emqx/neuron:latest
```
接下来请参考 [入门指南](https://neugates.io/docs/en/latest/quick-start/installation.html) 开启您的 Neuron 之旅。
> **提示**
>
> Neuron 开源版只包含 Modbus 和 MQTT 驱动。
>
> 您可以[免费申请 15 天试用期限的 License](https://www.emqx.com/zh/apply-licenses/neuron),加载所有的驱动。
#### 更多安装方式
您可以从 [neugates.io/zh/downloads?os=Linux](https://neugates.io/zh/downloads?os=Linux) 下载不同格式的 Neuron 安装包进行手动安装。
也可以直接访问 [Neuron 安装文档](https://neugates.io/docs/en/latest/quick-start/installation.html) 查看不同安装方式的操作步骤。
## 文档
Neuron 文档地址:[https://neugates.io/docs/zh/latest/](https://neugates.io/docs/zh/latest/)。
## 社区
- 访问 [Neuron 论坛](https://askemq.com/c/neuron/8) 以获取帮助,也可以分享您的想法或项目。
- 添加小助手微信号 `emqmkt`,加入 Neuron 微信技术交流群。
- 加入我们的 [Discord](https://discord.gg/xYGf3fQnES),参于实时讨论。
- 关注我们的 [bilibili](https://space.bilibili.com/522222081),获取最新物联网技术分享。
- 关注我们的 [微博](https://weibo.com/emqtt) 或 [Twitter](https://twitter.com/EMQTech),获取 Neuron 最新资讯。
## 从源码构建
**安装依赖**
请参考 [Install-dependencies](https://github.com/emqx/neuron/blob/main/Install-dependencies.md)
**编译**
```
$ git clone https://github.com/emqx/neuron
$ cd neuron
$ mkdir build && cd build
$ cmake .. && make
```
**安装 Dashboard**
在 [neuron-dashboard](https://github.com/emqx/neuron-dashboard/releases) 页面下载最新的 `neuron-dashboard.zip`,解压后放到 Neuron 可执行目录下的 dist 目录中。
**运行**
```
$ cd build
$ ./neuron
```
>**修改日志级别**
>
>在 zlog.conf 文件的规则中更改日志级别。可用的日志级别包括 INFO,DEBUG,NOTICE,WARN,ERROR 和 FATAL。
## 测试
### 单元测试
运行所有的单元测试
```shell
$ cd build
$ ctest --output-on-failure
```
### 功能测试
运行所有的功能测试
```shell
$ sudo apt-get install -y mosquitto
$ mosquitto -v &
$ python3 -m pip install -U pip
$ python3 -m pip install -r ft/requirements.txt
$ python3 -m robot --maxerroelines=600 -P ft/ -d ft/reports ft
```
### 压力测试
在目录`ft/data/persistence/`中有压力测试的数据集。
运行压力测试
```shell
# 安装依赖
$ pip3 install -r ft/requirements.txt
# 使用http接口在数据集total-10k上进行测试
$ python3 -m robot -P ft/ --variable neuron_api:http --variable dataset:total-10k -d ft/http-total-10k ft/pressure.test
# 使用http接口在数据集total-50k上进行测试
$ python3 -m robot -P ft/ --variable neuron_api:http --variable dataset:total-50k -d ft/http-total-50k ft/pressure.test
# 如果使用mqtt接口则需要MQTT服务器,此例为mosquitto
$ mosquitto -v &
# 使用mqtt接口在数据集simple-1k上进行测试
$ python3 -m robot -P ft/ --variable neuron_api:mqtt --variable dataset:simple-1k -d ft/mqtt-simple-1k ft/pressure.test
```
## 开源许可
详见 [LICENSE](./LICENSE)。
+99 -107
View File
@@ -2,146 +2,138 @@
[![GitHub Release](https://img.shields.io/github/release/emqx/neuron?color=brightgreen&label=Release)](https://github.com/emqx/neuron/releases)
[![Docker Pulls](https://img.shields.io/docker/pulls/emqx/neuron?label=Docker%20Pulls)](https://hub.docker.com/r/emqx/neuron)
[![codecov](https://codecov.io/gh/emqx/neuron/graph/badge.svg?token=X95RD0NUT0)](https://codecov.io/gh/emqx/neuron)
[![Slack](https://img.shields.io/badge/Slack-Neuron-39AE85?logo=slack)](https://slack-invite.emqx.io/)
[![Discord](https://img.shields.io/discord/931086341838622751?label=Discord&logo=discord)](https://discord.gg/xYGf3fQnES)
[![Twitter](https://img.shields.io/badge/Follow-EMQ-1DA1F2?logo=twitter)](https://twitter.com/EMQTech)
[![YouTube](https://img.shields.io/badge/Subscribe-EMQ-FF0000?logo=youtube)](https://www.youtube.com/channel/UC5FjR77ErAxvZENEWzQaO5Q)
Neuron is an Industrial IoT (IIoT) connectivity server that bridges industrial devices and modern data platforms. It supports dozens of industrial protocols and converts them into MQTT for seamless integration between IIoT platforms and shop-floor devices.
Neuron is an Industrial IoT (IIoT) connectivity server for modern big data and AI/ML technology to leverage the power of Industrial 4.0. It supports one-stop access to dozens of industrial protocols and converts them into MQTT protocol and realize the interconnection between IIoT platforms and various industrial devices.
![neuron-overview](docs/pictures/neuron-final.png)
Key features:
The following are some important features of Neuron:
- Edge-native application with real-time capability and low latency at the edge.
- Loosely coupled modular architecture with pluggable modules for easy extension.
- Hot-pluggable plugins: update device and application modules at runtime.
- Broad protocol support: Modbus, OPC UA, Ethernet/IP, IEC 60870-5-104, BACnet, and more.
- High concurrency: connect many devices with heterogeneous protocols simultaneously.
- Built-in stream processing via [eKuiper](https://www.lfedge.org/projects/ekuiper) for rules and AI/ML analytics.
- Northbound access for MES/ERP, SCADA, historians, and analytics via Sparkplug B.
- Tiny footprint (<10 MB) and low CPU usage; runs on ARM, x86, and RISC-V.
- Flexible deployments: native binaries or containers.
- Manage devices and tags via [HTTP API](docs/api/english/http.md) and [MQTT API](docs/api/english/mqtt.md).
- Works well with [EMQX](https://www.emqx.com/en/products/emqx), [NanoMQ](https://nanomq.io/), and [eKuiper](https://ekuiper.org/).
- Core framework and Modbus/MQTT/eKuiper plugins are available under LGPLv3.
- Edge native application with real-time capability to leverage the low latency network of edge side.
- Loosely-coupled modularity [architecture design](https://neugates.io/docs/en/latest/architecture/architecture.html) for extending more functional services by pluggable modules.
- Support hot plugins that can update device and application modules during runtime.
- Support numerous protocols for industrial devices, including Modbus, OPCUA, Ethernet/IP, IEC104, BACnet and [more](https://neugates.io/docs/en/latest/introduction/module-list/module-list.html).
- Support simultaneous connection of a large number of industrial devices with different protocols.
- Combine with the rule engine function provided by [eKuiper](https://www.lfedge.org/projects/ekuiper) to quickly implement rule-based device control or AI/ML analytics.
- Support data access to industrial applications, such as MES or ERP, SCADA, historian and data analytics software via [SparkplugB](https://neugates.io/docs/en/latest/use-cases/use_cases.html) solution.
- Has very low memory footprint, less than 10M, and CPU usage, can run on limited resource hardware like ARM, x86 and RISC-V.
- Support installation of native executable or deployed in containerized enviornment.
- Control industrial devices, and make changes to the parameters and data tags through [API](https://neugates.io/docs/en/latest/http-api/configuration.html) and [MQTT](https://neugates.io/docs/en/latest/north-apps/mqtt/api.html) services.
- Highly integrated with other EMQ products, including [EMQX](https://www.emqx.com/en/products/emqx), [NanoMQ](https://nanomq.io/), [eKuiper](https://ekuiper.org/).
- The code of the core framework and Modbus, MQTT and eKuiper are licensed under open source LGPLv3. Commercial modules require a [EMQ License](https://neugates.io/docs/en/latest/introduction/license-describe.html) to run.
## About NeuronEX
For more information, please visit [Neuron homepage](https://neugates.io/).
[NeuronEX](https://www.emqx.com/en/products/emqx-neuron) is the commercial distribution of Neuron, offering extended capabilities and professional support. Compared to the open-source Neuron:
## Get Started
- **Extended protocol support**: Additional industrial protocols (e.g., OPC UA, Siemens S7, Ethernet/IP, etc.)
- **Advanced dashboard**: Full-featured web UI with enhanced visualization and management capabilities
- **Enterprise plugins**: More southbound device adapters, northbound application connectors, and rule engines
- **Cloud integration**: Direct connectors for major cloud platforms (AWS, Azure, Google Cloud, etc.)
- **Data security**: Enhanced encryption, auditing, and compliance features
#### Run Neuron using Docker
For more details, visit the [NeuronEX documentation](https://docs.emqx.com/en/neuronex/latest/).
## Table of Contents
- [Quick Start](#quick-start)
- [Installation](#installation)
- [Binaries](#binaries)
- [Build from Source](#build-from-source)
- [Configuration](#configuration)
- [Documentation](#documentation)
- [Dashboard](#dashboard)
- [Community](#community)
- [Contributing](#contributing)
- [Security](#security)
- [License](#license)
## Quick Start
Default credentials: username `admin`, password `0000`.
### Download
You can download from [Release](https://github.com/emqx/neuron/releases).
```bash
$ tar xvf neuron-{version}-linux-amd64.tar.gz
$ cd neuron
$ ./neuron --log
```
docker run -d --name neuron -p 7000:7000 -p 7001:7001 -p 9081:9081 --privileged=true --restart=always emqx/neuron:latest
```
Open a browser and navigate to `http://localhost:7000` to access the Neuron web interface.
Next, please follow the [getting started guide](https://neugates.io/docs/en/latest/quick-start/installation.html) to tour the Neuron features.
## Installation
> **Limitations**
>
> Neuron open source edition only includes Modbus and MQTT drivers.
>
> [Apply for a 15-day trial license](https://www.emqx.com/en/apply-licenses/neuron) to load all drivers.
### Binaries
#### More installation options
Download the latest release from [Releases](https://github.com/emqx/neuron/releases), then extract and run as shown above.
If you prefer to install and manage Neuron yourself, you can download the latest version from [neugates.io/downloads?os=Linux](https://neugates.io/downloads?os=Linux).
### Build from Source
1. [Install Dependencies](./Install-dependencies.md)
2. Build Neuron
```bash
$ git clone https://github.com/emqx/neuron
$ cd neuron && mkdir build && cd build
$ cmake .. && make
```
3. Download and Unzip Dashboard
```bash
$ wget https://github.com/emqx/neuron-dashboard/releases/download/2.6.3/neuron-dashboard.zip
# Unzip neuron-dashboard.zip to the build directory
$ unzip neuron-dashboard.zip
```
4. Run Neuron
```bash
$ ./neuron --log
```
On the first startup, Neuron will automatically generate `neuron.key` and `neuron.key.pub` in the config directory if they do not exist yet.
If you want to pre-generate the key pair manually, you can still run:
```bash
$ bash ./generate_rsa_keypair.sh ./build/config
```
5. Open a browser and navigate to `http://localhost:7000` to access the Neuron web interface.
## Configuration
- Web UI: `http://localhost:7000` (default credentials above)
- Main config: `neuron.conf`
- Default plugins: `default_plugins.json`
- Logging: `zlog.conf` / `sdk-zlog.conf`
See the quick start for a hands-on walkthrough.
For more installation options, see the [Neuron installation documentation](https://neugates.io/docs/en/latest/quick-start/installation.html).
## Documentation
- Quick start: [Modbus TCP collection and MQTT publishing](./docs/quick_start/quick_start.md)
- APIs: [HTTP](docs/api/english/http.md), [MQTT](docs/api/english/mqtt.md)
The Neuron documentation is available at [neugates.io/docs/en/latest/](https://neugates.io/docs/en/latest/).
## Dashboard
The open-source version of the [Dashboard](https://github.com/emqx/neuron-dashboard) is currently at version 2.6.3, which has been suspended for development and maintenance. This version is also the one integrated by default with Neuron. For a more complete and professional Dashboard, please use [NeuronEX](https://www.emqx.com/en/products/neuronex).
## Community
## Get Involved
- Follow [@EMQTech on Twitter](https://twitter.com/EMQTech).
- If you have a specific question, check out our [discussion forums](https://github.com/emqx/neuron/discussions).
- For general discussions, join us on the [official Discord](https://discord.gg/xYGf3fQnES) team.
- Keep updated on [EMQ YouTube](https://www.youtube.com/channel/UC5FjR77ErAxvZENEWzQaO5Q) by subscribing.
## Contributing
## Build From Source
Contributions are welcome! Feel free to open issues and pull requests to improve Neuron. If you plan a larger change, please start a discussion first to align on direction.
**Install required dependencies**
## Security
[Install-dependencies](https://github.com/emqx/neuron/blob/main/Install-dependencies.md)
If you believe you have found a security vulnerability, please avoid creating a public issue. Instead, contact the maintainers privately (e.g., via GitHub Security Advisories) so we can investigate and fix it responsibly.
**Build**
```
$ git clone https://github.com/emqx/neuron
$ cd neuron
$ mkdir build && cd build
$ cmake .. && make
```
**Install Dashboard**
Download the latest `neuron-dashboard.zip` from the [neuron-dashboard](https://github.com/emqx/neuron-dashboard/releases) page, unzip it and put it to the `dist` directory under the Neuron executable directory.
**Run**
```
$ cd build
$ ./neuron
```
>**Log level**
>
>Change the log level in the rules in the zlog.conf file. Available levels include INFO, DEBUG, NOTICE, WARN, ERROR and FATAL.
>
## Test
To run all unit testers
```shell
$ cd build
$ ctest --output-on-failure
```
### Functional test
To run all functional testers
```shell
$ sudo apt-get install -y mosquitto
$ mosquitto -v &
$ python3 -m pip install -U pip
$ python3 -m pip install -r ft/requirements.txt
$ python3 -m robot --maxerroelines=600 -P ft/ -d ft/reports ft
```
### Pressure test
There are datasets for pressure testing in directory `ft/data/persistence/`.
To run pressure tests
```shell
# python dependencies
$ pip3 install -r ft/requirements.txt
# through http api, on dataset total-10k
$ python3 -m robot -P ft/ --variable neuron_api:http --variable dataset:total-10k -d ft/http-total-10k ft/pressure.test
# through http api, on dataset total-50k
$ python3 -m robot -P ft/ --variable neuron_api:http --variable dataset:total-50k -d ft/http-total-50k ft/pressure.test
# A MQTT broker is needed if using the mqtt api, mosquitto in this example
$ mosquitto -v &
# through mqtt api, on dataset simple-1k
$ python3 -m robot -P ft/ --variable neuron_api:mqtt --variable dataset:simple-1k -d ft/mqtt-simple-1k ft/pressure.test
```
## License
-23
View File
@@ -1,23 +0,0 @@
# Security Policy
## Private reporting
The canonical [Neuron security policy](.github/SECURITY.md) governs vulnerability reporting, supported release tracks, and coordinated disclosure. This page adds reporting context for the open-source Neuron code maintained in this repository; it does not establish a separate support or reward policy.
**Do not disclose vulnerabilities through public issues, discussions, or pull requests.** Open the [Neuron Security tab](https://github.com/emqx/neuron/security) and select **Report a vulnerability** to report privately.
## Neuron report context
Include the affected version/commit or image digest, component, safe reproduction, observed impact, and affected/unaffected conditions. In particular, specify:
- Neuron deployment/package and whether access is direct or through an operator-managed reverse proxy.
- Effective management binding, authentication/token configuration, and direct or proxied access path.
- Attacker identity/permissions and controlled input: REST request, device response, broker message, import, file, or other source.
- Relevant plugin, transport/TLS mode, broker topic/direction ACLs, and device-write prerequisites.
- Whether the demonstrated effect is disclosure, denied/unauthorized action, crash, persistent availability loss, memory corruption, or execution. Distinguish observed effects from downstream hypotheses.
Use synthetic credentials and isolated services/simulators. Do not test customer systems or real equipment without authorization, and transmit sensitive evidence only in the private report.
The [threat model](docs/threat-model.md) explains deployment boundaries and intended capabilities. Loopback/private networking and authenticated peers are assessment context, not blanket exclusions. Anonymous metrics and authorized native extensions have intended uses; report a concrete confidentiality, integrity, availability, or authorization violation rather than the capability alone.
Maintainers and automated reviewers should also follow [AGENTS.md](AGENTS.md). Do not place private vulnerability details in public review comments.
+2 -7
View File
@@ -1,7 +1,7 @@
set(CMAKE_SYSTEM_NAME Linux)
set(COMPILER_PREFIX aarch64-linux-gnu)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(LIBRARY_DIR /home/neuron/main/libs)
set(LIBRARY_DIR /home/neuron/libs)
set(CMAKE_C_COMPILER ${COMPILER_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${COMPILER_PREFIX}-g++)
@@ -21,9 +21,4 @@ set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
link_directories(${CMAKE_STAGING_PREFIX})
file(COPY ${CMAKE_STAGING_PREFIX}/lib/libzlog.so.1.2 DESTINATION /usr/local/lib)
file(COPY ${CMAKE_STAGING_PREFIX}/lib/librdkafka.so.1 DESTINATION /usr/local/lib)
file(GLOB ZSTD_LIBS "${CMAKE_STAGING_PREFIX}/lib/libzstd.so*")
file(COPY ${ZSTD_LIBS} DESTINATION /usr/local/lib)
file(GLOB SASL_LIBS "${CMAKE_STAGING_PREFIX}/lib/libsasl2.so*")
file(COPY ${SASL_LIBS} DESTINATION /usr/local/lib)
file(COPY ${CMAKE_STAGING_PREFIX}/lib/libzlog.so.1.2 DESTINATION /usr/local/lib)
+1 -6
View File
@@ -1,7 +1,7 @@
set(CMAKE_SYSTEM_NAME Linux)
set(COMPILER_PREFIX arm-linux-gnueabi)
set(CMAKE_SYSTEM_PROCESSOR armv5tejl)
set(LIBRARY_DIR /home/neuron/main/libs)
set(LIBRARY_DIR /home/neuron/libs)
set(CMAKE_C_COMPILER ${COMPILER_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${COMPILER_PREFIX}-g++)
@@ -22,8 +22,3 @@ set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
link_directories(${CMAKE_STAGING_PREFIX})
file(COPY ${CMAKE_STAGING_PREFIX}/lib/libzlog.so.1.2 DESTINATION /usr/local/lib)
file(COPY ${CMAKE_STAGING_PREFIX}/lib/librdkafka.so.1 DESTINATION /usr/local/lib)
file(GLOB ZSTD_LIBS "${CMAKE_STAGING_PREFIX}/lib/libzstd.so*")
file(COPY ${ZSTD_LIBS} DESTINATION /usr/local/lib)
file(GLOB SASL_LIBS "${CMAKE_STAGING_PREFIX}/lib/libsasl2.so*")
file(COPY ${SASL_LIBS} DESTINATION /usr/local/lib)
+2 -7
View File
@@ -1,7 +1,7 @@
set(CMAKE_SYSTEM_NAME Linux)
set(COMPILER_PREFIX arm-linux-gnueabihf)
set(CMAKE_SYSTEM_PROCESSOR armv7l)
set(LIBRARY_DIR /home/neuron/main/libs)
set(LIBRARY_DIR /home/neuron/libs)
set(CMAKE_C_COMPILER ${COMPILER_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${COMPILER_PREFIX}-g++)
@@ -21,9 +21,4 @@ set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
link_directories(${CMAKE_STAGING_PREFIX})
file(COPY ${CMAKE_STAGING_PREFIX}/lib/libzlog.so.1.2 DESTINATION /usr/local/lib)
file(COPY ${CMAKE_STAGING_PREFIX}/lib/librdkafka.so.1 DESTINATION /usr/local/lib)
file(GLOB ZSTD_LIBS "${CMAKE_STAGING_PREFIX}/lib/libzstd.so*")
file(COPY ${ZSTD_LIBS} DESTINATION /usr/local/lib)
file(GLOB SASL_LIBS "${CMAKE_STAGING_PREFIX}/lib/libsasl2.so*")
file(COPY ${SASL_LIBS} DESTINATION /usr/local/lib)
file(COPY ${CMAKE_STAGING_PREFIX}/lib/libzlog.so.1.2 DESTINATION /usr/local/lib)
-7
View File
@@ -21,10 +21,3 @@ link_directories(${CMAKE_STAGING_PREFIX})
if(CMAKE_SYSTEM_PROCESSOR MATCHES "mips32")
link_libraries(atomic)
endif()
if(CMAKE_SYSTEM_PROCESSOR MATCHES "riscv64")
link_libraries(atomic)
endif()
if(CMAKE_SYSTEM_PROCESSOR MATCHES "loongarch64")
link_libraries(atomic)
endif()
+1 -11
View File
@@ -1,7 +1,7 @@
set(CMAKE_SYSTEM_NAME Linux)
set(COMPILER_PREFIX x86_64-linux-gnu)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(LIBRARY_DIR /home/neuron/main/libs)
set(LIBRARY_DIR /home/neuron/libs)
set(CMAKE_C_COMPILER ${COMPILER_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${COMPILER_PREFIX}-g++)
@@ -13,11 +13,6 @@ set(CMAKE_RANLIB ${COMPILER_PREFIX}-ranlib)
set(CMAKE_STAGING_PREFIX ${LIBRARY_DIR}/${COMPILER_PREFIX})
set(CMAKE_PREFIX_PATH ${CMAKE_STAGING_PREFIX})
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-lto")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-lto")
include_directories(SYSTEM ${CMAKE_STAGING_PREFIX}/include)
include_directories(SYSTEM ${CMAKE_STAGING_PREFIX}/openssl/include)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_STAGING_PREFIX})
@@ -27,8 +22,3 @@ set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
link_directories(${CMAKE_STAGING_PREFIX})
file(COPY ${CMAKE_STAGING_PREFIX}/lib/libzlog.so.1.2 DESTINATION /usr/local/lib)
file(COPY ${CMAKE_STAGING_PREFIX}/lib/librdkafka.so.1 DESTINATION /usr/local/lib)
file(GLOB ZSTD_LIBS "${CMAKE_STAGING_PREFIX}/lib/libzstd.so*")
file(COPY ${ZSTD_LIBS} DESTINATION /usr/local/lib)
file(GLOB SASL_LIBS "${CMAKE_STAGING_PREFIX}/lib/libsasl2.so*")
file(COPY ${SASL_LIBS} DESTINATION /usr/local/lib)
-9
View File
@@ -1,9 +0,0 @@
ignore:
- "src/otel/*.pb-c.c"
- "plugins/mqtt/azure_iot_plugin.c"
coverage:
status:
project:
default:
target: auto # the required coverage value
threshold: 0.1% # the leniency in hitting the target
-54
View File
@@ -1,54 +0,0 @@
#!/bin/bash
# core trace
lcov -c -d build/CMakeFiles/neuron-base.dir/src -o cov_report/cov-neuron-base.info
lcov -c -d build/CMakeFiles/neuron.dir/src -o cov_report/cov-neuron.info
lcov -c -d build/CMakeFiles/neuron.dir/plugins/restful -o cov_report/cov-restful.info
# always with core trace
function gen_trace () {
lcov -a cov-neuron-base.info -a cov-neuron.info -a cov-restful.info $@;
}
case $1 in
(ut)
rm -f cov_report/cov-neuron.info cov_report/cov-restful.info
for dir in build/tests/ut/CMakeFiles/*_test.dir; do
test_name=$(basename ${dir} "_test.dir")
lcov -c -d ${dir} -o "cov_report/cov-${test_name}_test.info"
done
cd cov_report
lcov $(for file in cov-*.info; do echo "-a $file "; done) -a cov-neuron-base.info -o cov-ut.info;;
(ft)
lcov -c -d build/plugins/mqtt/CMakeFiles/plugin-mqtt.dir -o cov_report/cov-mqtt.info
lcov -c -d build/plugins/modbus/CMakeFiles/plugin-modbus-rtu.dir -o cov_report/cov-modbus-rtu.info
lcov -c -d build/plugins/modbus/CMakeFiles/plugin-modbus-tcp.dir -o cov_report/cov-modbus-tcp.info
cd cov_report
gen_trace -a cov-mqtt.info -a cov-modbus-rtu.info -a cov-modbus-tcp.info -o cov-ft.info;;
(ekuiper)
lcov -c -d build/plugins/ekuiper/CMakeFiles/plugin-ekuiper.dir -o cov_report/cov-ekuiper.info
cd cov_report
gen_trace -a cov-ekuiper.info -o cov-ekuiper.info;;
(modbus)
lcov -c -d build/plugins/modbus/CMakeFiles/plugin-modbus-rtu.dir -o cov_report/cov-modbus-rtu.info
lcov -c -d build/plugins/modbus/CMakeFiles/plugin-modbus-tcp.dir -o cov_report/cov-modbus-tcp.info
lcov -c -d build/simulator/CMakeFiles/modbus_simulator.dir -o cov_report/cov-simulator-modbus.info
cd cov_report
gen_trace -a cov-simulator-modbus.info -a cov-modbus-rtu.info -a cov-modbus-tcp.info -o cov-modbus.info;;
(mqtt)
lcov -c -d build/plugins/mqtt/CMakeFiles/plugin-mqtt.dir -o cov_report/cov-mqtt.info
cd cov_report
gen_trace -a cov-mqtt.info -o cov-mqtt.info;;
(azure)
lcov -c -d build/plugins/mqtt/CMakeFiles/plugin-azure-iot.dir -o cov_report/cov-azure.info
cd cov_report
gen_trace -a cov-azure.info -o cov-azure.info;;
(core)
cd cov_report
gen_trace -o cov-core.info;;
(metrics)
cd cov_report
gen_trace -o cov-metrics.info;;
(*)
lcov -c -d build/plugins/$1/CMakeFiles/plugin-$1.dir -o cov_report/cov-$1.info;;
esac
+3 -6
View File
@@ -1,12 +1,9 @@
{
"plugins": [
"libplugin-mqtt.so",
"libplugin-aws-iot.so",
"libplugin-azure-iot.so",
"libplugin-ekuiper.so",
"libplugin-modbus-tcp.so",
"libplugin-modbus-rtu.so",
"libplugin-file.so",
"libplugin-kafka.so"
"libplugin-monitor.so",
"libplugin-modbus-tcp-comm.so",
"libplugin-file.so"
]
}
+4 -3
View File
@@ -4,9 +4,10 @@ file perms = 666
[formats]
stdout_format = "%d:%ms [%V] [%c] %f:%L %m%n"
file_format = "%d:%ms [%V] %f:%L %m%n"
stdout_format = "%d [%V] [%c] %f:%L %m%n"
file_format = "%d [%V] %f:%L %m%n"
[rules]
*.* "./logs/%c.log", 50MB * 2 ~ "./logs/%c.#2r.log"; file_format
*.* "./logs/%c.log", 50MB * 1 ~ "./logs/%c.#2s.log"; file_format
*.* >stdout; stdout_format
View File
-2187
View File
File diff suppressed because it is too large Load Diff
-482
View File
@@ -1,482 +0,0 @@
# 数据上下行格式
以下内容描述 MQTT 插件如何上报采集的数据,以及如何通过 MQTT 插件实现读写点位数据。
## 数据上报
MQTT 插件将采集到的数据以 JSON 形式发布到指定的主题。
上报数据的具体格式由**上报数据格式**参数指定,有[**Values-format 格式**](#values-format-格式)、[**Tags-format 格式**](#tags-format-格式)、[**ECP-format 格式**](#ecp-format-格式)、[**Custom 自定义格式**](#custom-自定义格式)多种格式可选。
### 上报主题
上报主题在北向节点订阅中指定,其默认值为 **/neuron/{MQTT driver name}** ,见下图
### Values-format 格式
在 **Values-format** 格式中, 上报的数据由以下字段构成:
* `timestamp` : 数据采集时的 UNIX 时间戳。
* `node` : 被采集的南向节点的名字。
* `group` : 被采集的南向节点的点位组的名字。
* `values` : 存储采集成功的点位值的字典。
* `errors` : 存储采集失败的错误码的字典。
* `metas` : 驱动相关的元数据信息。
以下为使用 **values-format** 格式的数据样例,采集成功的点位数据值存放在`values`键中,采集失败的点位错误码存放在`errors`键中。
```json
{
"timestamp": 1650006388943,
"node": "modbus",
"group": "grp",
"values":
{
"tag0": 123
},
"errors":
{
"tag1": 2014
},
"metas":{}
}
```
::: tip 注意
当点位采集成功时,返回采集到的数据。当点位采集发生错误时,返回错误码,不再返回数值。
当 MQTT 插件配置项 **上报点位错误码** 为 **False** 时,不上报点位错误码。
:::
### Tags-format 格式
在 **Tags-format** 格式中, 上报的数据由以下字段构成:
* `timestamp` : 数据采集时的 UNIX 时间戳。
* `node` : 被采集的南向节点的名字。
* `group` : 被采集的南向节点的点位组的名字。
* `tags` : 点位数据数组,每个元素对应一个点位。
* `name` : 具体的点位名称字段。
* `value` : 具体点位对应的值。
* `error` : 具体点位对应的错误码。
以下为使用 **Tags-format** 格式的数据样例,其中所有点位数据存放在一个数组中,每个数组元素包含点位的名字,采集成功时的数据值或者采集失败时的错误码。
```json
{
"timestamp": 1647497389075,
"node": "modbus",
"group": "grp",
"tags": [
{
"name": "tag0",
"value": 123,
},
{
"name": "tag1",
"error": 2014
}
]
}
```
::: tip 注意
当点位采集成功时,返回采集到的数据。当点位采集发生错误时,返回错误码,不再返回数值。
当 MQTT 插件配置项 **上报点位错误码** 为 **False** 时,不上报点位错误码。
:::
### ECP-format 格式
在 **ECP-format** 格式中, 上报的数据由以下字段构成:
* `timestamp` : 数据采集时的 UNIX 时间戳。
* `node` : 被采集的南向节点的名字。
* `group` : 被采集的南向节点的点位组的名字。
* `tags` : 点位数据数组,每个元素对应一个点位。
* `name` : 具体的点位名称字段。
* `value` : 具体点位对应的值。
* `type` : 具体点位对应的数据类型,共包含布尔、整型、浮点型、字符串四种数据类型。
以下为使用 *ECP-format* 格式的数据样例,其中所有点位数据存放在一个数组中,每个数组元素包含点位的名字,点位的数据类型和采集成功时的数据值,不包含采集失败的点位。
数据类分为布尔、整型、浮点型、字符串四种。
* type = 1 布尔
* type = 2 整型
* type = 3 浮点型
* type = 4 字符串
```json
{
"timestamp": 1647497389075,
"node": "modbus",
"group": "grp",
"tags": [
{
"name": "tag_boolean",
"value": true,
"type": 1,
},
{
"name": "tag_int32_",
"value": 123,
"type": 2,
},
{
"name": "tag_float",
"value": 1.23,
"type": 3,
},
{
"name": "tag_string",
"value": "abcd",
"type": 4,
}
]
}
```
::: tip 注意
ECP-format 格式不支持上报点位错误码。
不管 MQTT 插件配置项 **上报点位错误码** 为 **False** 或为 **True** ,都不上报点位错误码。
:::
### Custom 自定义格式
在自定义格式中,可以使用内置支持的变量自定义数据上报格式。
![custom-format](./assets/custom-format.png)
#### 内置变量
| **变量名称** | **说明** |
| ------------------ | ---------------------- |
| `${timestamp}` | 数据采集时的 UNIX 时间戳。 |
| `${node}` | 南向采集点位所属的南向驱动名称。<br> 示例:`modbus` |
| `${group}` | 南向采集点位所属的南向驱动的采集组的名称。<br> 示例:`group` |
| `${tags}` | 南向采集点位有效值的数组集合。<br> 示例:`[{"name": "tag1", "value": 123}, {"name": "tag2", "value": 456}]` |
| `${tag_values}` | 南向采集点位有效值的Json集合。<br> 示例:`{"tag1": 123, "tag2": 456}` |
| `${tag_errors}` | 南向采集点位报错值的数组集合。<br> 示例:`[{"name": "tag3", "error": 2014}, {"name": "tag4", "error": 2015}]` |
| `${tag_error_values}` | 南向采集点位报错值的Json集合。<br> 示例:`{"tag3": 2014, "tag4": 2015}` |
| `${static_tags}` | 订阅采集组时,自定义配置的静态点位的数组集合。<br> 示例: `[{"name": "static_tag1", "value": "abc"}, {"name": "static_tag2", "value": "def"}]` <br> 详见 [静态点位](#静态点位)|
| `${static_tag_values}` | 订阅采集组时,自定义配置的静态点位的Json集合。<br> 示例: `{"static_tag1": "abc", "static_tag2": "def"}`<br> 详见 [静态点位](#静态点位) |
::: tip 注意
`${tags}` 和 `${tag_values}` 是采集数据点的2种不同格式,`${tags}` 是数组格式,`${tag_values}` 是Json格式。一般在构建自定义数据上报格式时,使用 `${tags}` 或 `${tag_values}` 中的一种即可。
`${static_tags}` 和 `${static_tag_values}`、`${tag_errors}`和`${tag_error_values}` 同理,使用其中一种即可。
MQTT 插件配置项 **上报点位错误码** 为 **False** 时,即使配置了`${tag_errors}` 和或 `${tag_error_values}`,也不上报点位错误码信息。
:::
上表中示例部分,原始南向驱动`modbus1`采集组`group1`下,包含4个点位,其中`tag1`和`tag2`的点位采集数据正常,`tag3`和`tag4`的点位采集数据异常。在北向节点订阅时,自定义配置的静态点位为`static_tag1`和`static_tag2`,其值分别为`abc`和`def`。
以上配置,以 Values-format 数据格式上报时,内容如下:
```json
{
"timestamp": 1650006388943,
"node": "modbus1",
"group": "group1",
"values": {"tag1": 123, "tag2": 456, "static_tag1": "abc", "static_tag2": "def"},
"errors": {"tag3": 2014, "tag4": 2015},
"metas":{}
}
```
以下示例通过设定不同的 Custom 配置,将上面 Json 报文输出为不同的数据上报格式。
#### 示例一
将数据点位转换为数组格式,并自定义Json 键名。
```json
{
"timestamp": "${timestamp}",
"node": "${node}",
"group": "${group}",
"custom_tag_name": "${tags}",
"custom_tag_errors": "${tag_errors}",
}
```
实际输出结果如下:
```json
{
"timestamp": 1650006388943,
"node": "modbus1",
"group": "group1",
"custom_tag_name": [{"name": "tag1", "value": 123}, {"name": "tag2", "value": 456}],
"custom_tag_errors": [{"name": "tag3", "error": 2014}, {"name": "tag4", "error": 2015}]
}
```
#### 示例二
通过自定义数据格式,添加全局静态点位。该静态点位在所有驱动采集组上报数据时,均会携带。如 NeuronEX 部署在一个物理网关上,可将网关的SN号、IP地址、位置信息等信息,添加到所有驱动采集组中。
```json
{
"timestamp": "${timestamp}",
"node": "${node}",
"group": "${group}",
"values": "${tag_values}",
"gateway_info": {
"ip": "192.168.1.100",
"sn": "SN123456789",
"location": "shanghai"
}
}
```
实际输出结果如下:
```json
{
"timestamp": 1650006388943,
"node": "modbus1",
"group": "group1",
"values": {"tag1": 123, "tag2": 456},
"gateway_info": {
"ip": "192.168.1.100",
"sn": "SN123456789",
"location": "shanghai"
}
}
```
#### 示例三
将采集数据点位、静态点位、驱动、采集组信息放入 Json 子节点中,可自定义 Json 节点名称。目前自定义数据结构做多支持三层子层级。
```json
{
"timestamp": "${timestamp}",
"data": {
"child_node": {
"node": "${node}",
"group": "${group}",
"tag_values": "${tag_values}",
"static_tag_values": "${static_tag_values}",
"tag_error_values": "${tag_error_values}"
}
}
}
```
数据上报的格式为:
```json
{
"timestamp": 1650006388943,
"data": {
"child_node": {
"node": "modbus",
"group": "group",
"tag_values": {"tag1": 123, "tag2": 456},
"static_tag_values": {"static_tag1": "abc", "static_tag2": "def"},
"tag_error_values": {"tag3": 2014, "tag4": 2015}
}
}
}
```
## 静态点位
静态点位功能,支持在南向驱动数据上报时,由用户在**北向应用组列表**页面手动配置静态点位,在数据上报时,将静态点位数据一并上报。
![static-tags](./assets/static-tags.png)
静态点位配置,需输入标准JSON格式内容,每个静态点位均为JSON对象中的键值对。静态点位将根据配置的数据上报格式(Values-format、Tags-format、ECP-format)与采集组数据一并上报。
```json
{
"location": "sh",
"sn_number": "123456"
}
```
每个南向采集组,可分别配置不同静态点位。表示该采集组代表的物理设备,具有哪些静态属性信息。
静态点位功能支持布尔、整型、浮点型、字符串四种数据类型。复杂数据类型,如数组、结构体等,将以字符串形式上报。
如果添加的静态点位,为 NeuronEX 实例中所有采集组共用,既可以勾选**所有采集组**,添加静态点位,也可以在 Custom 自定义格式中,统一添加静态点位,参考 [自定义格式:示例二](#自定义格式)。
::: tip 注意
静态点位命名,不能和南向驱动采集的点位名称相同。
如命名相同,则如果选择`Values-format`上报数据时,静态点位数据将覆盖南向驱动采集的点位数据.
如果选择`Tags-format`、`ECP-format`或`Custom`上报数据时,静态点位数据将和南向驱动采集的点位数据一起重复上报。
:::
## 读 Tags
### 请求
通过发送 JSON 形式的请求到 MQTT 主题 **/neuron/{node_name}/read/req**,您可以读取一组点位数据。
#### 请求体
读请求体由以下字段构成:
* `uuid` : 唯一的标志,会在响应中原样返回用以区分对应的请求。
* `node` : 某个南向节点名字。
* `group` : 南向节点的某个组的名字。
以下为一个读请求样例:
```json
{
"uuid": "bca54fe7-a2b1-43e2-a4b4-1da715d28eab",
"node": "modbus",
"group": "grp"
}
```
### 响应
读响应会发布到 MQTT 主题 **/neuron/{node_name}/read/resp** 。
#### 响应体
读响应体由以下字段构成:
* `uuid` : 对应请求中所设置的唯一标志。
* `tags` : 点位数据数组,和 [tags 格式](#tags-格式)中的表示方法一样。
以下为一个读响应样例:
```json
{
"uuid": "bca54fe7-a2b1-43e2-a4b4-1da715d28eab",
"tags": [
{
"name": "tag0",
"value": 4,
},
{
"name": "tag1",
"error": 2014
}
]
}
```
::: tip
当点位采集成功时,返回采集到的数据。当点位采集发生错误时,返回错误码,不再返回数值。
:::
## 写 Tag
### 请求
通过发送 JSON 形式的请求到**写请求主题**参数指定的 MQTT 主题,您可以写一个点位数据。可在 MQTT 参数配置中配置写请求的主题,默认为为 **/neuron/{random_str}/write/req**。
#### 请求体
#### 写一个 Tag
写请求体由以下字段构成:
* `uuid` : 唯一的标志,会在响应中原样返回用以区分对应的请求。
* `node` : 某个南向节点名字。
* `group` : 南向节点的某个组的名字。
* `tag` : 要写入的点位名字。
* `value` : 要写入的数据值。
以下为一个写请求样例:
```json
{
"uuid": "cd32be1b-c8b1-3257-94af-77f847b1ed3e",
"node": "modbus",
"group": "grp",
"tag": "tag0",
"value": 1234
}
```
#### 写多个 Tag
Neuron 提供了对多点位写入的支持。要在一次请求中可以写入多个点位数据, 写请求体应由以下字段构成:
* `uuid` : 唯一的标志,会在响应中原样返回用以区分对应的请求。
* `node` : 某个南向节点名字。
* `group` : 南向节点的某个组的名字。
* `tags` : 点位数据数组,每个元素对应一个点位。
以下为一个写请求样例:
```json
{
"uuid": "cd32be1b-c8b1-3257-94af-77f847b1ed3e",
"node": "modbus",
"group": "grp",
"tags": [
{
"tag": "tag0",
"value": 1234
},
{
"tag": "tag1",
"value": 5678
}
]
}
```
### 响应
写响应会发布到**写响应主题**参数指定的 MQTT 主题。默认**写响应主题**参数为 **/neuron/{random_str}//write/resp**。
#### 响应体
写响应体由以下字段构成:
* `uuid` : 对应请求中所设置的唯一标志。
* `error` : 错误码。0 代表成功,非零代表失败。
以下为一个读响应样例:
```json
{
"uuid": "cd32be1b-c8b1-3257-94af-77f847b1ed3e",
"error": 0
}
```
## 驱动状态上报
上报所有南向驱动状态到指定的 MQTT 主题。
### 状态上报主题
上报主题在北向节点配置中指定,其默认值为 **/neuron/{random_str}/state/update**
### 状态上报间隔
状态上报间隔在北向节点配置中指定,指每条消息之间间隔的秒数,其默认值为 1,允许的范围为 1-3600。
### 上报消息格式
上报的数据由以下字段构成:
* `timestamp` : 数据采集时的 UNIX 时间戳。
* `states` : 节点状态信息的数组。
以下是一个驱动状态上报消息样例。
```json
{
"timestamp": 1658134132237,
"states": [
{
"node": "modbus-tcp",
"link": 1,
"running": 3
},
{
"node": "modbus-rtu",
"link": 1,
"running": 3
}
]
}
```
File diff suppressed because it is too large Load Diff
-492
View File
@@ -1,492 +0,0 @@
# Upstream/Downstream Data Format
The following contents describe how MQTT plugin publishes collected data, and how to read or write data through MQTT plugin.
## Data Upload
The MQTT plugin publishes collected data in JSON format to some user-defined topics. The exact format of the data reported is controlled by the **Upload Format** parameter. There are four formats, [**Values-format**](#values-format), [**Tags-format**](#tags-format), [**ECP-format**](#ecp-format), and [**Custom**](#custom-format).
### Upload Topic
The upload topic is specified by the **upload-topic** parameter, and the default one set through the dashboard is **/neuron/{MQTT driver name}**.
### Values Format
In **Values-format**, the upload data has the following fields:
* `timestamp`: the Unix timestamp when the data was collected
* `node`: name of the south node from which data was collected
* `group`: name of the south node group that the tags belong to
* `values`: dictionary storing tags with successfully collected values
* `errors`: dictionary storing tags with encountered error codes
* `metas`: driver related metadata information
The following example is in **Values-format**, where tag values collected successfully are stored in a dictionary, while error codes in another.
```json
{
"timestamp": 1650006388943,
"node": "modbus",
"group": "grp",
"values":
{
"tag0": 123
},
"errors":
{
"tag1": 2014
},
"metas":{}
}
```
::: tip
When a tag is collected successfully, the collected data is returned. When a tag collection fails, the error code is returned instead of the value.
When the **Upload Tag Error Code** parameter is set to **False**, the error code is not reported.
:::
### Tags Format
In **Tags-format**, the upload data has the following fields:
* `timestamp`: the Unix timestamp when the data was collected
* `node`: name of the south node from which data was collected
* `group`: name of the south node group that the tags belong to
* `tags`: tags data array where each element corresponds to one tag in the group
* `name`: the name of the tag
* `value`: the value of the tag
* `error`: the error code of the tag
The following example is in **Tags-format**, where tag data are stored in an array. Each element has the name of the tag, and the tag value or error code if something went wrong.
```json
{
"timestamp": 1647497389075,
"node": "modbus",
"group": "grp",
"tags": [
{
"name": "tag0",
"value": 123,
},
{
"name": "tag1",
"error": 2014
}
]
}
```
::: tip
Tag value is returned only when the tag is read successfully. If something goes wrong when reading a tag, the error code is returned.
When the **Upload Tag Error Code** parameter is set to **False**, the error code is not reported.
:::
### ECP Format
In **ECP-format**, the upload data has the following fields:
* `timestamp`: the Unix timestamp when the data was collected
* `node`: name of the south node from which data was collected
* `group`: name of the south node group that the tags belong to
* `tags`: tags data array where each element corresponds to one tag in the group
* `name`: the name of the tag
* `value`: the value of the tag
* `type`: the type of the tag, which can be Boolean, Integer, Float, or String
The following example data is in **ECP-format**, where tag data are stored in an array. Each element has the name of the tag, the tag type and the tag value, excluding tags where collection failed.
Data classes are divided into four types: Boolean, Integer, Float, and String.
* type = 1 Boolean
* type = 2 Integer
* type = 3 Float
* type = 4 String
```json
{
"timestamp": 1647497389075,
"node": "modbus",
"group": "grp",
"tags": [
{
"name": "tag_boolean",
"value": true,
"type": 1,
},
{
"name": "tag_int32_",
"value": 123,
"type": 2,
},
{
"name": "tag_float",
"value": 1.23,
"type": 3,
},
{
"name": "tag_string",
"value": "abcd",
"type": 4,
}
]
}
```
::: tip
ECP-format does not support reporting tag error codes.
Regardless of whether the **Upload Tag Error Code** parameter is set to **False** or **True**, tag error codes are not reported.
:::
### Custom Format
In the custom format, use built-in variables to define the data upload format.
![custom-format](./assets/custom-format-en.png)
#### Variables
| *variable* | *description* |
| ------------------ | ---------------------- |
| `${timestamp}` | The UNIX timestamp when the data was read. |
| `${node}` | The name of the southbound node. <br> Example: `modbus` |
| `${group}` | The name of the group. <br> Example: `group` |
| `${tags}` | The array of valid tag values. <br> Example: `[{"name": "tag1", "value": 123}, {"name": "tag2", "value": 456}]` |
| `${tag_values}` | The array of valid tag values, Value Format <br> Example: `{"tag1": 123, "tag2": 456}` |
| `${tag_errors}` | The array of error codes. <br> Example: `[{"name": "tag3", "error": 2014}, {"name": "tag4", "error": 2015}]` |
| `${tag_error_values}` | The array of error codes, Value Format <br> Example: `{"tag3": 2014, "tag4": 2015}` |
| `${static_tags}` | The array of static tags. <br> Example: `[{"name": "static_tag1", "value": "abc"}, {"name": "static_tag2", "value": "def"}]` <br> See [Static Tags](#static-tags) |
| `${static_tag_values}` | The array of static tags, Value Format <br> Example: `{"static_tag1": "abc", "static_tag2": "def"}` <br> See [Static Tags](#static-tags) |
::: tip
`${tags}` and `${tag_values}` are two different formats for tag values. `${tags}` is an Array format, and `${tag_values}` is a JSON format. In most cases, use one of them.
`${static_tags}` and `${static_tag_values}`, `${tag_errors}` and `${tag_error_values}` are similar. Use one of them.
When the **Upload Tag Error Code** parameter is set to **False**, even if `${tag_errors}` and `${tag_error_values}` are configured, tag error codes are not reported.
:::
The example above is based on the original southbound driver `modbus1` and group `group1`, which contains 4 tags. `tag1` and `tag2` are collected successfully, while `tag3` and `tag4` are collected with errors. When subscribing to the northbound node, the static tags are configured as `static_tag1` and `static_tag2`, with values of `abc` and `def` respectively.
The above configuration, when reported in **Values-format** data format, is as follows:
```json
{
"timestamp": 1650006388943,
"node": "modbus1",
"group": "group1",
"values": {"tag1": 123, "tag2": 456, "static_tag1": "abc", "static_tag2": "def"},
"errors": {"tag3": 2014, "tag4": 2015},
"metas":{}
}
```
The following example shows how to output the above JSON message in different data reporting formats by setting different **Custom** configurations.
#### Example One
Convert tag data to array format and customize the Json key name.
```json
{
"timestamp": "${timestamp}",
"node": "${node}",
"group": "${group}",
"custom_tag_name": "${tags}",
"custom_tag_errors": "${tag_errors}",
}
```
The actual output result is as follows:
```json
{
"timestamp": 1650006388943,
"node": "modbus1",
"group": "group1",
"custom_tag_name": [{"name": "tag1", "value": 123}, {"name": "tag2", "value": 456}],
"custom_tag_errors": [{"name": "tag3", "error": 2014}, {"name": "tag4", "error": 2015}]
}
```
#### Example Two
Add global static tags through custom data format. These static tags will be carried in all driver group data reporting. For example, if NeuronEX is deployed on a gateway hardware, you can add the gateway's SN number, IP address, and location information to all driver groups.
```json
{
"timestamp": "${timestamp}",
"node": "${node}",
"group": "${group}",
"values": "${tag_values}",
"gateway_info": {
"ip": "192.168.1.100",
"sn": "SN123456789",
"location": "shanghai"
}
}
```
The actual output result is as follows:
```json
{
"timestamp": 1650006388943,
"node": "modbus1",
"group": "group1",
"values": {"tag1": 123, "tag2": 456},
"gateway_info": {
"ip": "192.168.1.100",
"sn": "SN123456789",
"location": "shanghai"
}
}
```
#### Example Three
Put tag data, static tags, driver, and group information into a Json sub-node, and customize the Json node name. Currently, the custom data structure supports up to three sub-levels.
```json
{
"timestamp": "${timestamp}",
"data": {
"child_node": {
"node": "${node}",
"group": "${group}",
"tag_values": "${tag_values}",
"static_tag_values": "${static_tag_values}",
"tag_error_values": "${tag_error_values}"
}
}
}
```
The data reporting format is as follows:
```json
{
"timestamp": 1650006388943,
"data": {
"child_node": {
"node": "modbus",
"group": "group",
"tag_values": {"tag1": 123, "tag2": 456},
"static_tag_values": {"static_tag1": "abc", "static_tag2": "def"},
"tag_error_values": {"tag3": 2014, "tag4": 2015}
}
}
}
```
## Static Tags
The static tag feature supports configuring static tags manually in the **North Application Group List** page, and reporting static tag data together with the collected data.
![static-tags](./assets/static-tags-en.png)
Static tag configuration requires inputting standard JSON format content, where each static tag is a key-value pair in the JSON object. Static tags will be reported together with the collected data according to the configured data reporting format (Values-format, Tags-format, ECP-format).
```json
{
"location": "sh",
"sn_number": "123456"
}
```
Each southbound collection group can configure different static tags. This represents the physical device represented by the collection group, with which static attribute information.
Static tag feature supports **Boolean**, **Integer**, **Float**, and **String** data types. Complex data types, such as arrays and structures, will be reported as **String**.
If the static tags added are shared by all collection groups in the NeuronEX instance, you can select **All Collection Groups** to add static tags, or add static tags in the **Custom** custom format, as shown in [Custom Format: Example Two](#example-two).
::: tip
Static tag names must not be the same as the tag names collected by the southbound driver.
If the names are the same:
- When using `Values-format` for data upload, the static tag data will overwrite the tag data collected by the southbound driver.
- When using `Tags-format`, `ECP-format`, or `Custom` for data upload, both the static tag data and the southbound driver collected tag data will be reported together (resulting in duplicate reporting).
:::
## Read Tag
### Request
You can read a group of tags by sending requests in JSON to the MQTT topic **/neuron/{node_name}/read/req**.
#### Body
The read request body should have the following fields:
* `uuid`: a unique identifier, which will be echoed back in the response to help identify the corresponding request
* `node`: the name of a southbound node
* `group`: the name of a group to read
Below is an example read request:
```json
{
"uuid": "bca54fe7-a2b1-43e2-a4b4-1da715d28eab",
"node": "modbus",
"group": "grp"
}
```
### Response
Read response will be published to the MQTT topic **/neuron/{node_name}/read/resp**.
#### Body
The read response body has the following fields:
* `uuid`: the same unique identifier which is set by the corresponding request
* `tags`: tags data array, same as that in [tags format](#tags-format)
Below is an example read response:
```json
{
"uuid": "bca54fe7-a2b1-43e2-a4b4-1da715d28eab",
"tags": [
{
"name": "tag0",
"value": 4,
},
{
"name": "tag1",
"error": 2014
}
]
}
```
::: tip
Tag value is returned only when the tag is read successfully. If something goes wrong when reading a tag, the error code is returned.
:::
## Write Tag
### Request
You could write a tag by sending requests in JSON to the MQTT topic designated by the **Write Request Topic** parameter.
::: tip
Before Neuron version 2.4.5, the write request topic was hard-coded to **/neuron/{random_str}/write/req**.
:::
#### Body
#### Write one tag
The write request body should have the following fields:
* `uuid`: a unique identifier, which will be echoed back in the response to help identify the corresponding request
* `node`: the name of a southbound node
* `group`: the name of a group
* `tag`: the name of a tag
* `value`: the value to write
Below is an example of write request:
```json
{
"uuid": "cd32be1b-c8b1-3257-94af-77f847b1ed3e",
"node": "modbus",
"group": "grp",
"tag": "tag0",
"value": 1234
}
```
#### Write multiple tags
Since Neuron version 2.6.0, write requests also support writing multiple tags at
a time. To write multiple tags at a time, the request body should have the
following fields:
* `uuid` : a unique identifier, which will be echoed back in the response to help identify the corresponding request.
* `node` : the name of a southbound node.
* `group` : the name of a group.
* `tags` : tags data array where each element corresponds to one tag in the group.
Below is an example write request:
```json
{
"uuid": "cd32be1b-c8b1-3257-94af-77f847b1ed3e",
"node": "modbus",
"group": "grp",
"tags": [
{
"tag": "tag0",
"value": 1234
},
{
"tag": "tag1",
"value": 5678
}
]
}
```
### Response
Write response will be published to the MQTT topic designated by the **Write Response Topic** parameter.
::: tip
Before Neuron version 2.4.5, the write response topic was hard-coded to **/neuron/{random_str}//write/resp**.
:::
#### Body
The write response body has the following fields:
* `uuid`: the same unique identifier which is set by the corresponding request.
* `error`: error code if something bad happens, `0` represents success.
Below is an example of write response:
```json
{
"uuid": "cd32be1b-c8b1-3257-94af-77f847b1ed3e",
"error": 0
}
```
## Driver Status Report
Reports status of all the southbound nodes to the specified topic.
### Status Report Topic
The status report topic is specified in the northbound node configuration. Its default value is **/neuron/{random_str}/state/update**.
### Status Report Interval
The status report interval is specified in the northbound node configuration, indicating the number of seconds between each message. The default value is 1, with an allowed range of 1-3600.
### Reporting Message Format
The reported data consists of the following fields:
* `timestamp`: The UNIX timestamp when the data was collected.
* `states`: An array of node status information.
Below is an example of a driver status reporting message.
```json
{
"timestamp": 1658134132237,
"states": [
{
"node": "modbus-tcp",
"link": 1,
"running": 3
},
{
"node": "modbus-rtu",
"link": 1,
"running": 3
}
]
}
```
Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 507 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

-89
View File
@@ -1,89 +0,0 @@
# Quick Start
This document requires that you have successfully run Neuron according to the [Installation and Operation](https://github.com/emqx/neuron/README.md) guide, and that you can open the Neuron management panel in your browser.
## Start the Modbus TCP Simulator
Two types of Modbus TCP simulators are provided. You can run one of them according to your needs.
### Neuron's Built-in Simulator
The Neuron source code or release package includes a test simulator that runs on Linux, which can read and write some fixed points. The Slave ID is 1.
```bash
# If compiling from source, it's in the build/simulator directory
# In the release package, it's in the root directory
# Run the Modbus simulator, use TCP mode, and bind to 0.0.0.0:1502
$ ./modbus_simulator tcp 1502 ip_v4
```
### PeakHMI Slave Simulators
The PeakHMI simulator needs to run on a Windows system. [PeakHMI Official Website](https://hmisys.com)
After downloading and installing, run the Modbus TCP slave EX simulator. The default port for this simulator is 502, which will be blocked by the default Windows firewall. Make sure the firewall allows this port or disable the firewall.
![modbus-simulator](./assets/modbus-simulator.png)
## Create a Southbound Device in Neuron
### Create a Device
**Southbound Device Management** -> **Add Device**. In the pop-up dialog box, fill in the device information and create the device. Select `Modbus TCP` for the **Plugin** and enter `modbus-tcp-1` for the **Name**.
### Configure the Device
After creating the device, go to the device details page to configure the device parameters.
**IP Address**: Fill in the IP address of the machine running the Modbus TCP simulator. If Neuron and the simulator are running on the same machine, you can enter `127.0.0.1`.
**Port**: The default is 502. Modify it according to the listening port of the simulator.
Other parameters can be left as default.
### Configure Data Points
On the **Southbound Device Management** page, click the **Device Name** to enter the group page. Add a group and name it `group1`.
After entering the group, click the **Group Name** to enter the tag configuration page and create a tag.
![add-tag](./assets/tag-list-null.png)
**Name**: `tag-1`
**Address**: `1!40001` (Slave ID 1, holding register 40001)
**Attribute**: `Read` and `Write`
**Data Type**: `INT16`
### View Tag Values
After configuring the tags, go to the **Data Monitoring** page, select the device and group you just created, and you will see the values of the tags.
![data-monitor](./assets/data-monitoring.png)
## Create a Northbound Application
Use MQTT to send the data collected from the Modbus device to an MQTT Broker.
**Northbound Application Management** -> **Add Application**. In the pop-up dialog box, fill in the application information to create the application.
Select `MQTT` for the **Plugin** and enter `mqtt` for the **Name**.
### Configure the Northbound Application
After creating the northbound application, go to the northbound configuration page. The default server address is `broker.emqx.io` and the port is `1883`. If you need to connect to another MQTT Broker, please modify it according to the actual situation.
### Subscribe to Southbound Data Groups
On the **Northbound Application Management** page, click the northbound application to enter the **Subscription Management** page. Click **Add Subscription**, and in the pop-up dialog box, select the Modbus device and group you just created.
![subscribe](./assets/subscription-add.png)
Remember the topic used for the subscription, as you will need to subscribe to this topic later to see the data. The default topic format is `/neuron/{application_name}`.
At this point, the configuration for Neuron to collect data via Modbus TCP and send it via MQTT is complete.
## Use MQTTX to Subscribe to the Topic and View Data
After subscribing, open [MQTTX](https://mqttx.app/zh), create a new connection, and connect to `broker.emqx.io:1883` (modify according to the MQTT broker configured in the northbound application).
After a successful connection, subscribe to the topic `/neuron/mqtt` (modify according to the topic used in the northbound subscription), and you will see the tag data.
![mqttx](./assets/mqttx.png)
-81
View File
@@ -1,81 +0,0 @@
# Neuron Threat Model
## Purpose and scope
This model guides security review of the Neuron core, built-in plugins, and their integration boundaries. It describes intended security requirements and deployment assumptions, not a certification that every path implements them. Check the actual changed code, configuration, packaging, and release when assessing a finding.
This model covers code maintained in this open-source repository. Describe external inputs and deployment dependencies in terms of the interfaces they expose; derive security requirements from Neuron's own behavior and supported identity model.
## Deployment profiles
| Profile | Entry and authentication boundary | Review implications |
| --- | --- | --- |
| Direct management access | Management HTTP API, normally port 7000; checked-in configuration uses `127.0.0.1` and enables authentication | Binding, authentication, keys, process identity, and TLS termination are configurable. State the actual profile, not just defaults. |
| Management through an operator-managed reverse proxy | Client requests reach Neuron through an intermediary | Verify exposed routes, forwarding, TLS termination, and effective gateway/proxy authentication and authorization. Do not assume a proxy preserves the intended boundary. |
| Device and broker integrations | Protocol sockets, broker subscriptions, and configured remote endpoints | These paths do not inherit REST authentication. Trace their own peer identity, topic permissions, message handling, and device-write controls. |
Loopback reduces direct remote exposure but does not exclude access through a proxy, SSRF, a local process, shared network namespaces, or modified packaging. Conversely, a source-level handler defect does not by itself establish external reachability in a particular deployment.
Production deployments commonly segment management and OT networks. Segmentation affects exploit prerequisites; it is not a parser safety guarantee or a reason to exclude a compromised device or authorized malicious publisher.
## Assets and actors
Protect management identities and JWT signing material; device/broker credentials; configuration and persisted state; plugin libraries and schemas; process memory; collected data; device-write capability; and acquisition/forwarding availability.
Relevant actors include unauthenticated management peers, ordinary authenticated users, administrators, holders of tokens from configured issuers, malicious or compromised devices/brokers/publishers, and local processes. Administrators and installed native code possess substantial intended authority, but that does not authorize lower-trust actors to acquire it.
Use synthetic secrets and simulators for validation. Never direct a security test at customer equipment without explicit authorization.
## Data flows and trust boundaries
| Flow | Boundary | Required checks |
| --- | --- | --- |
| Client → REST handler | Network caller → management authority | Explicit authentication and operation-specific identity/authorization checks; complete error handling |
| JWT → identity | Externally supplied claims → trusted principal | Signature, permitted algorithm, configured issuer/key association, intended audience, applicable time constraints, and explicit identity semantics |
| Broker → MQTT write request | Authorized publisher → gateway/device action | Broker identity and direction-specific topic ACLs, message validation, configured target and write permissions |
| Device reply → parser/cache | Remote bytes → process memory and tag values | Actual frame lengths, destination bounds, counts/types, resource limits, and object lifetimes |
| Configuration/import/database → runtime | Stored or supplied data → allocations, paths, plugins, and connections | Revalidation, bounded decoding, path containment, safe persistence, and secret protection |
| Plugin library → gateway process | Native extension → service privileges | Authorized loading and trusted artifact admission; no assumed plugin sandbox |
| Driver write → field device | Management/integration command → operational state | Caller authority, configured destination, writable tag attributes, and value validation |
## Security requirements
### Management identity and JWT
A valid signature is necessary but not sufficient for accepting a token. Bind verification to the expected audience and configured issuer/key relationship; reject missing or invalid claims when required by that token profile. Do not treat tokens minted for other services as Neuron credentials merely because a signing key is shared.
Claims must not implicitly create administrative authority without an explicit supported identity policy. Distinguish legacy local-token conventions from admission of externally issued tokens. User creation, deletion, credential changes, and other sensitive actions require their own authorization decisions; a valid JWT alone does not establish that every operation is permitted.
Trace credential removal and token lifetime separately. Deleting an account need not invalidate an already signed stateless token unless a revocation mechanism exists; do not claim immediate revocation without evidence.
### Parsing, memory, and concurrency
Validate source bytes remaining and destination capacity independently. Check arithmetic before allocation, copy, pointer movement, or narrowing conversion. Reject inconsistent frame lengths, invalid types, excessive counts, oversized strings, and incomplete fragments. Keep allocation sizes consistent with iteration bounds.
For asynchronous messages, establish whether each buffer is borrowed, owned, copied, or transferred. Retain data until the last callback finishes. Cancellation, reconnection, configuration changes, and shutdown must not leave callbacks accessing freed state. Include failure and partial-initialization cleanup paths.
Treat persisted/imported data as input. Schema or UI checks are not substitutes for runtime validation, including when old configuration is restored after an upgrade.
### Cryptography and secrets
Validate peer identity as well as chain trust when the protocol requires it. Keep TLS server verification separate from possession of a client credential. Public certificates are not private keys; do not confuse their sensitivity. Shipped production credentials and reusable trusted CA private keys must not be public image material. Protect key storage, rotation, permissions, logs, backups, and exports.
### Metrics and intended capabilities
Prometheus metrics intentionally supports unauthenticated scraping. Absence of JWT verification on the documented metrics endpoint is not alone an authorization defect. Still review added labels/data for secrets, unexpected sensitive values, unbounded cardinality, expensive requests, and newly introduced control actions. Operators must place the endpoint appropriately.
Native plugins execute within the gateway process with its service-account authority. Authorized loading of native code is an intended capability, not evidence of container escape. A lower-trust path reaching plugin loading or corrupting plugin memory is a separate security question. Process-user execution, container-root execution, and host compromise require different evidence.
## Availability and impact limits
Identify the actual supervisor and restart policy. Recovery is deployment-dependent, not universal. Automatic recovery can reduce a single crash's duration, while repeated inputs may sustain a crash loop. It does not undo credential exposure, state corruption, unsafe device writes, or arbitrary execution.
Do not infer code execution from every invalid memory access. Conversely, do not reduce established memory corruption to harmless failure just because the process restarts. Account for build architecture, hardening, actual privileges, and demonstrated primitives. Do not extrapolate historical non-PIE or sanitizer assumptions to current binaries.
## Evidence and maintenance
Useful anchors are `neuron.json`, `src/argparse.c`, `src/utils/neu_jwt.c`, `include/neuron/utils/http_handler.h`, `plugins/restful/`, `plugins/mqtt/`, `src/core/plugin_manager.c`, `src/persist/`, and `CMakeLists.txt`.
A review should identify the changed boundary, attacker-controlled input, necessary configuration, actual effects, and a safe regression test. Separate implementation evidence from packaging assumptions and unverified downstream consequences.
Update this model when changing binding/authentication defaults, token profiles, metrics contents, user policies, plugin admission, write routing, persistence formats, process privileges, or supervision. Follow the [security policy](../SECURITY.md) for private disclosure and the [review rules](../AGENTS.md) for engineering guidance.
@@ -2,41 +2,48 @@ import asyncio
import time
from collections import defaultdict, deque
from robot.api import logger
from robot.api.deco import keyword, library
from gmqtt import Client as MQTTClient, Subscription
from gmqtt.mqtt.constants import MQTTv311, MQTTv50
from gmqtt.mqtt.constants import MQTTv311
def info(msg):
print(msg)
ROBOT_AUTO_KEYWORDS = False
@library
class Client:
"""
"""A keyword library for Robot Framework. It provides keywords for
performing various operations on an MQTT broker. See http://mqtt.org/
for more details on MQTT specification.
This library uses gmqtt: Python async MQTT client. For more information
on underlying methods and documentation, see:
https://github.com/wialon/gmqtt
"""
def __init__(self, reconnect_retries=-1, reconnect_delay=0.1):
ROBOT_LIBRARY_SCOPE = "GLOBAL"
ROBOT_LIBRARY_VERSION = "0.1.0"
def __init__(self, reconnect_retries=5, reconnect_delay=0):
"""
`reconnect_retries` number of reconnect attempt in case of lost
connections, default to -1
connections, default to 5
`reconnect_delay` delay between reconnect attemps in seconds,
default to 0.1
default to 0
"""
self._client = None
self._reconnect_retries = reconnect_retries
self._reconnect_delay = reconnect_delay
self._messages = defaultdict(deque)
self._handlers = {}
self._subcribed = asyncio.Event()
self._unsubcribed = asyncio.Event()
self._recved = asyncio.Event()
self._run = asyncio.get_event_loop().run_until_complete
@keyword
def connect(
self,
host,
@@ -64,6 +71,17 @@ class Client:
`clean_session` specifies the clean session flag for the connection
Examples:
Connect to a broker with default port and client id
| Connect | 127.0.0.1 |
Connect to a broker by specifying the port and client id explicitly
| Connect | 127.0.0.1 | 1883 | test.client |
Connect to a broker with clean session flag set to false
| Connect | 127.0.0.1 | clean_session=${false} |
"""
client = MQTTClient(client_id, clean_session=clean_session)
client.on_connect = self._on_connect
@@ -79,24 +97,28 @@ class Client:
)
self._client = client
self._run(self._client.connect(host, port, version=MQTTv50))
self._run(self._client.connect(host, port, version=MQTTv311))
@keyword
def disconnect(self):
"""Disconnect from MQTT Broker."""
"""Disconnect from MQTT Broker.
Example:
| Disconnect |
"""
self._run(self._client.disconnect())
self._messages.clear()
self._handlers.clear()
@keyword
def unsubscribe_all_and_clear_messages(self):
"""Unsubscribe all subscriptions and clear all messages in queue."""
for topic in self._messages:
self.unsubscribe(topic)
self._messages.clear()
self._handlers.clear()
def subscribe(
self, topic, qos=0, timeout=1, handler=None, no_except=False
):
@keyword
def subscribe(self, topic, qos=0, timeout=1):
"""Subscribe to a topic and return a message payload received
within the specified time.
@@ -106,47 +128,38 @@ class Client:
`timeout` duration of subscription, default to 1 second
`handler` callback on received message, should take one parameter `payload`
Examples:
`no_except` indicate to not throw exception
Subscribe and get a list of all messages received within 5 seconds
| ${messages}= | Subscribe | test/test | qos=1 | timeout=5 |
"""
timeout = 1 if timeout < 1 else timeout
self._client.subscribe(Subscription(topic, qos))
if self._wait(self._subcribed, timeout=timeout):
if no_except:
info(
f"WARN: MQTT client subscribe timeout, topic={topic} qos={qos}"
)
else:
raise Exception(
f"MQTT client subscribe timeout, topic={topic} qos={qos}"
)
info(f"MQTT client subscribed to topic={topic} qos={qos}")
if handler:
self._handlers[topic] = handler
raise Exception(
"MQTT client subscribe timeout topic=%s qos=%s" % (topic, qos))
logger.info("MQTT client subscribed to topic=%s qos=%s" % (topic, qos))
def unsubscribe(self, topic, timeout=1, no_except=False):
@keyword
def unsubscribe(self, topic, timeout=1):
"""Unsubscribe the client from the specified topic.
`topic` topic to unsubscribe from
`timeout` duration of unsubscription, default to 1 second
`no_except` indicate to not throw exception
Example:
| Unsubscribe | test/mqtt_test |
"""
timeout = 1 if timeout < 1 else timeout
self._client.unsubscribe(topic)
if self._wait(self._unsubcribed, timeout=timeout):
if no_except:
info(f"WARN: MQTT client unsubscribe timeout, topic={topic}")
else:
raise Exception(
f"MQTT client unsubscribe timeout, topic={topic}"
)
self._messages[topic].clear()
self._handlers.pop(topic, None)
info(f"MQTT client unsubscribed topic={topic}")
raise Exception("MQTT client unsubscribe timeout topic=%s" % topic)
logger.info("MQTT client unsubscribed topic=%s" % topic)
@keyword
def publish(self, topic, payload, qos=0, retain=False):
"""Publish a message to a topic with specified qos and retained flag.
It is required that a connection has been established using `Connect`
@@ -160,10 +173,14 @@ class Client:
`retain` retained flag
"""
self._client.publish(topic, payload, qos=qos,
retain=retain, user_property=[('traceparent', '00-8fb7651916cd43dd8448eb211c80319c-0000000000000000-01'), ('tracestate', 'service.name=ex,a=b,c=d,e=f,g=h')])
Examples:
| Publish | test/test | test message | 1 | ${false} |
"""
self._client.publish(topic, payload, qos=qos, retain=retain)
@keyword
def listen(self, topic, timeout=1):
"""Listen to a topic and return a message payload received within the
specified time. Requires an Subscribe to have been called previously.
@@ -172,8 +189,14 @@ class Client:
`timeout` duration to listen, default to 1 second
Examples:
Listen and get a message received within 5 seconds
| ${messages}= | Listen | test/test | timeout=5 |
"""
start = time.time_ns()
timeout = 1 if timeout < 1 else timeout
start = time.time()
while True:
if topic in self._messages and self._messages[topic]:
msg = self._messages[topic].popleft()
@@ -181,7 +204,7 @@ class Client:
if (
self._wait(self._recved, timeout=timeout)
or (time.time_ns() - start) >= timeout * 1_000_000_000
or (time.time() - start) >= timeout
):
break
@@ -190,29 +213,23 @@ class Client:
Returns true on timeout, false otherwise."""
try:
self._run(asyncio.wait_for(event.wait(), timeout))
event.clear()
except asyncio.TimeoutError:
return True
finally:
event.clear()
return False
def _on_connect(self, client, flags, rc, properties):
info("MQTT client connected")
logger.info("MQTT client connected")
def _on_message(self, client, topic, payload, qos, properties):
info(
"MQTT client received: topic=%s qos=%s payload=%s"
% (topic, qos, payload)
)
if topic in self._handlers:
self._handlers[topic](payload=payload.decode())
else:
self._messages[topic].append(payload.decode())
logger.info("MQTT client received: topic=%s qos=%s payload=%s" %
(topic, qos, payload))
self._messages[topic].append(payload.decode())
self._recved.set()
return 0
def _on_disconnect(self, client, packet, exc=None):
info("MQTT client disconnected")
logger.info("MQTT client disconnected")
def _on_subscribe(self, client, mid, qos, properties):
self._subcribed.set()
@@ -222,6 +239,9 @@ class Client:
if __name__ == "__main__":
# import logging
# logging.basicConfig(level=logging.DEBUG)
host = "127.0.0.1"
topic = "test/test_mqtt"
client = Client()
+235
View File
@@ -0,0 +1,235 @@
from os import remove
import os
import random
import subprocess
import shutil
import time
def prepare_persistence_dir():
os.system("mkdir -p build/persistence")
class Neuron(object):
process = 0
profiler_process = None
def __init__(self):
pass
def Start_Neuron(self):
prepare_persistence_dir()
self.process = subprocess.Popen(['./neuron', '--log'], cwd='build/')
return self.process
def Stop_Neuron(self, remove_persistence_data=True):
if self.profiler_process is not None:
# wait for plotting
time.sleep(5)
self.process.kill()
if remove_persistence_data:
os.system("rm build/persistence/sqlite.db")
prepare_persistence_dir()
def End_Neuron(self, process):
process.kill()
def Remove_Persistence(self):
os.system("rm build/persistence/sqlite.db")
prepare_persistence_dir()
def Profile_Neuron(self, interval, result_dir):
cmd = [
'psrecord', str(self.process.pid),
'--interval', str(interval),
'--plot', result_dir + '/cpu_mem.svg'
]
self.profiler_process = subprocess.Popen(cmd)
class Tool(object):
def __init__(self):
pass
def Array_Len(self, array):
return len(array)
class Plugin(object):
def __init__(self):
pass
def Get_Plugin_By_Name(self, splugins, name):
for plugin in splugins:
if plugin['name'] == name:
return plugin['id']
return 0
def Plugin_With_Name_Sholud_Exist(self, splugins, name):
assert 0 != self.Get_Plugin_By_Name(splugins, name)
def Plugin_With_Name_Should_Not_Exist(self, splugins, name):
assert 0 == self.Get_Plugin_By_Name(splugins, name)
class GrpConfig(object):
def __init__(self):
pass
def Get_Interval_In_Group_Config(self, gconfigs, gname):
for gconfig in gconfigs[0]:
if gconfig['name'] == gname:
return gconfig['interval']
return 0
def Group_Config_Size(self, gconfigs):
size = 0
for gconfig in gconfigs[0]:
size = size + 1
return size
def Get_Random_Group_Config(self, gconfigs):
return random.choice(gconfigs)['name']
def Group_Config_Check(self, gconfigs, name, interval):
ret = -1
for gconfig in gconfigs:
if gconfig['name'] == name:
if gconfig['interval'] == int(interval):
ret = 0
break
return ret
def Get_Group_Config_By_Name(self, sgconfigs, name):
for gconfig in sgconfigs:
if gconfig['name'] == name:
return gconfig['name']
return 0
class Node(object):
def __init__(self):
pass
def Get_Node_By_Name(self, snodes, name):
for node in snodes:
if node['name'] == name:
return True
return False
def Node_Should_Exist(self, snodes, name):
assert True == self.Get_Node_By_Name(snodes, name)
def Node_Should_Not_Exist(self, snodes, name):
assert False == self.Get_Node_By_Name(snodes, name)
def Node_With_Name_Should_Not_Exist(self, snodes, name):
assert 0 == self.Get_Node_By_Name(snodes, name)
def Get_Random_Node(self, snodes):
return random.choice(snodes)['id']
class Tag(object):
def __init__(self):
pass
def Tag_Check(self, tags, name, type, group_config_name, attribute, address):
ret = -1
for tag in tags:
if tag['name'] == name:
if tag['type'] == int(type) and tag['group_config_name'] == group_config_name and tag['attribute'] == int(attribute) and tag['address'] == address:
ret = 0
break
return ret
def Tag_Find_By_Name(self, tags, name):
for tag in tags:
if tag['name'] == name:
return tag
return None
class Subscribe(object):
def __init__(self):
pass
def Subscribe_Check(self, groups, node_id, group_config_name):
ret = -1
for group in groups:
if group['node_id'] == int(node_id) and group['group_config_name'] == group_config_name:
ret = 0
break
return ret
class Read(object):
def __init__(self):
pass
def Compare_Tag_Value_Bool(self, tags, name, value):
ret = -1
for tag in tags:
if tag['name'] == name:
if isinstance(value, str):
if (tag['value'] == False and value.lower() == "false" or
tag['value'] == True and value.lower() == "true"):
ret = 0
elif tag['value'] == int(value):
ret = 0
break
return ret
def Compare_Tag_Value_Int(self, tags, name, value):
ret = -1
for tag in tags:
if tag['name'] == name:
if int(tag['value']) == int(value):
ret = 0
break
return ret
def Compare_Tag_Value_Float(self, tags, name, value):
ret = -1
for tag in tags:
if tag['name'] == name:
if float(tag['value']) <= float(value) + 0.1 and float(tag['value']) >= float(value) - 0.1:
ret = 0
break
return ret
def Compare_Tag_Value_String(self, tags, name, value):
ret = -1
for tag in tags:
if tag['name'] == name:
if tag['value'] == str(value):
ret = 0
break
return ret
def Compare_Tag_Value_Strings(self, tags, name, value):
ret = -1
for tag in tags:
if tag['name'] == name:
if "\"" + tag['value'] + "\"" == str(value):
ret = 0
break
return ret
def Check_Tag_Error(self, tags, name, value):
ret = -1
for tag in tags:
if tag['name'] == name:
if int(tag['error']) == int(value):
ret = 0
break
return ret
@@ -0,0 +1,10 @@
{
"nodes": [
{
"type": 1,
"plugin_name": "modbus-tcp",
"name": "modbus-tcp-0000",
"state": 1
}
]
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0000",
"adapter_name": "modbus-tcp-0000"
}
@@ -0,0 +1,7 @@
{
"params": {
"host": "127.0.0.1",
"port": 60502,
"connection_mode": 0
}
}
@@ -0,0 +1,11 @@
{
"params": {
"req-topic": "Neuron/req",
"res-topic": "Neuron/resp",
"ssl": "false",
"host": "127.0.0.1",
"port": 1883,
"username": "",
"password": ""
}
}
@@ -0,0 +1,10 @@
{
"subscriptions": [
{
"sub_adapter_name": "mqtt-0000",
"group_config_name": "group-0000",
"src_adapter_name": "modbus-tcp-0000",
"read_interval": 1000
}
]
}
@@ -0,0 +1,3 @@
{
"plugins": []
}
@@ -0,0 +1,10 @@
{
"nodes": [
{
"type": 1,
"plugin_name": "modbus-tcp",
"name": "modbus-tcp-0000",
"state": 1
}
]
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0000",
"adapter_name": "modbus-tcp-0000"
}
@@ -0,0 +1,7 @@
{
"params": {
"host": "127.0.0.1",
"port": 60502,
"connection_mode": 0
}
}
@@ -0,0 +1,11 @@
{
"params": {
"req-topic": "Neuron/req",
"res-topic": "Neuron/resp",
"ssl": "false",
"host": "127.0.0.1",
"port": 1883,
"username": "",
"password": ""
}
}
@@ -0,0 +1,10 @@
{
"subscriptions": [
{
"sub_adapter_name": "mqtt-0000",
"group_config_name": "group-0000",
"src_adapter_name": "modbus-tcp-0000",
"read_interval": 1000
}
]
}
@@ -0,0 +1,3 @@
{
"plugins": []
}
+124
View File
@@ -0,0 +1,124 @@
{
"nodes": [
{
"type": 1,
"plugin_name": "modbus-tcp",
"name": "modbus-tcp-0000",
"state": 1
},
{
"type": 1,
"plugin_name": "modbus-tcp",
"name": "modbus-tcp-0001",
"state": 1
},
{
"type": 1,
"plugin_name": "modbus-tcp",
"name": "modbus-tcp-0002",
"state": 1
},
{
"type": 1,
"plugin_name": "modbus-tcp",
"name": "modbus-tcp-0003",
"state": 1
},
{
"type": 1,
"plugin_name": "modbus-tcp",
"name": "modbus-tcp-0004",
"state": 1
},
{
"type": 1,
"plugin_name": "modbus-tcp",
"name": "modbus-tcp-0005",
"state": 1
},
{
"type": 1,
"plugin_name": "modbus-tcp",
"name": "modbus-tcp-0006",
"state": 1
},
{
"type": 1,
"plugin_name": "modbus-tcp",
"name": "modbus-tcp-0007",
"state": 1
},
{
"type": 1,
"plugin_name": "modbus-tcp",
"name": "modbus-tcp-0008",
"state": 1
},
{
"type": 1,
"plugin_name": "modbus-tcp",
"name": "modbus-tcp-0009",
"state": 1
},
{
"type": 1,
"plugin_name": "neuron-opcua-plugin",
"name": "opcua-0000",
"state": 1
},
{
"type": 1,
"plugin_name": "neuron-opcua-plugin",
"name": "opcua-0001",
"state": 1
},
{
"type": 1,
"plugin_name": "neuron-opcua-plugin",
"name": "opcua-0002",
"state": 1
},
{
"type": 1,
"plugin_name": "neuron-opcua-plugin",
"name": "opcua-0003",
"state": 1
},
{
"type": 1,
"plugin_name": "neuron-opcua-plugin",
"name": "opcua-0004",
"state": 1
},
{
"type": 1,
"plugin_name": "neuron-opcua-plugin",
"name": "opcua-0005",
"state": 1
},
{
"type": 1,
"plugin_name": "neuron-opcua-plugin",
"name": "opcua-0006",
"state": 1
},
{
"type": 1,
"plugin_name": "neuron-opcua-plugin",
"name": "opcua-0007",
"state": 1
},
{
"type": 1,
"plugin_name": "neuron-opcua-plugin",
"name": "opcua-0008",
"state": 1
},
{
"type": 1,
"plugin_name": "neuron-opcua-plugin",
"name": "opcua-0009",
"state": 1
}
]
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0000",
"adapter_name": "modbus-tcp-0000"
}
@@ -0,0 +1,604 @@
{
"tags": [
{
"type": 4,
"name": "modbus1",
"attribute": 3,
"address": "1!400001"
},
{
"type": 4,
"name": "modbus2",
"attribute": 3,
"address": "1!400002"
},
{
"type": 4,
"name": "modbus3",
"attribute": 3,
"address": "1!400003"
},
{
"type": 4,
"name": "modbus4",
"attribute": 3,
"address": "1!400004"
},
{
"type": 4,
"name": "modbus5",
"attribute": 3,
"address": "1!400005"
},
{
"type": 4,
"name": "modbus6",
"attribute": 3,
"address": "1!400006"
},
{
"type": 4,
"name": "modbus7",
"attribute": 3,
"address": "1!400007"
},
{
"type": 4,
"name": "modbus8",
"attribute": 3,
"address": "1!400008"
},
{
"type": 4,
"name": "modbus9",
"attribute": 3,
"address": "1!400009"
},
{
"type": 4,
"name": "modbus10",
"attribute": 3,
"address": "1!400010"
},
{
"type": 4,
"name": "modbus11",
"attribute": 3,
"address": "1!400011"
},
{
"type": 4,
"name": "modbus12",
"attribute": 3,
"address": "1!400012"
},
{
"type": 4,
"name": "modbus13",
"attribute": 3,
"address": "1!400013"
},
{
"type": 4,
"name": "modbus14",
"attribute": 3,
"address": "1!400014"
},
{
"type": 4,
"name": "modbus15",
"attribute": 3,
"address": "1!400015"
},
{
"type": 4,
"name": "modbus16",
"attribute": 3,
"address": "1!400016"
},
{
"type": 4,
"name": "modbus17",
"attribute": 3,
"address": "1!400017"
},
{
"type": 4,
"name": "modbus18",
"attribute": 3,
"address": "1!400018"
},
{
"type": 4,
"name": "modbus19",
"attribute": 3,
"address": "1!400019"
},
{
"type": 4,
"name": "modbus20",
"attribute": 3,
"address": "1!400020"
},
{
"type": 4,
"name": "modbus21",
"attribute": 3,
"address": "1!400021"
},
{
"type": 4,
"name": "modbus22",
"attribute": 3,
"address": "1!400022"
},
{
"type": 4,
"name": "modbus23",
"attribute": 3,
"address": "1!400023"
},
{
"type": 4,
"name": "modbus24",
"attribute": 3,
"address": "1!400024"
},
{
"type": 4,
"name": "modbus25",
"attribute": 3,
"address": "1!400025"
},
{
"type": 4,
"name": "modbus26",
"attribute": 3,
"address": "1!400026"
},
{
"type": 4,
"name": "modbus27",
"attribute": 3,
"address": "1!400027"
},
{
"type": 4,
"name": "modbus28",
"attribute": 3,
"address": "1!400028"
},
{
"type": 4,
"name": "modbus29",
"attribute": 3,
"address": "1!400029"
},
{
"type": 4,
"name": "modbus30",
"attribute": 3,
"address": "1!400030"
},
{
"type": 4,
"name": "modbus31",
"attribute": 3,
"address": "1!400031"
},
{
"type": 4,
"name": "modbus32",
"attribute": 3,
"address": "1!400032"
},
{
"type": 4,
"name": "modbus33",
"attribute": 3,
"address": "1!400033"
},
{
"type": 4,
"name": "modbus34",
"attribute": 3,
"address": "1!400034"
},
{
"type": 4,
"name": "modbus35",
"attribute": 3,
"address": "1!400035"
},
{
"type": 4,
"name": "modbus36",
"attribute": 3,
"address": "1!400036"
},
{
"type": 4,
"name": "modbus37",
"attribute": 3,
"address": "1!400037"
},
{
"type": 4,
"name": "modbus38",
"attribute": 3,
"address": "1!400038"
},
{
"type": 4,
"name": "modbus39",
"attribute": 3,
"address": "1!400039"
},
{
"type": 4,
"name": "modbus40",
"attribute": 3,
"address": "1!400040"
},
{
"type": 4,
"name": "modbus41",
"attribute": 3,
"address": "1!400041"
},
{
"type": 4,
"name": "modbus42",
"attribute": 3,
"address": "1!400042"
},
{
"type": 4,
"name": "modbus43",
"attribute": 3,
"address": "1!400043"
},
{
"type": 4,
"name": "modbus44",
"attribute": 3,
"address": "1!400044"
},
{
"type": 4,
"name": "modbus45",
"attribute": 3,
"address": "1!400045"
},
{
"type": 4,
"name": "modbus46",
"attribute": 3,
"address": "1!400046"
},
{
"type": 4,
"name": "modbus47",
"attribute": 3,
"address": "1!400047"
},
{
"type": 4,
"name": "modbus48",
"attribute": 3,
"address": "1!400048"
},
{
"type": 4,
"name": "modbus49",
"attribute": 3,
"address": "1!400049"
},
{
"type": 4,
"name": "modbus50",
"attribute": 3,
"address": "1!400050"
},
{
"type": 4,
"name": "modbus51",
"attribute": 3,
"address": "1!400051"
},
{
"type": 4,
"name": "modbus52",
"attribute": 3,
"address": "1!400052"
},
{
"type": 4,
"name": "modbus53",
"attribute": 3,
"address": "1!400053"
},
{
"type": 4,
"name": "modbus54",
"attribute": 3,
"address": "1!400054"
},
{
"type": 4,
"name": "modbus55",
"attribute": 3,
"address": "1!400055"
},
{
"type": 4,
"name": "modbus56",
"attribute": 3,
"address": "1!400056"
},
{
"type": 4,
"name": "modbus57",
"attribute": 3,
"address": "1!400057"
},
{
"type": 4,
"name": "modbus58",
"attribute": 3,
"address": "1!400058"
},
{
"type": 4,
"name": "modbus59",
"attribute": 3,
"address": "1!400059"
},
{
"type": 4,
"name": "modbus60",
"attribute": 3,
"address": "1!400060"
},
{
"type": 4,
"name": "modbus61",
"attribute": 3,
"address": "1!400061"
},
{
"type": 4,
"name": "modbus62",
"attribute": 3,
"address": "1!400062"
},
{
"type": 4,
"name": "modbus63",
"attribute": 3,
"address": "1!400063"
},
{
"type": 4,
"name": "modbus64",
"attribute": 3,
"address": "1!400064"
},
{
"type": 4,
"name": "modbus65",
"attribute": 3,
"address": "1!400065"
},
{
"type": 4,
"name": "modbus66",
"attribute": 3,
"address": "1!400066"
},
{
"type": 4,
"name": "modbus67",
"attribute": 3,
"address": "1!400067"
},
{
"type": 4,
"name": "modbus68",
"attribute": 3,
"address": "1!400068"
},
{
"type": 4,
"name": "modbus69",
"attribute": 3,
"address": "1!400069"
},
{
"type": 4,
"name": "modbus70",
"attribute": 3,
"address": "1!400070"
},
{
"type": 4,
"name": "modbus71",
"attribute": 3,
"address": "1!400071"
},
{
"type": 4,
"name": "modbus72",
"attribute": 3,
"address": "1!400072"
},
{
"type": 4,
"name": "modbus73",
"attribute": 3,
"address": "1!400073"
},
{
"type": 4,
"name": "modbus74",
"attribute": 3,
"address": "1!400074"
},
{
"type": 4,
"name": "modbus75",
"attribute": 3,
"address": "1!400075"
},
{
"type": 4,
"name": "modbus76",
"attribute": 3,
"address": "1!400076"
},
{
"type": 4,
"name": "modbus77",
"attribute": 3,
"address": "1!400077"
},
{
"type": 4,
"name": "modbus78",
"attribute": 3,
"address": "1!400078"
},
{
"type": 4,
"name": "modbus79",
"attribute": 3,
"address": "1!400079"
},
{
"type": 4,
"name": "modbus80",
"attribute": 3,
"address": "1!400080"
},
{
"type": 4,
"name": "modbus81",
"attribute": 3,
"address": "1!400081"
},
{
"type": 4,
"name": "modbus82",
"attribute": 3,
"address": "1!400082"
},
{
"type": 4,
"name": "modbus83",
"attribute": 3,
"address": "1!400083"
},
{
"type": 4,
"name": "modbus84",
"attribute": 3,
"address": "1!400084"
},
{
"type": 4,
"name": "modbus85",
"attribute": 3,
"address": "1!400085"
},
{
"type": 4,
"name": "modbus86",
"attribute": 3,
"address": "1!400086"
},
{
"type": 4,
"name": "modbus87",
"attribute": 3,
"address": "1!400087"
},
{
"type": 4,
"name": "modbus88",
"attribute": 3,
"address": "1!400088"
},
{
"type": 4,
"name": "modbus89",
"attribute": 3,
"address": "1!400089"
},
{
"type": 4,
"name": "modbus90",
"attribute": 3,
"address": "1!400090"
},
{
"type": 4,
"name": "modbus91",
"attribute": 3,
"address": "1!400091"
},
{
"type": 4,
"name": "modbus92",
"attribute": 3,
"address": "1!400092"
},
{
"type": 4,
"name": "modbus93",
"attribute": 3,
"address": "1!400093"
},
{
"type": 4,
"name": "modbus94",
"attribute": 3,
"address": "1!400094"
},
{
"type": 4,
"name": "modbus95",
"attribute": 3,
"address": "1!400095"
},
{
"type": 4,
"name": "modbus96",
"attribute": 3,
"address": "1!400096"
},
{
"type": 4,
"name": "modbus97",
"attribute": 3,
"address": "1!400097"
},
{
"type": 4,
"name": "modbus98",
"attribute": 3,
"address": "1!400098"
},
{
"type": 4,
"name": "modbus99",
"attribute": 3,
"address": "1!400099"
},
{
"type": 4,
"name": "modbus100",
"attribute": 3,
"address": "1!400100"
}
]
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0001",
"adapter_name": "modbus-tcp-0000"
}
@@ -0,0 +1,604 @@
{
"tags": [
{
"type": 4,
"name": "modbus101",
"attribute": 3,
"address": "1!400101"
},
{
"type": 4,
"name": "modbus102",
"attribute": 3,
"address": "1!400102"
},
{
"type": 4,
"name": "modbus103",
"attribute": 3,
"address": "1!400103"
},
{
"type": 4,
"name": "modbus104",
"attribute": 3,
"address": "1!400104"
},
{
"type": 4,
"name": "modbus105",
"attribute": 3,
"address": "1!400105"
},
{
"type": 4,
"name": "modbus106",
"attribute": 3,
"address": "1!400106"
},
{
"type": 4,
"name": "modbus107",
"attribute": 3,
"address": "1!400107"
},
{
"type": 4,
"name": "modbus108",
"attribute": 3,
"address": "1!400108"
},
{
"type": 4,
"name": "modbus109",
"attribute": 3,
"address": "1!400109"
},
{
"type": 4,
"name": "modbus110",
"attribute": 3,
"address": "1!400110"
},
{
"type": 4,
"name": "modbus111",
"attribute": 3,
"address": "1!400111"
},
{
"type": 4,
"name": "modbus112",
"attribute": 3,
"address": "1!400112"
},
{
"type": 4,
"name": "modbus113",
"attribute": 3,
"address": "1!400113"
},
{
"type": 4,
"name": "modbus114",
"attribute": 3,
"address": "1!400114"
},
{
"type": 4,
"name": "modbus115",
"attribute": 3,
"address": "1!400115"
},
{
"type": 4,
"name": "modbus116",
"attribute": 3,
"address": "1!400116"
},
{
"type": 4,
"name": "modbus117",
"attribute": 3,
"address": "1!400117"
},
{
"type": 4,
"name": "modbus118",
"attribute": 3,
"address": "1!400118"
},
{
"type": 4,
"name": "modbus119",
"attribute": 3,
"address": "1!400119"
},
{
"type": 4,
"name": "modbus120",
"attribute": 3,
"address": "1!400120"
},
{
"type": 4,
"name": "modbus121",
"attribute": 3,
"address": "1!400121"
},
{
"type": 4,
"name": "modbus122",
"attribute": 3,
"address": "1!400122"
},
{
"type": 4,
"name": "modbus123",
"attribute": 3,
"address": "1!400123"
},
{
"type": 4,
"name": "modbus124",
"attribute": 3,
"address": "1!400124"
},
{
"type": 4,
"name": "modbus125",
"attribute": 3,
"address": "1!400125"
},
{
"type": 4,
"name": "modbus126",
"attribute": 3,
"address": "1!400126"
},
{
"type": 4,
"name": "modbus127",
"attribute": 3,
"address": "1!400127"
},
{
"type": 4,
"name": "modbus128",
"attribute": 3,
"address": "1!400128"
},
{
"type": 4,
"name": "modbus129",
"attribute": 3,
"address": "1!400129"
},
{
"type": 4,
"name": "modbus130",
"attribute": 3,
"address": "1!400130"
},
{
"type": 4,
"name": "modbus131",
"attribute": 3,
"address": "1!400131"
},
{
"type": 4,
"name": "modbus132",
"attribute": 3,
"address": "1!400132"
},
{
"type": 4,
"name": "modbus133",
"attribute": 3,
"address": "1!400133"
},
{
"type": 4,
"name": "modbus134",
"attribute": 3,
"address": "1!400134"
},
{
"type": 4,
"name": "modbus135",
"attribute": 3,
"address": "1!400135"
},
{
"type": 4,
"name": "modbus136",
"attribute": 3,
"address": "1!400136"
},
{
"type": 4,
"name": "modbus137",
"attribute": 3,
"address": "1!400137"
},
{
"type": 4,
"name": "modbus138",
"attribute": 3,
"address": "1!400138"
},
{
"type": 4,
"name": "modbus139",
"attribute": 3,
"address": "1!400139"
},
{
"type": 4,
"name": "modbus140",
"attribute": 3,
"address": "1!400140"
},
{
"type": 4,
"name": "modbus141",
"attribute": 3,
"address": "1!400141"
},
{
"type": 4,
"name": "modbus142",
"attribute": 3,
"address": "1!400142"
},
{
"type": 4,
"name": "modbus143",
"attribute": 3,
"address": "1!400143"
},
{
"type": 4,
"name": "modbus144",
"attribute": 3,
"address": "1!400144"
},
{
"type": 4,
"name": "modbus145",
"attribute": 3,
"address": "1!400145"
},
{
"type": 4,
"name": "modbus146",
"attribute": 3,
"address": "1!400146"
},
{
"type": 4,
"name": "modbus147",
"attribute": 3,
"address": "1!400147"
},
{
"type": 4,
"name": "modbus148",
"attribute": 3,
"address": "1!400148"
},
{
"type": 4,
"name": "modbus149",
"attribute": 3,
"address": "1!400149"
},
{
"type": 4,
"name": "modbus150",
"attribute": 3,
"address": "1!400150"
},
{
"type": 4,
"name": "modbus151",
"attribute": 3,
"address": "1!400151"
},
{
"type": 4,
"name": "modbus152",
"attribute": 3,
"address": "1!400152"
},
{
"type": 4,
"name": "modbus153",
"attribute": 3,
"address": "1!400153"
},
{
"type": 4,
"name": "modbus154",
"attribute": 3,
"address": "1!400154"
},
{
"type": 4,
"name": "modbus155",
"attribute": 3,
"address": "1!400155"
},
{
"type": 4,
"name": "modbus156",
"attribute": 3,
"address": "1!400156"
},
{
"type": 4,
"name": "modbus157",
"attribute": 3,
"address": "1!400157"
},
{
"type": 4,
"name": "modbus158",
"attribute": 3,
"address": "1!400158"
},
{
"type": 4,
"name": "modbus159",
"attribute": 3,
"address": "1!400159"
},
{
"type": 4,
"name": "modbus160",
"attribute": 3,
"address": "1!400160"
},
{
"type": 4,
"name": "modbus161",
"attribute": 3,
"address": "1!400161"
},
{
"type": 4,
"name": "modbus162",
"attribute": 3,
"address": "1!400162"
},
{
"type": 4,
"name": "modbus163",
"attribute": 3,
"address": "1!400163"
},
{
"type": 4,
"name": "modbus164",
"attribute": 3,
"address": "1!400164"
},
{
"type": 4,
"name": "modbus165",
"attribute": 3,
"address": "1!400165"
},
{
"type": 4,
"name": "modbus166",
"attribute": 3,
"address": "1!400166"
},
{
"type": 4,
"name": "modbus167",
"attribute": 3,
"address": "1!400167"
},
{
"type": 4,
"name": "modbus168",
"attribute": 3,
"address": "1!400168"
},
{
"type": 4,
"name": "modbus169",
"attribute": 3,
"address": "1!400169"
},
{
"type": 4,
"name": "modbus170",
"attribute": 3,
"address": "1!400170"
},
{
"type": 4,
"name": "modbus171",
"attribute": 3,
"address": "1!400171"
},
{
"type": 4,
"name": "modbus172",
"attribute": 3,
"address": "1!400172"
},
{
"type": 4,
"name": "modbus173",
"attribute": 3,
"address": "1!400173"
},
{
"type": 4,
"name": "modbus174",
"attribute": 3,
"address": "1!400174"
},
{
"type": 4,
"name": "modbus175",
"attribute": 3,
"address": "1!400175"
},
{
"type": 4,
"name": "modbus176",
"attribute": 3,
"address": "1!400176"
},
{
"type": 4,
"name": "modbus177",
"attribute": 3,
"address": "1!400177"
},
{
"type": 4,
"name": "modbus178",
"attribute": 3,
"address": "1!400178"
},
{
"type": 4,
"name": "modbus179",
"attribute": 3,
"address": "1!400179"
},
{
"type": 4,
"name": "modbus180",
"attribute": 3,
"address": "1!400180"
},
{
"type": 4,
"name": "modbus181",
"attribute": 3,
"address": "1!400181"
},
{
"type": 4,
"name": "modbus182",
"attribute": 3,
"address": "1!400182"
},
{
"type": 4,
"name": "modbus183",
"attribute": 3,
"address": "1!400183"
},
{
"type": 4,
"name": "modbus184",
"attribute": 3,
"address": "1!400184"
},
{
"type": 4,
"name": "modbus185",
"attribute": 3,
"address": "1!400185"
},
{
"type": 4,
"name": "modbus186",
"attribute": 3,
"address": "1!400186"
},
{
"type": 4,
"name": "modbus187",
"attribute": 3,
"address": "1!400187"
},
{
"type": 4,
"name": "modbus188",
"attribute": 3,
"address": "1!400188"
},
{
"type": 4,
"name": "modbus189",
"attribute": 3,
"address": "1!400189"
},
{
"type": 4,
"name": "modbus190",
"attribute": 3,
"address": "1!400190"
},
{
"type": 4,
"name": "modbus191",
"attribute": 3,
"address": "1!400191"
},
{
"type": 4,
"name": "modbus192",
"attribute": 3,
"address": "1!400192"
},
{
"type": 4,
"name": "modbus193",
"attribute": 3,
"address": "1!400193"
},
{
"type": 4,
"name": "modbus194",
"attribute": 3,
"address": "1!400194"
},
{
"type": 4,
"name": "modbus195",
"attribute": 3,
"address": "1!400195"
},
{
"type": 4,
"name": "modbus196",
"attribute": 3,
"address": "1!400196"
},
{
"type": 4,
"name": "modbus197",
"attribute": 3,
"address": "1!400197"
},
{
"type": 4,
"name": "modbus198",
"attribute": 3,
"address": "1!400198"
},
{
"type": 4,
"name": "modbus199",
"attribute": 3,
"address": "1!400199"
},
{
"type": 4,
"name": "modbus200",
"attribute": 3,
"address": "1!400200"
}
]
}
@@ -0,0 +1,7 @@
{
"params": {
"host":"127.0.0.1",
"port":60502,
"connection_mode":0
}
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0000",
"adapter_name": "modbus-tcp-0001"
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0001",
"adapter_name": "modbus-tcp-0001"
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0002",
"adapter_name": "modbus-tcp-0001"
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0003",
"adapter_name": "modbus-tcp-0001"
}
@@ -0,0 +1,7 @@
{
"params": {
"host":"127.0.0.1",
"port":60502,
"connection_mode":0
}
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0000",
"adapter_name": "modbus-tcp-0002"
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0001",
"adapter_name": "modbus-tcp-0002"
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0002",
"adapter_name": "modbus-tcp-0002"
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0003",
"adapter_name": "modbus-tcp-0002"
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0004",
"adapter_name": "modbus-tcp-0002"
}
@@ -0,0 +1,7 @@
{
"params": {
"host":"127.0.0.1",
"port":60502,
"connection_mode":0
}
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0000",
"adapter_name": "modbus-tcp-0003"
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0001",
"adapter_name": "modbus-tcp-0003"
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0002",
"adapter_name": "modbus-tcp-0003"
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0003",
"adapter_name": "modbus-tcp-0003"
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0004",
"adapter_name": "modbus-tcp-0003"
}
@@ -0,0 +1,7 @@
{
"params": {
"host":"127.0.0.1",
"port":60502,
"connection_mode":0
}
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0000",
"adapter_name": "modbus-tcp-0004"
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0001",
"adapter_name": "modbus-tcp-0004"
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0002",
"adapter_name": "modbus-tcp-0004"
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0003",
"adapter_name": "modbus-tcp-0004"
}
@@ -0,0 +1,5 @@
{
"read_interval": 1000,
"group_config_name": "group-0004",
"adapter_name": "modbus-tcp-0004"
}
@@ -0,0 +1,7 @@
{
"params": {
"host":"127.0.0.1",
"port":60502,
"connection_mode":0
}
}

Some files were not shown because too many files have changed in this diff Show More