Compare commits

..
145 Commits
Author SHA1 Message Date
fengzeroz fd3b96a568 Merge pull request #2779 from fengzeroz/v2.15
version: 2.15.0
2026-06-02 17:07:47 +08:00
fengzero 36fe84ab6e version: 2.15.0 2026-06-02 09:06:51 +00:00
fengzeroz ccc9492f82 Merge pull request #2776 from fengzeroz/v2.15
refactor: replace strcpy with strncpy
2026-06-02 16:27:18 +08:00
fengzero 4b97979d38 refactor: replace strcpy with strncpy 2026-06-02 08:10:30 +00:00
fengzeroz 2a4e6a39e9 Merge pull request #2772 from fengzeroz/v2.15
api(cid,tpy,db): enable validate jwt
2026-05-29 14:50:42 +08:00
fengzero b1bf3830ad api(cid,tpy,db): enable validate jwt 2026-05-29 06:26:18 +00:00
fengzeroz 0e850fe85a Merge pull request #2767 from fengzeroz/v2.15
fix(mqtt): replace strcpy with strncpy
2026-05-29 14:24:15 +08:00
fengzero 8ac388f414 fix(mqtt): replace strcpy with strncpy 2026-05-29 06:09:54 +00:00
fengzeroz 3400c7546f Merge pull request #2765 from hxy7yx/v2.15-1
[2.15]feat(mqtt): add connection watchdog and reason-code logging
2026-05-26 10:23:23 +08:00
hxy7yx a4b863818f feat(mqtt): add connection watchdog and reason-code logging 2026-05-24 18:47:25 -07:00
fengzeroz 62ed730ba6 Merge pull request #2761 from fengzeroz/v2.15
fix(mqtt): verify the length of the data received from protobuf
2026-05-25 09:37:32 +08:00
fengzero e9c318296b fix(mqtt): verify the length of the data received from protobuf 2026-05-22 08:47:49 +00:00
fengzeroz 848a38e962 Merge pull request #2752 from fengzeroz/v2.15
delete api related to log file (deprecated)
2026-05-22 15:51:37 +08:00
fengzero dd689ca4d2 delete api related to log file (deprecated) 2026-05-22 07:34:04 +00:00
fengzeroz e878a3fd8e Merge pull request #2746 from fengzeroz/v2.15
version: 2.15.0-beta3
2026-05-18 09:39:21 +08:00
fengzero 8b1d72b9dc version: 2.15.0-beta3 2026-05-18 01:38:37 +00:00
fengzeroz 638a2d08f9 Merge pull request #2745 from fengzeroz/v2.15
version: 2.15.0-beta2
2026-05-11 13:51:47 +08:00
fengzero 7000e3f7a6 version: 2.15.0-beta2 2026-05-11 05:51:14 +00:00
fengzeroz 9e9b5be252 Merge pull request #2744 from fengzeroz/v2.15
branch v2.15
2026-05-06 14:19:34 +08:00
fengzero ed67628e92 branch v2.15 2026-05-06 06:06:30 +00:00
fengzeroz 9f8f4db9e0 Merge pull request #2741 from hxy7yx/main-1
feat(azure):support offline data caching
2026-05-06 10:45:57 +08:00
hxy7yx c42e0a8be5 feat(azure):support offline data caching 2026-04-26 20:41:16 -07:00
fengzeroz 6e243ab60f Merge pull request #2736 from moruoxian/upstream-modbus-wswr
modbus: add 64-bit byte order support for WS and WR
2026-04-21 10:36:28 +08:00
wangjun c99999b8c9 modbus: add 64-bit byte order support for WS and WR
Add support for WS (34 12 78 56) and WR (56 78 12 34) as additional 64-bit byte orders for Modbus int64, uint64, and double tags.

This change extends address suffix parsing, updates 64-bit endian conversion in the Modbus read/write paths, updates the Modbus TCP/RTU schemas, and adds functional test coverage.

Signed-off-by: wangjun <794997265@qq.com>
2026-04-16 13:59:53 +08:00
fengzeroz af61f406c0 Merge pull request #2735 from hxy7yx/main-1
kafka:add libzstd & libsasl2
2026-04-16 13:51:28 +08:00
hxy7yx 243778b229 kafka:add libzstd & libsasl2 2026-04-13 01:25:15 -07:00
fengzeroz 207da6dbc1 Merge pull request #2727 from hxy7yx/main-1
feat:add kafka plugin
2026-04-13 11:25:00 +08:00
hxy7yx 7c03aba923 mqtt: report driver node state immediately 2026-04-12 18:55:21 -07:00
fengzeroz 522db9fc9d Merge pull request #2731 from ZhouBox/main
fix(core): correct parameter order in neu_req_del_tag_copy
2026-04-09 10:56:41 +08:00
ZhouBox 118dfc08b8 fix(core): correct parameter order in neu_req_del_tag_copy
- Fix reversed parameter order in neu_req_del_tag_copy call at manager.c:1488
- Add missing return statement on strdup failure in neu_req_del_tag_copy function
2026-04-09 10:10:58 +08:00
fengzeroz 84b0a5c6a2 Merge pull request #2728 from ZhouBox/main
fix bug:json object equality check failed
2026-04-08 14:15:36 +08:00
ZhouBox a485bb6f56 fix bug:json object equality check failed 2026-04-08 13:53:33 +08:00
hxy7yx feca615f61 feat:add kafka plugin 2026-04-07 20:37:05 -07:00
fengzeroz f300668e76 Merge pull request #2724 from fengzeroz/main
fix: read tag with unit in read_group_paginate
2026-03-27 15:47:17 +08:00
fengzero 8cce511761 fix: read tag with unit in read_group_paginate 2026-03-27 07:43:55 +00:00
fengzeroz 0c9ff7fb8c Merge pull request #2723 from fengzeroz/main
fix: read tag with unit
2026-03-27 15:22:06 +08:00
fengzero 937f536194 fix: read tag with unit 2026-03-27 07:05:41 +00:00
fengzeroz 3cc99d9b00 Merge pull request #2722 from fengzeroz/main
tag: option unit
2026-03-27 14:31:31 +08:00
fengzero ae4623ed7e tag: option unit 2026-03-27 06:17:27 +00:00
fengzeroz b8a9ce4b4e Merge pull request #2718 from hxy7yx/main-1
feat(tag):supports renaming tags
2026-03-25 09:32:26 +08:00
fengzeroz f730eb0369 Merge pull request #2720 from hxy7yx/main-2
datalayers:switch to asynchronous connection
2026-03-25 09:31:55 +08:00
hxy7yx 2dcdadc40e datalayers:switch to asynchronous connection 2026-03-22 23:58:36 -07:00
hxy7yx 4174f3eee4 feat(tag):supports renaming tags 2026-03-22 20:08:34 -07:00
fengzeroz 0ff2c57bfd Merge pull request #2719 from fengzeroz/main
tag desc length: 256
2026-03-23 11:02:11 +08:00
fengzero 8a5426b707 tag desc length: 256 2026-03-23 02:35:14 +00:00
fengzeroz 3f6517a8b9 Merge pull request #2709 from hxy7yx/main-1
upgrade nanosdk
2026-03-13 17:07:21 +08:00
fengzeroz 171b1226e9 Merge pull request #2710 from ZhouBox/main
log errno detail on message forward failure
2026-03-13 17:06:35 +08:00
ZhouBox 39c39a5371 fix monitor ready check to use abstract socket name index 2026-03-13 16:39:33 +08:00
ZhouBox e40aa30a83 log errno detail on message forward failure 2026-03-13 15:29:12 +08:00
hxy7yx f6dd2bdb94 upgrade nanosdk 2026-03-10 18:30:46 -07:00
fengzeroz 32325490c8 Merge pull request #2706 from fengzeroz/main
filter array in metas
2026-03-10 14:13:46 +08:00
fengzero 24fb80efd7 filter array in metas 2026-03-10 03:13:34 +00:00
fengzeroz 317463b816 Merge pull request #2702 from ZhouBox/main
compile:support gcc 14
2026-03-10 10:53:55 +08:00
ZhouBox 878d1d42c7 compile:support gcc 14 2026-03-10 09:47:17 +08:00
fengzeroz b506fdceb6 Merge pull request #2700 from ZhouBox/main
fixbug:heap use after free
2026-03-03 13:38:11 +08:00
xiang.zhou aebbf6cfd3 fixbug:heap use after free 2026-03-03 11:10:24 +08:00
xiang.zhou a5ae436104 Merge branch 'main' of github.com:ZhouBox/neuron 2026-02-27 10:30:27 +08:00
fengzeroz b4ef7d631c Merge pull request #2694 from ZhouBox/opcua
update scan tags api
2026-02-26 17:28:18 +08:00
fengzeroz f17068886b Merge pull request #2692 from ZhouBox/main
update disconnect error status code and update scan node api
2026-02-25 15:26:10 +08:00
fengzeroz e490fe3980 Merge pull request #2693 from fengzeroz/main
core(driver): try connect device when no tags
2026-02-24 15:02:09 +08:00
fengzero f3eecd01af core(driver): try connect device when no tags 2026-02-24 06:38:10 +00:00
xiang.zhou a9beb81c41 update scan tags api 2026-02-11 15:09:04 +08:00
xiang.zhou 0159b2590b update scan tags api 2026-02-11 15:03:50 +08:00
xiang.zhou 98dfb6601c update disconnect error status code and update scan node api 2026-02-09 13:34:40 +08:00
fengzeroz a2bb561cd6 Merge pull request #2689 from fengzeroz/main
fix crash & import tags
2026-02-03 14:04:00 +08:00
fengzero a6764d7395 rm mqtt db cache 2026-02-03 03:50:59 +00:00
fengzero 9687801d5a reuse tcp server port 2026-02-03 03:35:05 +00:00
fengzero 9d2f763bea fix: import apps crash && import empty tags 2026-02-03 03:35:05 +00:00
fengzeroz 3dee50ddeb Merge pull request #2687 from fengzeroz/main
fix: import apps crash
2026-01-29 16:07:43 +08:00
fengzero c9bae5e737 fix: import apps crash 2026-01-29 07:52:23 +00:00
fengzeroz 4533901078 Merge pull request #2682 from fengzeroz/main
default log config
2026-01-27 18:10:11 +08:00
fengzero 2231d4eac7 core(cache): update_im_f_m, filter dup value 2026-01-27 09:30:52 +00:00
fengzero 533c01d07d modify default log config 2026-01-27 09:16:07 +00:00
fengzeroz 4b26d00106 Merge pull request #2678 from fengzeroz/main
fix tag & simulator bug
2026-01-27 10:33:15 +08:00
fengzero 355cfd589b simulator: close fd & exit when recv signal 2026-01-27 02:16:15 +00:00
fengzero a6fdcb545f test query nodes by tags 2026-01-26 03:30:55 +00:00
fengzero de03827d6b fix import tags in defalut node 2026-01-26 03:30:31 +00:00
fengzeroz b6e707fb23 Merge pull request #2675 from fengzeroz/main
simulator: close fd
2026-01-23 14:49:46 +08:00
fengzero 0bca9e39a5 export app: include static tags 2026-01-23 02:41:59 +00:00
fengzero 3086974da4 modbus tag address range 2026-01-22 06:04:12 +00:00
fengzero 938efcfa0b simulator: close fd 2026-01-22 03:39:01 +00:00
fengzeroz 1eb1332989 Merge pull request #2672 from fengzeroz/main
node tags test
2026-01-21 13:51:05 +08:00
fengzero ebc11baafd node tags test 2026-01-21 02:54:38 +00:00
fengzero 4a13b39ec6 node tags: filter spec symbol 2026-01-21 02:35:04 +00:00
fengzeroz 66ab7bc2eb Merge pull request #2671 from ZhouBox/main
memery leak
2026-01-20 15:10:49 +08:00
fengzeroz 7490e6a42e Merge pull request #2663 from fengzeroz/main
node tags
2026-01-20 15:00:59 +08:00
fengzero 9d93a4d410 fix query by tags 2026-01-20 06:48:22 +00:00
fengzero e3d2cb7336 fix query by tags 2026-01-20 06:48:16 +00:00
fengzero abb76d3e80 get nodes: rm query tags 2026-01-20 06:48:07 +00:00
xiang.zhou 0b3f0f8088 memery leak 2026-01-20 14:47:54 +08:00
fengzero f080ec2711 write tag log 2026-01-19 06:56:13 +00:00
fengzero f6921ee2fb get nodes by tags 2026-01-19 06:56:06 +00:00
fengzero c52fae40c8 nodes: support tags 2026-01-19 06:56:00 +00:00
fengzeroz 3bb42f900f Merge pull request #2655 from fengzeroz/main
cid name len
2026-01-06 14:31:07 +08:00
fengzero bbda93038c cid name len 2026-01-06 06:25:20 +00:00
fengzeroz 9a5c1862b8 Merge pull request #2652 from hxy7yx/main-2
chore:update key from nodes to apps
2025-12-26 10:15:32 +08:00
hxy7yx 0652bbe00b chore:update key from nodes to apps 2025-12-25 18:04:31 -08:00
fengzeroz 6c088356f6 Merge pull request #2648 from hxy7yx/main-2
feat:export&import apps
2025-12-25 10:09:46 +08:00
fengzeroz 7441908f3a Merge pull request #2651 from fengzeroz/main
fix(group): free group context with old group
2025-12-25 10:08:54 +08:00
hxy7yx 0a32d72fcd feat:export&import apps 2025-12-24 18:06:39 -08:00
fengzero bda5ef1b2e fix(group): free group context with old group 2025-12-25 02:03:55 +00:00
fengzeroz 6b47dc6a4a Merge pull request #2645 from fengzeroz/main
update readme
2025-12-24 15:17:39 +08:00
fengzero 16ad3f6873 update readme 2025-12-24 06:28:16 +00:00
fengzeroz 63f9a0eb9c Merge pull request #2644 from fengzeroz/main
readme: remove cn
2025-12-24 14:18:44 +08:00
fengzeroz 942fcf8663 Merge pull request #2643 from hxy7yx/main-2
feat:export&import apps
2025-12-24 14:18:12 +08:00
fengzero 05b6d6be5a readme: about NeuronEX 2025-12-24 06:16:31 +00:00
fengzero 6c34076976 readme: remove cn 2025-12-24 05:50:43 +00:00
hxy7yx 96ebfa4034 feat:export&import apps 2025-12-21 18:57:19 -08:00
fengzeroz 2ca52aecec Merge pull request #2642 from fengzeroz/main
fix(connection): close fd if ip invalid
2025-12-19 15:08:56 +08:00
fengzero 989421f000 fix(connection): close fd if ip invalid 2025-12-19 07:00:57 +00:00
fengzeroz 88e5fc85b7 Merge pull request #2632 from fengzeroz/main
cid: parse float array
2025-12-12 17:31:09 +08:00
fengzeroz 14b55309e4 Merge pull request #2631 from hxy7yx/main-1
fix(simulator):increase update frequency
2025-12-12 17:30:57 +08:00
fengzero f761bb9c85 cid: parse float array 2025-12-12 03:37:46 +00:00
hxy7yx c38ae03b2b fix(simulator):increase update frequency 2025-12-11 17:57:47 -08:00
fengzeroz b522583f51 Merge pull request #2630 from fengzeroz/main
cid: parse VisString65
2025-12-09 16:06:19 +08:00
fengzero 9f2b04300f cid: parse VisString65 2025-12-09 07:00:25 +00:00
fengzeroz d94afeedf6 Merge pull request #2623 from ZhouBox/main
fixbug:double free about json obj
2025-12-04 17:22:12 +08:00
xiang.zhou ffe0316e5f fixbug:double free about json obj 2025-12-04 15:20:34 +08:00
fengzeroz 7457543471 Merge pull request #2622 from fengzeroz/main
event log
2025-11-28 13:47:29 +08:00
fengzero 82615c0908 event: log category 2025-11-28 03:00:12 +00:00
fengzero 584732fe8c rm unused node cache sql 2025-11-26 02:55:41 +00:00
fengzeroz 8c11f83540 Merge pull request #2621 from hxy7yx/main-1
feat:add modbus-tcp simulator
2025-11-26 10:55:28 +08:00
hxy7yx c9bf291fa2 feat:add modbus-tcp simulator 2025-11-24 02:09:43 -08:00
fengzeroz ae959acc52 Merge pull request #2617 from ZhouBox/main
ekuiper:add warn log
2025-11-20 10:01:53 +08:00
xiang.zhou d1b3838695 ekuiper:add warn log 2025-11-19 16:37:11 +08:00
fengzeroz 159be9f511 Merge pull request #2614 from hxy7yx/main-2
add notice log for write tag/tags/gtags
2025-11-19 14:39:06 +08:00
hxy7yx e07e5ad2e8 add notice log for write tag/tags/tags 2025-11-18 22:24:31 -08:00
fengzeroz 68c9dc0b0e Merge pull request #2610 from ZhouBox/main
ekuiper:add log
2025-11-19 11:04:15 +08:00
xiang.zhou ef859090bb ekuiper:add log 2025-11-19 10:28:25 +08:00
fengzeroz 04defee4da Merge pull request #2605 from hxy7yx/main-1
add info logging for driver write tags
2025-11-12 10:17:44 +08:00
hxy7yx 811ba04408 add info logging for driver write tags 2025-11-10 18:12:12 -08:00
fengzeroz 1dc53e3ff1 Merge pull request #2603 from ZhouBox/main
reporting incomplete trace when timeout
2025-11-06 10:03:12 +08:00
xiang.zhou 14326c8da1 reporting incomplete trace when timeout 2025-11-04 15:04:13 +08:00
fengzeroz d0f7335c2b Merge pull request #2596 from hxy7yx/main-1
fix msg_internal.h
2025-10-22 10:01:53 +08:00
hxy7yx 157618269f fix msg_internal.h 2025-10-21 18:39:50 -07:00
fengzeroz c8f5feb28e Merge pull request #2591 from ZhouBox/main
1.update cert schema 2.add datatag when sub app frist sending
2025-10-22 09:31:46 +08:00
fengzeroz acd9f87ee8 Merge pull request #2595 from fengzeroz/main
export db: skip load failed data
2025-10-21 16:47:10 +08:00
xiang.zhou 763783b4d9 add index 2025-10-21 16:18:36 +08:00
fengzero 4d42b2ab16 export db: skip load failed data 2025-10-21 07:31:49 +00:00
xiang.zhou 472044a2a3 1.update cert schema 2.add datatag when sub app frist sending 2025-10-21 14:48:50 +08:00
fengzeroz 87817e080d Merge pull request #2589 from hxy7yx/main-1
fix(log):global config
2025-10-21 13:34:58 +08:00
hxy7yx 65b87e37ff fix(log):global config 2025-10-20 19:38:19 -07:00
fengzeroz 6c8b684f80 Merge pull request #2581 from fengzeroz/main
fix: arm32 compile error
2025-10-20 14:53:57 +08:00
fengzero 161172632c replace tar with zip db data 2025-10-20 03:29:29 +00:00
fengzero 84ecdde3d4 fix: arm32 compile error 2025-10-17 06:38:42 +00:00
157 changed files with 8663 additions and 2107 deletions
+125 -4
View File
@@ -27,7 +27,7 @@ jobs:
cppcheck:
runs-on: ubuntu-24.04
container: ghcr.io/neugates/build:x86_64-v2.13
container: ghcr.io/neugates/build:x86_64-v2.15
steps:
- uses: actions/checkout@v4
@@ -54,7 +54,7 @@ jobs:
build:
runs-on: ubuntu-24.04
container: ghcr.io/neugates/build:x86_64-v2.13
container: ghcr.io/neugates/build:x86_64-v2.15
steps:
- uses: actions/checkout@v4
@@ -77,7 +77,7 @@ jobs:
ut:
runs-on: ubuntu-24.04
container: ghcr.io/neugates/build:x86_64-v2.13
container: ghcr.io/neugates/build:x86_64-v2.15
needs: [build]
steps:
@@ -97,9 +97,25 @@ jobs:
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
- 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-v2.13
container: ghcr.io/neugates/build:x86_64-v2.15
needs: [build]
strategy:
matrix:
@@ -131,6 +147,7 @@ jobs:
--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 \
@@ -145,3 +162,107 @@ jobs:
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-v2.15
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/v2.15/libs/x86_64-linux-gnu/lib/librdkafka* || echo "no librdkafka"
ls -la /home/neuron/v2.15/libs/x86_64-linux-gnu/lib/libzstd* || echo "no libzstd"
ls -la /home/neuron/v2.15/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/v2.15/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
+4 -4
View File
@@ -8,9 +8,9 @@ on:
- published
env:
VERSION: 2.13.2
VERSION: 2.15.0
UI_VERSION: 2.6.1
BRANCH: v2.13
BRANCH: v2.15
jobs:
cross-build:
@@ -19,7 +19,7 @@ jobs:
matrix:
arch:
- [x86_64-linux-gnu, x86_64, amd64]
container: ghcr.io/neugates/build:${{ matrix.arch[1] }}-v2.13
container: ghcr.io/neugates/build:${{ matrix.arch[1] }}-v2.15
steps:
- uses: actions/checkout@v4
@@ -62,7 +62,7 @@ jobs:
matrix:
arch:
- [x86_64-linux-gnu, x86_64, amd64]
container: ghcr.io/neugates/build:${{ matrix.arch[1] }}-v2.13
container: ghcr.io/neugates/build:${{ matrix.arch[1] }}-v2.15
steps:
- uses: actions/checkout@v4
+2 -2
View File
@@ -11,7 +11,7 @@ on:
jobs:
build:
runs-on: ubuntu-24.04
container: ghcr.io/neugates/build:x86_64-v2.13
container: ghcr.io/neugates/build:x86_64-main
steps:
- uses: actions/checkout@v4
@@ -33,7 +33,7 @@ jobs:
unused_plugins_test:
runs-on: ubuntu-24.04
container: ghcr.io/neugates/build:x86_64-v2.13
container: ghcr.io/neugates/build:x86_64-main
needs: [build]
strategy:
matrix:
+5 -7
View File
@@ -40,13 +40,8 @@ if(USE_GCOV)
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")
@@ -102,7 +97,6 @@ set(NEURON_BASE_SOURCES
src/utils/log.c
src/utils/cid.c
src/utils/tpy.c
src/utils/neu_path.c
${PERSIST_SOURCES})
if (SMART_LINK)
@@ -172,9 +166,12 @@ set(NEURON_SOURCES
plugins/restful/otel_handle.c
plugins/restful/cid_handle.c
plugins/restful/tpy_handle.c
plugins/restful/system_handle.c
plugins/restful/datalayers_handle.c
plugins/restful/rest.c
plugins/restful/user.c)
plugins/restful/user.c
plugins/restful/simulator_handle.c
simulator/modbus_tcp_simulator.c)
set(CMAKE_BUILD_RPATH ./)
add_executable(neuron)
@@ -199,6 +196,7 @@ add_subdirectory(plugins/ekuiper)
add_subdirectory(plugins/file)
add_subdirectory(plugins/monitor)
add_subdirectory(plugins/datalayers)
add_subdirectory(plugins/kafka)
add_subdirectory(simulator)
+28 -1
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 -b neuron https://github.com/neugates/NanoSDK.git
$ git clone 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
```
@@ -74,6 +74,33 @@ $ ./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
-104
View File
@@ -1,104 +0,0 @@
# 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/emqx/neuron/blob/main/README.md) | 简体中文
Neuron 是一款开源的、轻量级工业协议网关软件,支持数十种工业协议的一站式设备连接、数据接入、MQTT 协议转换,为工业设备赋予工业 4.0 时代关键的物联网连接能力。
![neuron-overview](docs/pictures/neuron-final.png)
以下是 Neuron 的一些重要特性:
- 具有实时能力的边缘原生应用程序可以利用边缘端的低延迟网络。
- 松耦合模块化架构设计,通过可插拔模块扩展更多功能服务。
- 支持可以在运行时更新设备和应用程序模块的热插件。
- 支持多种工业设备协议,包括 Modbus、OPCUA、Ethernet/IP、IEC104、BACnet 等。
- 支持同时连接大量不同协议的工业设备。
- 结合[eKuiper](https://www.lfedge.org/projects/ekuiper)提供的规则引擎功能,快速实现基于规则的设备控制或 AI/ML 分析。
- 通过 SparkplugB 解决方案支持对工业应用程序的数据访问,例如 MES 或 ERP、SCADA、historian 和数据分析软件。
- 具有非常低的内存占用,小于 10M 的内存占用和 CPU 使用率,可以在 ARM、x86 和 RISC-V 等资源有限的硬件上运行。
- 支持在本地安装可执行文件或部署在容器化环境中。
- 控制工业设备,通过 [HTTP API](docs/api/cn/http.md) 和 [MQTT API](docs/api/cn/mqtt.md) 服务更改参数和数据标签。
- 与其他 EMQ 产品高度集成,包括 [EMQX](https://www.emqx.com/zh/products/emqx)、[NanoMQ](https://nanomq.io/zh)、[eKuiper](https://ekuiper.org/zh)。
- 核心框架和 Modbus、MQTT 和 eKuiper 的代码在 LGPLv3 的许可下开源。
开源版本只提供了部分插件,要使用更多的插件以及更完善的 Dashboard,请使用 [NeuronEX](https://www.emqx.com/zh/products/neuronex)。
## 快速开始
Neuron 管理面板的默认登陆账号为 `admin`,密码为 `0000`。
### 下载 tar 包运行
选择 [Relase](https://github.com/emqx/neuron/releases) 版本下载
```bash
$ tar xvf neuron-{version}-linux-amd64.tar.gz
$ cd neuron
$ ./neuron --log
```
浏览器中打开 `http://localhost:7000` 访问 Neuron。
### 源码编译运行
1. [安装依赖](https://github.com/emqx/neuron/install-dependencies.md)
2. 编译 Neuron
```bash
$ git clone https://github.com/emqx/neuron
$ cd neuron && mkdir build && cd build
$ cmake .. && make
```
3. 下载并解压 Dashboard
```bash
$ wget https://github.com/emqx/neuron-dashboard/releases/download/2.6.3/neuron-dashboard.zip
# 解压 neuron-dashboard.zip 到 build 目录
$ unzip neuron-dashboard.zip
```
3. 运行 Neuron
```bash
$ ./neuron --log
```
4. 浏览器中打开 `http://localhost:7000` 访问 Neuron。
### Docker
```bash
$ docker run -d --name neuron -p 7000:7000 emqx/neuron:2.6.9
```
目前 Neuron 维护的最新镜像是 emqx/neuron:2.6.9,2.7.x 及后续的版本不再提供镜像。
### [采集 Modbus TCP 数据并通过 MQTT 发送](./docs/quick_start/quick_start_cn.md)
## Dashboard
开源版 [Dashboard](https://github.com/emqx/neuron-dashboard) 最新 Release 的版本为 2.6.3,目前已暂停开发与维护,此版本也是 Neuron 默认集成的版本。要用更完善与更专业的 Dashboard,请使用 [NeuronEX](https://www.emqx.com/zh/products/neuronex)。
## 社区
- 访问 [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 最新资讯。
## 开源许可
详见 [LICENSE](./LICENSE)。
+68 -37
View File
@@ -8,33 +8,54 @@
[![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/emqx/neuron/blob/main/README-CN.md)
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 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-overview](docs/pictures/neuron-final.png)
The following are some important features of Neuron:
Key features:
- Edge native application with real-time capability to leverage the low latency network of edge side.
- Loosely-coupled modularity architecture design 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.
- 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 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 [HTTP API](docs/api/english/http.md) and [MQTT API](docs/api/english/mqtt.md) 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.
- 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.
Neuron only provides limited plugins in the open source version. To use more plugins and a more complete Dashboard, please use [NeuronEX](https://www.emqx.com/en/products/neuronex).
## About NeuronEX
[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:
- **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
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 username is `admin` and password is `0000`.
Default credentials: username `admin`, password `0000`.
### Download
@@ -46,11 +67,17 @@ $ cd neuron
$ ./neuron --log
```
Open a web browser and navigate to `http://localhost:7000` to access the Neuron web interface.
Open a browser and navigate to `http://localhost:7000` to access the Neuron web interface.
### Build and Run
## Installation
1. [Install Dependencies](https://github.com/emqx/neuron/install-dependencies.md)
### Binaries
Download the latest release from [Releases](https://github.com/emqx/neuron/releases), then extract and run as shown above.
### Build from Source
1. [Install Dependencies](./Install-dependencies.md)
2. Build Neuron
```bash
@@ -72,37 +99,41 @@ $ unzip neuron-dashboard.zip
$ ./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.
### Docker
## Configuration
```bash
$ docker run -d --name neuron -p 7000:7000 emqx/neuron:2.6.9
```
- Web UI: `http://localhost:7000` (default credentials above)
- Main config: `neuron.conf`
- Default plugins: `default_plugins.json`
- Logging: `zlog.conf` / `sdk-zlog.conf`
Currently, the latest image maintained for Neuron is `emqx/neuron:2.6.9`, and images for versions `2.7.x` and later are no longer provided.
See the quick start for a hands-on walkthrough.
### [Modbus TCP Data Collection and MQTT Transmission](./docs/quick_start/quick_start.md)
## 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)
## 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).
## Get Involved
## Community
- 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
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.
## Security
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.
## License
+7 -2
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/v2.13/libs)
set(LIBRARY_DIR /home/neuron/v2.15/libs)
set(CMAKE_C_COMPILER ${COMPILER_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${COMPILER_PREFIX}-g++)
@@ -21,4 +21,9 @@ 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/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)
+6 -1
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/v2.13/libs)
set(LIBRARY_DIR /home/neuron/v2.15/libs)
set(CMAKE_C_COMPILER ${COMPILER_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${COMPILER_PREFIX}-g++)
@@ -22,3 +22,8 @@ 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)
+7 -2
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/v2.13/libs)
set(LIBRARY_DIR /home/neuron/v2.15/libs)
set(CMAKE_C_COMPILER ${COMPILER_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${COMPILER_PREFIX}-g++)
@@ -21,4 +21,9 @@ 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/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)
+6 -1
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/v2.13/libs)
set(LIBRARY_DIR /home/neuron/v2.15/libs)
set(CMAKE_C_COMPILER ${COMPILER_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${COMPILER_PREFIX}-g++)
@@ -27,3 +27,8 @@ 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 -1
View File
@@ -6,6 +6,7 @@
"libplugin-ekuiper.so",
"libplugin-modbus-tcp.so",
"libplugin-modbus-rtu.so",
"libplugin-file.so"
"libplugin-file.so",
"libplugin-kafka.so"
]
}
-89
View File
@@ -1,89 +0,0 @@
# 快速开始
本文档需要先根据 [安装运行](https://github.com/emqx/neuron/README-CN.md) 成功运行 Neuron,并且能通过浏览器打开 Neuron 的管理面板。
## 启动 Modbus TCP 模拟器
提供两种 Modbus TCP 模拟器,根据情况运行一种模拟器即可。
### Neuron 自带模拟器
Neuron 源码或 Release 中自带了在 Linux 上运行的测试模拟器,可以读写一些固定点位。Slave ID 为 1。
```bash
# 通过源码编译的方式,build/simulator 目录下
# Release 包中根目录下
# 运行 Modbus 模拟器,使用 TCP 模式,绑定 0.0.0.0:1502
$ ./modbus_simulator tcp 1502 ip_v4
```
### PeakHMI Slave Simulators 模拟器
PeakHMI 模拟器需要在 Windows 系统中运行,[PeakHMI 官网](https://hmisys.com)
下载并安装后,运行 Modbus TCP slave EX 模拟器即可,该模拟器默认绑定的端口为 502,会被 Windows 默认的防火墙拦截,确保防火墙能开放此接口或关闭防火墙。
![modbus-simulator](./assets/modbus-simulator.png)
## Neuron 中创建南向设备
### 创建设备
**南向设备管理** -> **添加设备** 在弹出的对话框中填写设备信息后创建设备,**插件** 选择 `Modbus TCP`,**名称** 填写 `modbus-tcp-1` 即可。
### 配置设备
创建设备后,进入设备详情页面配置设备参数。
**IP 地址**: 填写 Modbus TCP 模拟器运行机器的 IP 地址。如果 Neuron 与模拟器运行在同一台机器上,可以填写 `127.0.0.1`。
**端口**: 默认502,根据模拟器的监听端口进行修改。
其他参数默认即可。
### 配置数据点
在 **南向设备管理** 页面,点击 **设备名称** 进入点位组页面,添加一个点位组,名称填写 `group1` 即可。
进入点位组后,点击 **组名称** 进入点位配置页面,创建点位。
![add-tag](./assets/tag-list-null.png)
**名称**: `tag-1`
**地址**: `1!40001` ( Slave ID 1, 保持寄存器40001 )
**属性**: `Read` 与 `Write`
**数据类型**: `INT16`
### 查看点位值
配置完点位后,进入 **数据监控** 页面,选择刚才创建的设备以及点位组,即可看到点位的值。
![data-monitor](./assets/data-monitoring.png)
## 创建北向应用
使用 MQTT,把刚才 modbus 设备中采集到的数据发送到 MQTT Broker。
**北向应用管理** -> **添加应用**,在弹出的对话框中填写应用信息创建应用。
**插件** 选择 `MQTT`,**名称** 填写 `mqtt` 即可。
### 配置北向应用
创建北向应用后,进入北向配置页面,默认服务器地址为 `broker.emqx.io`,端口为 `1883`,如果需要连接其他 MQTT Broker,请根据实际情况修改。
### 订阅南向数据组
在**北向应用管理**页面中,点击北向应用,进入**订阅管理**页面,点击**添加订阅**,在弹出的对话框中选择刚才创建的 Modbus 设备以及点位组。
![subscribe](./assets/subscription-add.png)
记住订阅时的 topic,后续订阅该 topic 即可看到数据。topic 默认格式为`/neuron/{应用名称}`
至此,Neuron 的 Modbus TCP 采集数据并通过 MQTT 发送数据的配置完成。
## 使用 MQTTX 订阅 topic 查看数据
订阅完成后,打开 [MQTTX](https://mqttx.app/zh),创建一个新的连接,连接到 `broker.emqx.io:1883`(根据北向应用配置的 MQTT broker 修改)。
连接成功后,订阅 topic `/neuron/mqtt` (根据北向订阅时的 topic 修改),即可看到点位数据。
![mqttx](./assets/mqttx.png)
-34
View File
@@ -1,34 +0,0 @@
#!/bin/bash
set -euo pipefail
output_dir="${1:-config}"
private_key_file="${output_dir}/neuron.key"
public_key_file="${output_dir}/neuron.key.pub"
if ! command -v openssl >/dev/null 2>&1; then
echo "openssl is required but was not found in PATH."
exit 1
fi
mkdir -p "${output_dir}"
if [[ -f "${private_key_file}" || -f "${public_key_file}" ]]; then
read -r -p "Existing key files found in ${output_dir}. Overwrite them? [y/N] " answer
case "${answer}" in
[yY]|[yY][eE][sS]) ;;
*)
echo "Aborted."
exit 0
;;
esac
fi
umask 077
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out "${private_key_file}"
openssl pkey -in "${private_key_file}" -pubout -out "${public_key_file}"
echo "Generated RSA key pair:"
echo " ${private_key_file}"
echo " ${public_key_file}"
echo "Run neuron with --config_dir ${output_dir} to use the generated keys."
@@ -305,5 +305,6 @@ int is_ipv6(const char *ip);
bool neu_conn_is_connected(neu_conn_t *conn);
uint16_t neu_conn_udp_act_port(neu_conn_t *conn);
bool conn_tcp_server_is_listen(neu_conn_t *conn);
#endif
+7 -5
View File
@@ -23,8 +23,8 @@
#include <stdbool.h>
#define NEU_VERSION_MAJOR 2
#define NEU_VERSION_MINOR 13
#define NEU_VERSION_FIX 2
#define NEU_VERSION_MINOR 15
#define NEU_VERSION_FIX 0
#define NEU_GET_VERSION_MAJOR(V) ((0xffff0000 & V) >> 16)
#define NEU_GET_VERSION_MINOR(V) ((0x0000ff00 & V) >> 8)
@@ -33,13 +33,14 @@
#define NEU_TAG_NAME_LEN 128
#define NEU_TAG_ADDRESS_LEN 128
#define NEU_TAG_DESCRIPTION_LEN 128
#define NEU_TAG_DESCRIPTION_LEN 256
#define NEU_GROUP_MAX_PER_NODE 512
#define NEU_GROUP_NAME_LEN 128
#define NEU_GROUP_INTERVAL_LIMIT 30
#define NEU_GROUP_INTERVAL_LIMIT 100
#define NEU_DEFAULT_GROUP_INTERVAL 100
#define NEU_NODE_NAME_LEN 128
#define NEU_PLUGIN_NAME_LEN 32
#define NEU_NODE_TAGS_LEN 256
#define NEU_PLUGIN_LIBRARY_LEN 64
#define NEU_PLUGIN_DESCRIPTION_LEN 512
#define NEU_TEMPLATE_NAME_LEN 128
@@ -58,6 +59,7 @@
#define NEU_TAG_META_LENGTH 20
#define NEU_TAG_META_SIZE 32
#define NEU_TAG_FORMAT_LENGTH 16
#define NEU_TAG_UNIT_LENGTH 16
#define NEU_LOG_LEVEL_DEBUG "debug"
#define NEU_LOG_LEVEL_INFO "info"
@@ -93,7 +95,7 @@
#define NEU_CID_LEN4 4
#define NEU_CID_LEN8 8
#define NEU_CID_LEN16 16
#define NEU_CID_LEN32 72
#define NEU_CID_LEN32 32
#define NEU_CID_LEN64 64
#define NEU_TRACE_ID_LEN 64
+4
View File
@@ -53,6 +53,7 @@ typedef enum {
NEU_ERR_USER_NOT_EXISTS = 1020,
NEU_ERR_INVALID_USER_LEN = 1021,
NEU_ERR_USER_NO_PERMISSION = 1022,
NEU_ERR_PORT_IN_USE = 1023,
NEU_ERR_NODE_EXIST = 2002,
NEU_ERR_NODE_NOT_EXIST = 2003,
@@ -68,6 +69,9 @@ typedef enum {
NEU_ERR_NODE_NOT_ALLOW_UPDATE = 2013,
NEU_ERR_NODE_NOT_ALLOW_MAP = 2014,
NEU_ERR_NODE_NAME_EMPTY = 2015,
NEU_ERR_NODE_TAGS_TOO_LONG = 2016,
NEU_ERR_NODE_TAGS_TOO_MANY = 2017,
NEU_ERR_NODE_TAGS_INVALID = 2018,
NEU_ERR_GROUP_ALREADY_SUBSCRIBED = 2101,
NEU_ERR_GROUP_NOT_SUBSCRIBE = 2102,
+15 -15
View File
@@ -61,58 +61,58 @@ typedef enum neu_json_type {
} neu_json_type_e;
typedef struct {
int8_t * i8s;
uint16_t length;
int8_t *i8s;
uint8_t length;
} neu_json_value_array_int8_t;
typedef struct {
uint8_t *u8s;
uint16_t length;
uint8_t length;
} neu_json_value_array_uint8_t;
typedef struct {
int16_t *i16s;
uint16_t length;
uint8_t length;
} neu_json_value_array_int16_t;
typedef struct {
uint16_t *u16s;
uint16_t length;
uint8_t length;
} neu_json_value_array_uint16_t;
typedef struct {
int32_t *i32s;
uint16_t length;
uint8_t length;
} neu_json_value_array_int32_t;
typedef struct {
uint32_t *u32s;
uint16_t length;
uint8_t length;
} neu_json_value_array_uint32_t;
typedef struct {
int64_t *i64s;
uint16_t length;
uint8_t length;
} neu_json_value_array_int64_t;
typedef struct {
uint64_t *u64s;
uint16_t length;
uint8_t length;
} neu_json_value_array_uint64_t;
typedef struct {
float * f32s;
uint16_t length;
float * f32s;
uint8_t length;
} neu_json_value_array_float_t;
typedef struct {
double * f64s;
uint16_t length;
double *f64s;
uint8_t length;
} neu_json_value_array_double_t;
typedef struct {
bool * bools;
uint16_t length;
bool * bools;
uint8_t length;
} neu_json_value_array_bool_t;
typedef struct {
+1
View File
@@ -37,6 +37,7 @@ typedef struct {
char *node;
char *id;
char *ctx;
int load_index;
} neu_json_scan_tags_req_t;
int neu_json_decode_scan_tags_req(char *buf, neu_json_scan_tags_req_t **result);
+58
View File
@@ -0,0 +1,58 @@
/**
* NEURON IIoT System for Industry 4.0
* Copyright (C) 2020-2025 EMQ Technologies Co., Ltd All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#ifndef NEU_MODBUS_TCP_SIMULATOR_H
#define NEU_MODBUS_TCP_SIMULATOR_H
#include <stdbool.h>
#include <stdint.h>
int neu_modbus_simulator_init(const char *config_dir);
void neu_modbus_simulator_fini(void);
int neu_modbus_simulator_start(const char *ip, uint16_t port);
void neu_modbus_simulator_stop(void);
void neu_modbus_simulator_apply_persist(void);
typedef enum {
NEU_SIM_TAG_NONE = 0,
NEU_SIM_TAG_SINE = 1, // float, [-100,100], period 60s
NEU_SIM_TAG_SAW = 2, // int16, [0,100], period 100s
NEU_SIM_TAG_SQUARE = 3, // int16, [-10,10], period 10s
NEU_SIM_TAG_RANDOM = 4 // int16, [0,100]
} neu_sim_tag_type_e;
int neu_modbus_simulator_config_tags(const char **names, const char **addr_strs,
const uint16_t * addresses,
const neu_sim_tag_type_e *types,
int tag_count);
char *neu_modbus_simulator_export_drivers_json(void);
char *neu_modbus_simulator_list_tags_json(void);
typedef struct {
bool running;
char ip[64];
uint16_t port;
int tag_count;
int error;
} neu_modbus_simulator_status_t;
void neu_modbus_simulator_get_status(neu_modbus_simulator_status_t *out);
#endif
+40
View File
@@ -78,6 +78,7 @@ typedef enum neu_reqresp_type {
NEU_RESP_NODE_UNINIT,
NEU_REQ_ADD_NODE,
NEU_REQ_UPDATE_NODE,
NEU_REQ_UPDATE_NODE_TAG,
NEU_REQ_DEL_NODE,
NEU_REQ_GET_NODE,
NEU_RESP_GET_NODE,
@@ -113,6 +114,8 @@ typedef enum neu_reqresp_type {
NEU_RESP_UPDATE_TAG,
NEU_REQ_GET_TAG,
NEU_RESP_GET_TAG,
NEU_REQ_RENAME_TAG,
NEU_RESP_RENAME_TAG,
NEU_REQ_ADD_PLUGIN,
NEU_REQ_DEL_PLUGIN,
@@ -168,6 +171,7 @@ typedef enum neu_reqresp_type {
NEU_REQ_ADD_TAG_EVENT,
NEU_REQ_DEL_TAG_EVENT,
NEU_REQ_UPDATE_TAG_EVENT,
NEU_REQ_RENAME_TAG_EVENT,
NEU_REQ_IMPORT_TAGS_EVENT,
NEU_REQ_ADD_GTAG_EVENT,
@@ -242,6 +246,7 @@ static const char *neu_reqresp_type_string_t[] = {
[NEU_RESP_NODE_UNINIT] = "NEU_RESP_NODE_UNINIT",
[NEU_REQ_ADD_NODE] = "NEU_REQ_ADD_NODE",
[NEU_REQ_UPDATE_NODE] = "NEU_REQ_UPDATE_NODE",
[NEU_REQ_UPDATE_NODE_TAG] = "NEU_REQ_UPDATE_NODE_TAG",
[NEU_REQ_DEL_NODE] = "NEU_REQ_DEL_NODE",
[NEU_REQ_GET_NODE] = "NEU_REQ_GET_NODE",
[NEU_RESP_GET_NODE] = "NEU_RESP_GET_NODE",
@@ -277,6 +282,8 @@ static const char *neu_reqresp_type_string_t[] = {
[NEU_RESP_UPDATE_TAG] = "NEU_RESP_UPDATE_TAG",
[NEU_REQ_GET_TAG] = "NEU_REQ_GET_TAG",
[NEU_RESP_GET_TAG] = "NEU_RESP_GET_TAG",
[NEU_REQ_RENAME_TAG] = "NEU_REQ_RENAME_TAG",
[NEU_RESP_RENAME_TAG] = "NEU_RESP_RENAME_TAG",
[NEU_REQ_ADD_PLUGIN] = "NEU_REQ_ADD_PLUGIN",
[NEU_REQ_DEL_PLUGIN] = "NEU_REQ_DEL_PLUGIN",
@@ -331,6 +338,7 @@ static const char *neu_reqresp_type_string_t[] = {
[NEU_REQ_ADD_TAG_EVENT] = "NEU_REQ_ADD_TAG_EVENT",
[NEU_REQ_DEL_TAG_EVENT] = "NEU_REQ_DEL_TAG_EVENT",
[NEU_REQ_UPDATE_TAG_EVENT] = "NEU_REQ_UPDATE_TAG_EVENT",
[NEU_REQ_RENAME_TAG_EVENT] = "NEU_REQ_RENAME_TAG_EVENT",
[NEU_REQ_IMPORT_TAGS_EVENT] = "NEU_REQ_IMPORT_TAGS_EVENT",
[NEU_REQ_ADD_GTAG_EVENT] = "NEU_REQ_ADD_GTAG_EVENT",
[NEU_REQ_ADD_PLUGIN_EVENT] = "NEU_REQ_ADD_PLUGIN_EVENT",
@@ -522,11 +530,14 @@ typedef struct neu_req_add_node {
char node[NEU_NODE_NAME_LEN];
char plugin[NEU_PLUGIN_NAME_LEN];
char *setting;
char *tags;
} neu_req_add_node_t;
static inline void neu_req_add_node_fini(neu_req_add_node_t *req)
{
free(req->setting);
if (req->tags)
free(req->tags);
}
typedef struct neu_req_update_node {
@@ -534,6 +545,11 @@ typedef struct neu_req_update_node {
char new_name[NEU_NODE_NAME_LEN];
} neu_req_update_node_t;
typedef struct neu_req_update_node_tag {
char node[NEU_NODE_NAME_LEN];
char tags[NEU_NODE_TAGS_LEN];
} neu_req_update_node_tag_t;
typedef struct neu_req_del_node {
char node[NEU_NODE_NAME_LEN];
} neu_req_del_node_t;
@@ -557,6 +573,7 @@ typedef struct neu_resp_node_info {
int64_t delay;
char node[NEU_NODE_NAME_LEN];
char plugin[NEU_PLUGIN_NAME_LEN];
char tags[NEU_NODE_TAGS_LEN];
} neu_resp_node_info_t;
typedef struct neu_resp_get_node {
@@ -720,6 +737,17 @@ typedef struct neu_resp_get_tag {
UT_array *tags; // array neu_datatag_t
} neu_resp_get_tag_t;
typedef struct {
char driver[NEU_NODE_NAME_LEN];
char group[NEU_GROUP_NAME_LEN];
char old_name[NEU_TAG_NAME_LEN];
char new_name[NEU_TAG_NAME_LEN];
} neu_req_rename_tag_t;
typedef struct {
int error;
} neu_resp_rename_tag_t;
typedef struct {
char app[NEU_NODE_NAME_LEN];
char driver[NEU_NODE_NAME_LEN];
@@ -1149,6 +1177,7 @@ typedef struct {
char * node;
char * plugin;
char * setting;
char * tags;
uint16_t n_group;
neu_gdatatag_t *groups;
} neu_req_driver_t;
@@ -1158,6 +1187,9 @@ static inline void neu_req_driver_fini(neu_req_driver_t *req)
free(req->node);
free(req->plugin);
free(req->setting);
if (req->tags)
free(req->tags);
for (uint16_t i = 0; i < req->n_group; i++) {
for (int j = 0; j < req->groups[i].n_tag; j++) {
neu_tag_fini(&req->groups[i].tags[j]);
@@ -1439,6 +1471,11 @@ neu_tag_value_to_json_paginate(neu_resp_tag_value_meta_paginate_t *tag_value,
tag_json->datatag.bias = tag_value->datatag.bias;
tag_json->datatag.description = strdup(tag_value->datatag.description);
tag_json->datatag.option = tag_value->datatag.option;
if (tag_value->datatag.unit) {
tag_json->datatag.unit = strdup(tag_value->datatag.unit);
} else {
tag_json->datatag.unit = strdup("");
}
memcpy(tag_json->datatag.meta, tag_value->datatag.meta,
NEU_TAG_META_LENGTH);
@@ -1692,6 +1729,7 @@ typedef struct neu_req_scan_tags {
char driver[NEU_NODE_NAME_LEN];
char id[NEU_TAG_ADDRESS_LEN];
char ctx[NEU_VALUE_SIZE];
int load_index;
} neu_req_scan_tags_t;
typedef struct {
@@ -1708,6 +1746,8 @@ typedef struct {
neu_type_e type;
bool is_array;
char ctx[NEU_VALUE_SIZE];
int load_index;
int c_flag;
} neu_resp_scan_tags_t;
typedef struct {
-1
View File
@@ -38,7 +38,6 @@ extern "C" {
#include "utils/zlog.h"
#include "utils/neu_jwt.h"
#include "utils/neu_path.h"
#include "utils/time.h"
#include "utils/utarray.h"
#include "utils/utextend.h"
+16
View File
@@ -36,6 +36,7 @@ typedef struct {
int type;
char *plugin_name;
int state;
char *tags;
} neu_persist_node_info_t;
typedef struct {
@@ -125,6 +126,9 @@ static inline void neu_persist_node_info_fini(neu_persist_node_info_t *info)
{
free(info->name);
free(info->plugin_name);
if (info->tags) {
free(info->tags);
}
}
static inline void neu_persist_group_info_fini(neu_persist_group_info_t *info)
@@ -250,6 +254,7 @@ int neu_persister_delete_node(const char *node_name);
* @return 0 on success, none-zero on failure
*/
int neu_persister_update_node(const char *node_name, const char *new_name);
int neu_persister_update_node_tags(const char *node_name, const char *tags);
/**
* Update node state.
@@ -328,6 +333,17 @@ int neu_persister_update_tag_value(const char * driver_name,
int neu_persister_delete_tag(const char *driver_name, const char *group_name,
const char *tag_name);
/**
* Rename node tag.
* @param driver_name name of the driver who owns the tags
* @param group_name name of the group
* @param old_name old name of the tag
* @param new_name new name of the tag
* @return 0 on success, non-zero otherwise
*/
int neu_persister_rename_tag(const char *driver_name, const char *group_name,
const char *old_name, const char *new_name);
/**
* Persist subscriptions.
* @param app_name name of the app node
+2 -1
View File
@@ -89,6 +89,7 @@ typedef struct neu_plugin_intf_funs {
int (*start)(neu_plugin_t *plugin);
int (*stop)(neu_plugin_t *plugin);
int (*setting)(neu_plugin_t *plugin, const char *setting);
int (*try_connect)(neu_plugin_t *plugin);
int (*request)(neu_plugin_t *plugin, neu_reqresp_head_t *head, void *data);
@@ -112,7 +113,7 @@ typedef struct neu_plugin_intf_funs {
int n_tag); // create tags by API
int (*del_tags)(neu_plugin_t *plugin, int n_tag);
int (*scan_tags)(neu_plugin_t *plugin, void *req, char *id,
char *ctx);
char *ctx, int load_index);
int (*test_read_tag)(neu_plugin_t *plugin, void *req,
neu_datatag_t tag);
int (*action)(neu_plugin_t *plugin, const char *action);
+3
View File
@@ -47,6 +47,8 @@ typedef enum {
NEU_DATATAG_ENDIAN_LB64 = 9,
NEU_DATATAG_ENDIAN_BB64 = 10,
NEU_DATATAG_ENDIAN_BL64 = 11,
NEU_DATATAG_ENDIAN_WS64 = 12, // #WS 3,4,1,2,7,8,5,6
NEU_DATATAG_ENDIAN_WR64 = 13, // #WR 5,6,7,8,1,2,3,4
} neu_datatag_endian_e;
typedef enum {
@@ -93,6 +95,7 @@ typedef struct {
double decimal;
double bias;
char * description;
char * unit;
neu_datatag_addr_option_u option;
uint8_t meta[NEU_TAG_META_LENGTH];
uint8_t format[NEU_TAG_FORMAT_LENGTH];
+36
View File
@@ -1187,6 +1187,42 @@ static inline uint64_t neu_htonbl(uint64_t u)
return out;
}
static inline uint64_t neu_htonws(uint64_t u)
{
uint8_t *in = (uint8_t *) &u;
uint64_t out = 0;
uint8_t *out_bytes = (uint8_t *) &out;
out_bytes[0] = in[2];
out_bytes[1] = in[3];
out_bytes[2] = in[0];
out_bytes[3] = in[1];
out_bytes[4] = in[6];
out_bytes[5] = in[7];
out_bytes[6] = in[4];
out_bytes[7] = in[5];
return out;
}
static inline uint64_t neu_htonwr(uint64_t u)
{
uint8_t *in = (uint8_t *) &u;
uint64_t out = 0;
uint8_t *out_bytes = (uint8_t *) &out;
out_bytes[0] = in[4];
out_bytes[1] = in[5];
out_bytes[2] = in[6];
out_bytes[3] = in[7];
out_bytes[4] = in[0];
out_bytes[5] = in[1];
out_bytes[6] = in[2];
out_bytes[7] = in[3];
return out;
}
static inline uint64_t neu_ntohll(uint64_t u)
{
return neu_htonll(u);
-50
View File
@@ -1,50 +0,0 @@
/**
* NEURON IIoT System for Industry 4.0
* Copyright (C) 2020-2024 EMQ Technologies Co., Ltd All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#ifndef NEURON_UTILS_PATH_H
#define NEURON_UTILS_PATH_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
// Reject an untrusted path component (e.g. a node/plugin/library name that is
// used to build a filesystem path). Returns true only when `name` is a single,
// safe path segment: non-empty, contains no '/' and is not "." or "..".
bool neu_path_is_valid_component(const char *name);
// Confine an untrusted `path` to the trusted base directory `base`.
//
// The result is the canonical absolute path when it resolves inside `base`
// (either a relative path joined under `base`, or an absolute path that already
// lies within `base`). Returns a newly heap-allocated string the caller must
// free(), or NULL if the path escapes `base`, is invalid, or cannot be
// resolved. A not-yet-existing final component (e.g. a file about to be
// created) is handled by resolving its parent directory.
//
// If `base` is NULL or empty, the current working directory is used.
char *neu_path_confine(const char *base, const char *path);
#ifdef __cplusplus
}
#endif
#endif
+30 -1
View File
@@ -27,7 +27,7 @@ cd ../..
# Install NanoSDK
echo "Installing NanoSDK..."
git clone -b neuron https://github.com/neugates/NanoSDK.git
git clone 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
cd ../..
@@ -73,6 +73,35 @@ cd protobuf-c
make && sudo make install
cd ..
# Install cyrus-sasl
echo "Installing cyrus-sasl..."
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
cd ..
# Install zstd
echo "Installing zstd..."
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
cd ../../../..
# Install librdkafka
echo "Installing librdkafka..."
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
cd ../..
# Install libxml2
echo "Installing libxml2..."
git clone -b v2.9.14 https://github.com/GNOME/libxml2.git
+2 -14
View File
@@ -1,20 +1,8 @@
import jwt # pip install pyjwt
import time
from pathlib import Path
key_candidates = [
Path("config") / "neuron.key",
Path("neuron.key"),
]
private_key = None
for key_path in key_candidates:
if key_path.is_file():
private_key = key_path.read_text()
break
if private_key is None:
raise FileNotFoundError("Cannot find neuron.key in ./config or the current directory")
with open("neuron.key", "r") as f:
private_key = f.read()
current_time = int(time.time())
two_years = 60 * 60 * 24 * 365 * 2
+4 -1
View File
@@ -4,5 +4,8 @@
"disable_auth": 0,
"syslog_host": "",
"syslog_port": 541,
"sub_filter_error": 0
"sub_filter_error": 0,
"modbus_simulator": {
"port": 502
}
}
+6
View File
@@ -59,6 +59,7 @@ cp build/plugins/schema/ekuiper.json \
build/plugins/schema/mqtt.json \
build/plugins/schema/modbus-tcp.json \
build/plugins/schema/file.json \
build/plugins/schema/kafka.json \
${package_name}/plugins/schema/
cp build/plugins/libplugin-ekuiper.so \
@@ -66,8 +67,13 @@ cp build/plugins/libplugin-ekuiper.so \
build/plugins/libplugin-mqtt.so \
build/plugins/libplugin-modbus-tcp.so \
build/plugins/libplugin-file.so \
build/plugins/libplugin-kafka.so \
${package_name}/plugins/
cp /usr/local/lib/librdkafka.so.1 ${package_name}/lib
cp /usr/local/lib/libzstd.so.1 ${package_name}/lib
cp /usr/local/lib/libsasl2.so.3 ${package_name}/lib
tar czf ${package_name}-${arch}.tar.gz ${package_name}/
ls ${package_name}
rm -rf ${package_name}
@@ -0,0 +1,12 @@
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS modbus_tcp_simulator (
id INTEGER PRIMARY KEY CHECK (id = 1),
enabled INTEGER NOT NULL DEFAULT 0,
tags_json TEXT NOT NULL DEFAULT '[]',
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
alter TABLE nodes add column tags TEXT NULL;
COMMIT;
+5
View File
@@ -0,0 +1,5 @@
BEGIN TRANSACTION;
alter TABLE tags add column unit TEXT NULL check(length(unit) <= 128);
COMMIT;
Binary file not shown.
+2
View File
@@ -74,6 +74,8 @@ struct neu_plugin {
pthread_rwlock_t plugin_mutex;
bool consumer_thread_stop_flag;
uint32_t config_seq;
int (*parse_config)(neu_plugin_t *plugin, const char *setting,
datalayers_config_t *config);
};
+96 -47
View File
@@ -17,6 +17,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#include <pthread.h>
#include "define.h"
#include "errcodes.h"
#include "flight_sql_client.h"
@@ -49,6 +51,7 @@ neu_plugin_t *datalayers_plugin_open(void)
neu_plugin_t *plugin = (neu_plugin_t *) calloc(1, sizeof(neu_plugin_t));
neu_plugin_common_init(&plugin->common);
plugin->parse_config = datalayers_config_parse;
plugin->config_seq = 0;
return plugin;
}
@@ -106,6 +109,7 @@ int datalayers_plugin_init(neu_plugin_t *plugin, bool load)
int datalayers_plugin_uninit(neu_plugin_t *plugin)
{
pthread_rwlock_wrlock(&plugin->plugin_mutex);
plugin->config_seq++;
stop_consumer_thread(&plugin->consumer_thread, plugin);
pthread_mutex_lock(&plugin->queue_mutex);
@@ -129,28 +133,71 @@ int datalayers_plugin_uninit(neu_plugin_t *plugin)
return NEU_ERR_SUCCESS;
}
static int config_datalayers_client(neu_plugin_t * plugin,
const datalayers_config_t *config)
static void *datalayers_connect_routine(void *arg)
{
if (!config->host || !config->port || !config->username ||
!config->password) {
plog_error(
plugin,
"Invalid datalayers config: NULL host/port/username/password");
return NEU_ERR_DATALAYERS_INIT_FAILURE;
neu_plugin_t *plugin = (neu_plugin_t *) arg;
char * host = NULL;
char * user = NULL;
char * pass = NULL;
int port = 0;
uint32_t seq = 0;
pthread_rwlock_wrlock(&plugin->plugin_mutex);
seq = plugin->config_seq;
if (!plugin->config.host || !plugin->config.username ||
!plugin->config.password) {
pthread_rwlock_unlock(&plugin->plugin_mutex);
return NULL;
}
host = strdup(plugin->config.host);
user = strdup(plugin->config.username);
pass = strdup(plugin->config.password);
port = (int) plugin->config.port;
pthread_rwlock_unlock(&plugin->plugin_mutex);
if (!host || !user || !pass) {
free(host);
free(user);
free(pass);
return NULL;
}
plugin->client = client_create(config->host, config->port, config->username,
config->password);
neu_datalayers_client *c = client_create(host, port, user, pass);
free(host);
free(user);
free(pass);
if (NULL == plugin->client) {
plog_error(plugin, "datalayers client_create failed");
plugin->common.link_state = NEU_NODE_LINK_STATE_DISCONNECTED;
} else {
pthread_rwlock_wrlock(&plugin->plugin_mutex);
if (seq != plugin->config_seq) {
if (c) {
client_destroy(c);
}
pthread_rwlock_unlock(&plugin->plugin_mutex);
return NULL;
}
if (c) {
if (plugin->client) {
client_destroy(plugin->client);
}
plugin->client = c;
plugin->common.link_state = NEU_NODE_LINK_STATE_CONNECTED;
} else {
plog_error(plugin, "datalayers async connect failed");
plugin->common.link_state = NEU_NODE_LINK_STATE_DISCONNECTED;
}
pthread_rwlock_unlock(&plugin->plugin_mutex);
return NULL;
}
return NEU_ERR_SUCCESS;
static void datalayers_spawn_async_connect(neu_plugin_t *plugin)
{
pthread_t tid;
int st = pthread_create(&tid, NULL, datalayers_connect_routine, plugin);
if (st != 0) {
plog_error(plugin, "datalayers pthread_create connect failed: %d", st);
return;
}
pthread_detach(tid);
}
int datalayers_plugin_config(neu_plugin_t *plugin, const char *setting)
@@ -158,6 +205,15 @@ int datalayers_plugin_config(neu_plugin_t *plugin, const char *setting)
int rv = 0;
const char * plugin_name = neu_plugin_module.module_name;
datalayers_config_t config = { 0 };
rv = plugin->parse_config(plugin, setting, &config);
if (0 != rv) {
plog_error(plugin, "neu_datalayers_config_parse fail");
return NEU_ERR_NODE_SETTING_INVALID;
}
plugin->config_seq++;
if (plugin->client != NULL) {
stop_consumer_thread(&plugin->consumer_thread, plugin);
pthread_mutex_lock(&plugin->queue_mutex);
@@ -168,25 +224,18 @@ int datalayers_plugin_config(neu_plugin_t *plugin, const char *setting)
create_consumer_thread(plugin);
}
rv = plugin->parse_config(plugin, setting, &config);
if (0 != rv) {
plog_error(plugin, "neu_datalayers_config_parse fail");
return NEU_ERR_NODE_SETTING_INVALID;
}
rv = config_datalayers_client(plugin, &config);
datalayers_config_fini(&plugin->config);
memmove(&plugin->config, &config, sizeof(config));
if (rv == 0) {
plog_notice(plugin, "config plugin `%s` success", plugin_name);
} else {
plog_error(plugin, "datalayers client configuration failed");
}
plugin->common.link_state = NEU_NODE_LINK_STATE_DISCONNECTED;
return rv;
plog_notice(plugin, "config plugin `%s` success (connect in background)",
plugin_name);
datalayers_spawn_async_connect(plugin);
return NEU_ERR_SUCCESS;
}
int datalayers_plugin_start(neu_plugin_t *plugin)
@@ -194,17 +243,16 @@ int datalayers_plugin_start(neu_plugin_t *plugin)
const char *plugin_name = neu_plugin_module.module_name;
if (NULL == plugin->client) {
plog_error(plugin, "datalayers started failed, reconnect");
int ret = config_datalayers_client(plugin, &plugin->config);
if (ret != 0) {
plog_error(plugin, "datalayers started failed");
plugin->common.link_state = NEU_NODE_LINK_STATE_DISCONNECTED;
return NEU_ERR_SUCCESS;
}
plog_notice(plugin, "datalayers start: connect in background");
datalayers_spawn_async_connect(plugin);
}
plog_notice(plugin, "start plugin `%s` success", plugin_name);
plugin->common.link_state = NEU_NODE_LINK_STATE_CONNECTED;
if (plugin->client) {
plugin->common.link_state = NEU_NODE_LINK_STATE_CONNECTED;
} else {
plugin->common.link_state = NEU_NODE_LINK_STATE_DISCONNECTED;
}
return NEU_ERR_SUCCESS;
}
@@ -225,11 +273,11 @@ int datalayers_plugin_request(neu_plugin_t *plugin, neu_reqresp_head_t *head,
switch (head->type) {
case NEU_REQRESP_TRANS_DATA: {
if (plugin->client == NULL) {
plog_notice(plugin, "datalayers client is NULL, reconnect");
plog_notice(plugin, "datalayers client is NULL, reconnect async");
NEU_PLUGIN_UPDATE_METRIC(plugin, NEU_METRIC_SEND_MSG_ERRORS_TOTAL,
1, NULL);
config_datalayers_client(plugin, &plugin->config);
datalayers_spawn_async_connect(plugin);
}
error = handle_trans_data(plugin, data);
@@ -266,12 +314,13 @@ int datalayers_plugin_request(neu_plugin_t *plugin, neu_reqresp_head_t *head,
}
const neu_plugin_intf_funs_t datalayers_plugin_intf_funs = {
.open = datalayers_plugin_open,
.close = datalayers_plugin_close,
.init = datalayers_plugin_init,
.uninit = datalayers_plugin_uninit,
.start = datalayers_plugin_start,
.stop = datalayers_plugin_stop,
.setting = datalayers_plugin_config,
.request = datalayers_plugin_request,
.open = datalayers_plugin_open,
.close = datalayers_plugin_close,
.init = datalayers_plugin_init,
.uninit = datalayers_plugin_uninit,
.start = datalayers_plugin_start,
.stop = datalayers_plugin_stop,
.setting = datalayers_plugin_config,
.request = datalayers_plugin_request,
.try_connect = NULL,
};
+9 -11
View File
@@ -30,7 +30,6 @@
#include "read_write.h"
#define EKUIPER_PLUGIN_URL "tcp://127.0.0.1:7081"
#define EKUIPER_PLUGIN_RECV_MAX_SIZE (4 * 1024 * 1024)
const neu_plugin_module_t neu_plugin_module;
@@ -150,8 +149,6 @@ static inline int start(neu_plugin_t *plugin, const char *url)
nng_pipe_notify(plugin->sock, NNG_PIPE_EV_REM_POST, pipe_rm_cb, plugin);
nng_socket_set_int(plugin->sock, NNG_OPT_SENDBUF, 2048);
nng_socket_set_int(plugin->sock, NNG_OPT_RECVBUF, 2048);
nng_socket_set_size(plugin->sock, NNG_OPT_RECVMAXSZ,
EKUIPER_PLUGIN_RECV_MAX_SIZE);
if ((rv = nng_listen(plugin->sock, url, NULL, 0)) != 0) {
plog_error(plugin, "nng_listen: %s", nng_strerror(rv));
@@ -426,14 +423,15 @@ static int ekuiper_plugin_request(neu_plugin_t * plugin,
}
static const neu_plugin_intf_funs_t plugin_intf_funs = {
.open = ekuiper_plugin_open,
.close = ekuiper_plugin_close,
.init = ekuiper_plugin_init,
.uninit = ekuiper_plugin_uninit,
.start = ekuiper_plugin_start,
.stop = ekuiper_plugin_stop,
.setting = ekuiper_plugin_config,
.request = ekuiper_plugin_request,
.open = ekuiper_plugin_open,
.close = ekuiper_plugin_close,
.init = ekuiper_plugin_init,
.uninit = ekuiper_plugin_uninit,
.start = ekuiper_plugin_start,
.stop = ekuiper_plugin_stop,
.setting = ekuiper_plugin_config,
.request = ekuiper_plugin_request,
.try_connect = NULL,
};
const neu_plugin_module_t neu_plugin_module = {
+9 -10
View File
@@ -204,8 +204,7 @@ void recv_data_callback(void *arg)
body_str = nng_msg_body(msg);
body_len = nng_msg_len(msg);
if (body_len >= 26 && *(uint8_t *) body_str == 0x0A &&
*(uint8_t *) (body_str + 1) == 0xCE) {
if (*(uint8_t *) body_str == 0x0A && *(uint8_t *) (body_str + 1) == 0xCE) {
// trace
trace_id = (uint8_t *) (body_str + 2);
span_id = (uint8_t *) (body_str + 18);
@@ -240,12 +239,13 @@ void recv_data_callback(void *arg)
req->single.tag, req->single.node);
}
} else {
if (req->plural.n_tag == 0) {
plog_warn(plugin, "no tags to write on node %s", req->plural.node);
} else {
rv = send_write_tags_req(plugin, &req->plural, json_str, trace_flag,
trace_id, span_id);
if (rv == 0) {
rv = send_write_tags_req(plugin, &req->plural, json_str, trace_flag,
trace_id, span_id);
if (rv == 0) {
if (req->plural.n_tag == 0) {
plog_warn(plugin, "no tags to write on node %s",
req->plural.node);
} else {
plog_notice(plugin, "write %d tags on node %s",
req->plural.n_tag, req->plural.node);
}
@@ -273,8 +273,7 @@ static int json_value_to_tag_value(union neu_json_value *req,
break;
case NEU_JSON_STR:
value->type = NEU_TYPE_STRING;
strncpy(value->value.str, req->val_str, sizeof(value->value.str) - 1);
value->value.str[sizeof(value->value.str) - 1] = '\0';
strncpy(value->value.str, req->val_str, sizeof(value->value.str));
break;
case NEU_JSON_DOUBLE:
value->type = NEU_TYPE_DOUBLE;
+15 -20
View File
@@ -49,14 +49,15 @@ static int driver_write(neu_plugin_t *plugin, void *req, neu_datatag_t *tag,
neu_value_u value);
static const neu_plugin_intf_funs_t plugin_intf_funs = {
.open = driver_open,
.close = driver_close,
.init = driver_init,
.uninit = driver_uninit,
.start = driver_start,
.stop = driver_stop,
.setting = driver_config,
.request = driver_request,
.open = driver_open,
.close = driver_close,
.init = driver_init,
.uninit = driver_uninit,
.start = driver_start,
.stop = driver_stop,
.setting = driver_config,
.request = driver_request,
.try_connect = NULL,
.driver.validate_tag = driver_validate_tag,
.driver.group_timer = driver_group_timer,
@@ -180,9 +181,7 @@ static int driver_group_timer(neu_plugin_t *plugin, neu_plugin_group_t *group)
{
neu_dvalue_t dvalue = { 0 };
char *safe_path = neu_path_confine(NULL, tag->address);
FILE *fp = safe_path ? fopen(safe_path, "r") : NULL;
free(safe_path);
FILE *fp = fopen(tag->address, "r");
if (fp == NULL) {
dvalue.type = NEU_TYPE_ERROR;
if (errno == ENOENT) {
@@ -191,16 +190,14 @@ static int driver_group_timer(neu_plugin_t *plugin, neu_plugin_group_t *group)
dvalue.value.i32 = NEU_ERR_FILE_OPEN_FAILURE;
}
} else {
char * buf = calloc(1, 1024);
size_t ret = fread(buf, 1, 1024, fp);
if (ret == 0 && ferror(fp)) {
char *buf = calloc(1, 1024);
int ret = fread(buf, 1, 1024, fp);
if (ret < 0) {
dvalue.type = NEU_TYPE_ERROR;
dvalue.value.i32 = NEU_ERR_FILE_READ_FAILURE;
} else {
size_t n = ret < NEU_VALUE_SIZE - 1 ? ret : NEU_VALUE_SIZE - 1;
dvalue.type = NEU_TYPE_STRING;
strncpy(dvalue.value.str, buf, n);
dvalue.value.str[n] = '\0';
strncpy(dvalue.value.str, buf, NEU_VALUE_SIZE - 1);
}
fclose(fp);
@@ -222,9 +219,7 @@ static int driver_group_timer(neu_plugin_t *plugin, neu_plugin_group_t *group)
static int driver_write(neu_plugin_t *plugin, void *req, neu_datatag_t *tag,
neu_value_u value)
{
char *safe_path = neu_path_confine(NULL, tag->address);
FILE *fp = safe_path ? fopen(safe_path, "w") : NULL;
free(safe_path);
FILE *fp = fopen(tag->address, "w");
if (fp == NULL) {
if (errno == ENOENT) {
plugin->common.adapter_callbacks->driver.write_response(
+62
View File
@@ -0,0 +1,62 @@
cmake_minimum_required(VERSION 3.12)
project(plugin-kafka)
set(LIBRARY_OUTPUT_PATH "${CMAKE_BINARY_DIR}/plugins")
file(COPY ${CMAKE_SOURCE_DIR}/plugins/kafka/kafka.json
DESTINATION ${CMAKE_BINARY_DIR}/plugins/schema/)
# --- locate librdkafka -------------------------------------------------------
# Prefer pkg-config (works for system install and staging sysroot).
# Falls back to find_library for minimal environments.
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(RDKAFKA IMPORTED_TARGET rdkafka)
endif()
if(NOT RDKAFKA_FOUND)
find_path(RDKAFKA_INCLUDE_DIR librdkafka/rdkafka.h
HINTS ${CMAKE_STAGING_PREFIX}/include
/usr/local/include
/usr/include)
find_library(RDKAFKA_LIBRARY rdkafka
HINTS ${CMAKE_STAGING_PREFIX}/lib
/usr/local/lib
/usr/lib)
if(RDKAFKA_INCLUDE_DIR AND RDKAFKA_LIBRARY)
set(RDKAFKA_FOUND TRUE)
set(RDKAFKA_INCLUDE_DIRS ${RDKAFKA_INCLUDE_DIR})
set(RDKAFKA_LIBRARIES ${RDKAFKA_LIBRARY})
else()
message(FATAL_ERROR
"librdkafka not found. Install librdkafka-dev or set "
"CMAKE_STAGING_PREFIX / PKG_CONFIG_PATH so that rdkafka.h "
"and librdkafka.so are discoverable.")
endif()
endif()
# --- plugin target ------------------------------------------------------------
add_library(${PROJECT_NAME} SHARED
kafka_config.c
kafka_handle.c
kafka_plugin.c
kafka_plugin_intf.c
)
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_SOURCE_DIR}/include/neuron
${CMAKE_SOURCE_DIR}/plugins/kafka
)
if(TARGET PkgConfig::RDKAFKA)
target_link_libraries(${PROJECT_NAME} neuron-base PkgConfig::RDKAFKA)
else()
target_include_directories(${PROJECT_NAME} PRIVATE ${RDKAFKA_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} neuron-base ${RDKAFKA_LIBRARIES})
endif()
target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT})
# librdkafka header triggers -Wignored-qualifiers on some versions
target_compile_options(${PROJECT_NAME} PRIVATE -Wno-ignored-qualifiers)
+295
View File
@@ -0,0 +1,295 @@
{
"broker": {
"name": "Broker Address",
"name_zh": "Broker 地址",
"description": "Kafka bootstrap server address, e.g. 127.0.0.1:9092. Multiple brokers separated by commas.",
"description_zh": "Kafka bootstrap server 地址,如 127.0.0.1:9092。多个 broker 用逗号分隔。",
"attribute": "required",
"type": "string",
"default": "127.0.0.1:9092",
"valid": {
"length": 1024
}
},
"topic": {
"name": "Default Topic",
"name_zh": "默认主题",
"description": "Default Kafka topic for publishing data.",
"description_zh": "发布数据的默认 Kafka 主题。",
"attribute": "required",
"type": "string",
"default": "neuron-data",
"valid": {
"length": 255
}
},
"format": {
"name": "Upload Format",
"name_zh": "上报数据格式",
"description": "JSON format of reported data. Values-format splits data into values and errors. Tags-format puts tags in a single array.",
"description_zh": "上报数据的 JSON 格式。Values-format 下数据分为 values 和 errors。Tags-format 下数据放在一个数组中。",
"attribute": "required",
"type": "map",
"default": 0,
"valid": {
"map": [
{
"key": "values-format",
"value": 0
},
{
"key": "tags-format",
"value": 1
}
]
}
},
"upload_err": {
"name": "Upload Tag Error Code",
"name_zh": "上报点位错误码",
"description": "When data tag collection reports an error, report the tag error code.",
"description_zh": "点位采集报错时,上报点位错误码。",
"attribute": "optional",
"type": "bool",
"default": true,
"valid": {}
},
"compression": {
"name": "Compression",
"name_zh": "压缩算法",
"description": "Compression codec for message batches.",
"description_zh": "消息批次的压缩编码。",
"attribute": "optional",
"type": "map",
"default": 0,
"valid": {
"map": [
{
"key": "none",
"value": 0
},
{
"key": "gzip",
"value": 1
},
{
"key": "snappy",
"value": 2
},
{
"key": "lz4",
"value": 3
},
{
"key": "zstd",
"value": 4
}
]
}
},
"batch-max-messages": {
"name": "Batch Max Messages",
"name_zh": "批量最大消息数",
"description": "Maximum number of messages batched in one produce request.",
"description_zh": "一次发送请求中的最大消息数量。",
"attribute": "optional",
"type": "int",
"default": 10000,
"valid": {
"min": 1,
"max": 1000000
}
},
"linger-ms": {
"name": "Linger (ms)",
"name_zh": "发送等待时间(ms)",
"description": "Delay to wait for more messages before sending a batch. Larger values increase throughput at the cost of latency.",
"description_zh": "发送前等待更多消息的延迟。更大的值可提高吞吐量但增加延迟。",
"attribute": "optional",
"type": "int",
"default": 5,
"valid": {
"min": 0,
"max": 60000
}
},
"security-protocol": {
"name": "Security Protocol",
"name_zh": "安全协议",
"description": "Protocol used to communicate with Kafka brokers.",
"description_zh": "与 Kafka Broker 通信时使用的安全协议。",
"attribute": "optional",
"type": "map",
"default": 0,
"valid": {
"map": [
{
"key": "PLAINTEXT",
"value": 0
},
{
"key": "SASL_PLAINTEXT",
"value": 1
},
{
"key": "SSL",
"value": 2
},
{
"key": "SASL_SSL",
"value": 3
}
]
}
},
"sasl-mechanism": {
"name": "SASL Mechanism",
"name_zh": "SASL 机制",
"attribute": "optional",
"type": "map",
"default": 0,
"condition": {
"field": "security-protocol",
"values": [1, 3]
},
"valid": {
"map": [
{
"key": "PLAIN",
"value": 0
},
{
"key": "SCRAM-SHA-256",
"value": 1
},
{
"key": "SCRAM-SHA-512",
"value": 2
}
]
}
},
"sasl-username": {
"name": "SASL Username",
"name_zh": "SASL 用户名",
"attribute": "optional",
"type": "string",
"condition": {
"field": "security-protocol",
"values": [1, 3]
},
"default": "",
"valid": {
"length": 255
}
},
"sasl-password": {
"name": "SASL Password",
"name_zh": "SASL 密码",
"attribute": "optional",
"type": "string",
"condition": {
"field": "security-protocol",
"values": [1, 3]
},
"default": "",
"valid": {
"length": 255
}
},
"ssl-ca": {
"name": "CA Certificate",
"name_zh": "CA 证书",
"description": "CA certificate for verifying the broker's certificate.",
"description_zh": "用于验证 Broker 证书的 CA 证书。",
"attribute": "optional",
"type": "file",
"condition": {
"field": "security-protocol",
"values": [2, 3]
},
"valid": {
"length": 81960
}
},
"ssl-cert": {
"name": "Client Certificate",
"name_zh": "客户端证书",
"description": "Client certificate for mutual TLS authentication.",
"description_zh": "用于双向 TLS 认证的客户端证书。",
"attribute": "optional",
"type": "file",
"condition": {
"field": "security-protocol",
"values": [2, 3]
},
"valid": {
"length": 81960
}
},
"ssl-key": {
"name": "Client Private Key",
"name_zh": "客户端私钥",
"description": "Client private key for mutual TLS authentication.",
"description_zh": "用于双向 TLS 认证的客户端私钥。",
"attribute": "optional",
"type": "file",
"condition": {
"field": "security-protocol",
"values": [2, 3]
},
"valid": {
"length": 81960
}
},
"message-timeout-ms": {
"name": "Message Timeout (ms)",
"name_zh": "消息超时时间(ms)",
"description": "Local message timeout. This is the maximum time librdkafka may use to deliver a message (including retries).",
"description_zh": "本地消息超时时间。librdkafka 用于投递消息(含重试)的最大时间。",
"attribute": "optional",
"type": "int",
"default": 5000,
"valid": {
"min": 1000,
"max": 300000
}
},
"acks": {
"name": "Acknowledgements",
"name_zh": "确认级别",
"description": "Number of broker acknowledgements required. -1 = all in-sync replicas, 0 = no ack, 1 = leader only.",
"description_zh": "要求的 broker 确认数。-1 = 所有同步副本,0 = 无需确认,1 = 仅 leader。",
"attribute": "optional",
"type": "map",
"default": -1,
"valid": {
"map": [
{
"key": "all",
"value": -1
},
{
"key": "none",
"value": 0
},
{
"key": "leader",
"value": 1
}
]
}
},
"client-id": {
"name": "Client ID",
"name_zh": "客户端标识",
"description": "Client identifier string passed to Kafka broker for logging and monitoring.",
"description_zh": "传递给 Kafka broker 的客户端标识,用于日志和监控。",
"attribute": "optional",
"type": "string",
"default": "",
"valid": {
"length": 255
}
}
}
+375
View File
@@ -0,0 +1,375 @@
/**
* NEURON IIoT System for Industry 4.0
* Copyright (C) 2020-2024 EMQ Technologies Co., Ltd All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#include <inttypes.h>
#include "neuron.h"
#include "json/json.h"
#include "json/neu_json_param.h"
#include "kafka_config.h"
#include "kafka_plugin.h"
const char *kafka_security_protocol_str(kafka_security_protocol_e p)
{
switch (p) {
case KAFKA_SECURITY_PLAINTEXT:
return "plaintext";
case KAFKA_SECURITY_SASL_PLAINTEXT:
return "sasl_plaintext";
case KAFKA_SECURITY_SSL:
return "ssl";
case KAFKA_SECURITY_SASL_SSL:
return "sasl_ssl";
default:
return "plaintext";
}
}
const char *kafka_sasl_mechanism_str(kafka_sasl_mechanism_e m)
{
switch (m) {
case KAFKA_SASL_PLAIN:
return "PLAIN";
case KAFKA_SASL_SCRAM_SHA_256:
return "SCRAM-SHA-256";
case KAFKA_SASL_SCRAM_SHA_512:
return "SCRAM-SHA-512";
default:
return "PLAIN";
}
}
const char *kafka_compression_str(kafka_compression_e c)
{
switch (c) {
case KAFKA_COMPRESS_NONE:
return "none";
case KAFKA_COMPRESS_GZIP:
return "gzip";
case KAFKA_COMPRESS_SNAPPY:
return "snappy";
case KAFKA_COMPRESS_LZ4:
return "lz4";
case KAFKA_COMPRESS_ZSTD:
return "zstd";
default:
return "none";
}
}
static int parse_ssl_params(neu_plugin_t *plugin, const char *setting,
kafka_config_t *config)
{
if (config->security_protocol != KAFKA_SECURITY_SSL &&
config->security_protocol != KAFKA_SECURITY_SASL_SSL) {
return 0;
}
neu_json_elem_t ssl_ca = { .name = "ssl-ca", .t = NEU_JSON_STR };
int ret = neu_parse_param(setting, NULL, 1, &ssl_ca);
if (0 == ret) {
int len = 0;
char *s = (char *) neu_decode64(&len, ssl_ca.v.val_str);
free(ssl_ca.v.val_str);
if (s && len > 0) {
config->ssl_ca = s;
} else {
free(s);
plog_notice(plugin, "setting ssl-ca decode fail or empty");
}
}
neu_json_elem_t ssl_cert = { .name = "ssl-cert", .t = NEU_JSON_STR };
ret = neu_parse_param(setting, NULL, 1, &ssl_cert);
if (0 == ret) {
int len = 0;
char *s = (char *) neu_decode64(&len, ssl_cert.v.val_str);
free(ssl_cert.v.val_str);
if (s && len > 0) {
config->ssl_cert = s;
} else {
free(s);
plog_notice(plugin, "setting ssl-cert decode fail or empty");
}
}
neu_json_elem_t ssl_key = { .name = "ssl-key", .t = NEU_JSON_STR };
ret = neu_parse_param(setting, NULL, 1, &ssl_key);
if (0 == ret) {
int len = 0;
char *s = (char *) neu_decode64(&len, ssl_key.v.val_str);
free(ssl_key.v.val_str);
if (s && len > 0) {
config->ssl_key = s;
} else {
free(s);
plog_notice(plugin, "setting ssl-key decode fail or empty");
}
}
return 0;
}
static int parse_sasl_params(neu_plugin_t *plugin, const char *setting,
kafka_config_t *config)
{
if (config->security_protocol != KAFKA_SECURITY_SASL_PLAINTEXT &&
config->security_protocol != KAFKA_SECURITY_SASL_SSL) {
return 0;
}
neu_json_elem_t mechanism = {
.name = "sasl-mechanism",
.t = NEU_JSON_INT,
.v.val_int = KAFKA_SASL_PLAIN,
.attribute = NEU_JSON_ATTRIBUTE_OPTIONAL,
};
neu_parse_param(setting, NULL, 1, &mechanism);
config->sasl_mechanism = mechanism.v.val_int;
neu_json_elem_t username = { .name = "sasl-username", .t = NEU_JSON_STR };
int ret = neu_parse_param(setting, NULL, 1, &username);
if (0 == ret) {
config->sasl_username = username.v.val_str;
} else {
plog_error(plugin, "setting no sasl-username");
return -1;
}
neu_json_elem_t password = { .name = "sasl-password", .t = NEU_JSON_STR };
ret = neu_parse_param(setting, NULL, 1, &password);
if (0 == ret) {
config->sasl_password = password.v.val_str;
} else {
plog_error(plugin, "setting no sasl-password");
return -1;
}
return 0;
}
int kafka_config_parse(neu_plugin_t *plugin, const char *setting,
kafka_config_t *config)
{
int ret = 0;
char * err_param = NULL;
const char *placeholder = "********";
neu_json_elem_t broker = { .name = "broker", .t = NEU_JSON_STR };
neu_json_elem_t topic = { .name = "topic", .t = NEU_JSON_STR };
neu_json_elem_t format = { .name = "format", .t = NEU_JSON_INT };
neu_json_elem_t upload_err = {
.name = "upload_err",
.t = NEU_JSON_BOOL,
.v.val_bool = true,
.attribute = NEU_JSON_ATTRIBUTE_OPTIONAL,
};
neu_json_elem_t compression = {
.name = "compression",
.t = NEU_JSON_INT,
.v.val_int = KAFKA_COMPRESS_NONE,
.attribute = NEU_JSON_ATTRIBUTE_OPTIONAL,
};
neu_json_elem_t batch_max = {
.name = "batch-max-messages",
.t = NEU_JSON_INT,
.v.val_int = 10000,
.attribute = NEU_JSON_ATTRIBUTE_OPTIONAL,
};
neu_json_elem_t linger = {
.name = "linger-ms",
.t = NEU_JSON_INT,
.v.val_int = 5,
.attribute = NEU_JSON_ATTRIBUTE_OPTIONAL,
};
neu_json_elem_t security = {
.name = "security-protocol",
.t = NEU_JSON_INT,
.v.val_int = KAFKA_SECURITY_PLAINTEXT,
.attribute = NEU_JSON_ATTRIBUTE_OPTIONAL,
};
neu_json_elem_t msg_timeout = {
.name = "message-timeout-ms",
.t = NEU_JSON_INT,
.v.val_int = 5000,
.attribute = NEU_JSON_ATTRIBUTE_OPTIONAL,
};
neu_json_elem_t acks_param = {
.name = "acks",
.t = NEU_JSON_INT,
.v.val_int = -1,
.attribute = NEU_JSON_ATTRIBUTE_OPTIONAL,
};
neu_json_elem_t client_id = {
.name = "client-id",
.t = NEU_JSON_STR,
.attribute = NEU_JSON_ATTRIBUTE_OPTIONAL,
};
if (NULL == setting || NULL == config) {
plog_error(plugin, "invalid argument, null pointer");
return -1;
}
ret = neu_parse_param(setting, &err_param, 3, &broker, &topic, &format);
if (0 != ret) {
plog_error(plugin, "parsing setting fail, key: `%s`", err_param);
free(err_param);
free(broker.v.val_str);
free(topic.v.val_str);
return -1;
}
config->broker = broker.v.val_str;
config->topic = topic.v.val_str;
config->format = format.v.val_int;
if (0 == strlen(config->broker)) {
plog_error(plugin, "setting empty broker");
goto error;
}
if (0 == strlen(config->topic)) {
plog_error(plugin, "setting empty topic");
goto error;
}
if (config->format != KAFKA_UPLOAD_FORMAT_VALUES &&
config->format != KAFKA_UPLOAD_FORMAT_TAGS) {
plog_error(plugin, "setting invalid format: %" PRIi64,
(int64_t) config->format);
goto error;
}
neu_parse_param(setting, NULL, 1, &upload_err);
neu_parse_param(setting, NULL, 1, &compression);
neu_parse_param(setting, NULL, 1, &batch_max);
neu_parse_param(setting, NULL, 1, &linger);
neu_parse_param(setting, NULL, 1, &security);
neu_parse_param(setting, NULL, 1, &msg_timeout);
neu_parse_param(setting, NULL, 1, &acks_param);
neu_parse_param(setting, NULL, 1, &client_id);
config->upload_err = upload_err.v.val_bool;
config->compression = compression.v.val_int;
config->batch_max_messages = batch_max.v.val_int;
config->linger_ms = linger.v.val_int;
config->security_protocol = security.v.val_int;
config->message_timeout_ms = msg_timeout.v.val_int;
config->acks = acks_param.v.val_int;
config->client_id = client_id.v.val_str;
if (config->compression < KAFKA_COMPRESS_NONE ||
config->compression > KAFKA_COMPRESS_ZSTD) {
plog_error(plugin, "setting invalid compression: %d",
config->compression);
goto error;
}
if (config->batch_max_messages < 1 ||
config->batch_max_messages > 1000000) {
plog_error(plugin, "setting invalid batch-max-messages: %d",
config->batch_max_messages);
goto error;
}
if (config->linger_ms < 0 || config->linger_ms > 60000) {
plog_error(plugin, "setting invalid linger-ms: %d", config->linger_ms);
goto error;
}
if (config->security_protocol < KAFKA_SECURITY_PLAINTEXT ||
config->security_protocol > KAFKA_SECURITY_SASL_SSL) {
plog_error(plugin, "setting invalid security-protocol: %d",
config->security_protocol);
goto error;
}
if (config->message_timeout_ms < 1000 ||
config->message_timeout_ms > 300000) {
plog_error(plugin, "setting invalid message-timeout-ms: %d",
config->message_timeout_ms);
goto error;
}
if (config->acks != -1 && config->acks != 0 && config->acks != 1) {
plog_error(plugin, "setting invalid acks: %d", config->acks);
goto error;
}
ret = parse_sasl_params(plugin, setting, config);
if (0 != ret) {
goto error;
}
ret = parse_ssl_params(plugin, setting, config);
if (0 != ret) {
goto error;
}
plog_notice(plugin, "config broker : %s", config->broker);
plog_notice(plugin, "config topic : %s", config->topic);
plog_notice(plugin, "config format : %d", config->format);
plog_notice(plugin, "config upload_err : %d", config->upload_err);
plog_notice(plugin, "config compression : %s",
kafka_compression_str(config->compression));
plog_notice(plugin, "config batch-max-messages : %d",
config->batch_max_messages);
plog_notice(plugin, "config linger-ms : %d", config->linger_ms);
plog_notice(plugin, "config security-protocol : %s",
kafka_security_protocol_str(config->security_protocol));
if (config->sasl_username) {
plog_notice(plugin, "config sasl-username : %s",
config->sasl_username);
}
if (config->sasl_password) {
plog_notice(plugin, "config sasl-password : %s", placeholder);
}
if (config->ssl_ca) {
plog_notice(plugin, "config ssl-ca : %s", placeholder);
}
plog_notice(plugin, "config message-timeout-ms : %d",
config->message_timeout_ms);
plog_notice(plugin, "config acks : %d", config->acks);
if (config->client_id) {
plog_notice(plugin, "config client-id : %s",
config->client_id);
}
return 0;
error:
kafka_config_fini(config);
return -1;
}
void kafka_config_fini(kafka_config_t *config)
{
free(config->broker);
free(config->topic);
free(config->sasl_username);
free(config->sasl_password);
free(config->ssl_ca);
free(config->ssl_cert);
free(config->ssl_key);
free(config->client_id);
memset(config, 0, sizeof(*config));
}
+92
View File
@@ -0,0 +1,92 @@
/**
* NEURON IIoT System for Industry 4.0
* Copyright (C) 2020-2024 EMQ Technologies Co., Ltd All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#ifndef NEURON_PLUGIN_KAFKA_CONFIG_H
#define NEURON_PLUGIN_KAFKA_CONFIG_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdlib.h>
#include "plugin.h"
typedef enum {
KAFKA_UPLOAD_FORMAT_VALUES = 0,
KAFKA_UPLOAD_FORMAT_TAGS = 1,
} kafka_upload_format_e;
typedef enum {
KAFKA_COMPRESS_NONE = 0,
KAFKA_COMPRESS_GZIP = 1,
KAFKA_COMPRESS_SNAPPY = 2,
KAFKA_COMPRESS_LZ4 = 3,
KAFKA_COMPRESS_ZSTD = 4,
} kafka_compression_e;
typedef enum {
KAFKA_SECURITY_PLAINTEXT = 0,
KAFKA_SECURITY_SASL_PLAINTEXT = 1,
KAFKA_SECURITY_SSL = 2,
KAFKA_SECURITY_SASL_SSL = 3,
} kafka_security_protocol_e;
typedef enum {
KAFKA_SASL_PLAIN = 0,
KAFKA_SASL_SCRAM_SHA_256 = 1,
KAFKA_SASL_SCRAM_SHA_512 = 2,
} kafka_sasl_mechanism_e;
typedef struct {
char *broker;
char *topic;
kafka_upload_format_e format;
bool upload_err;
kafka_compression_e compression;
int batch_max_messages;
int linger_ms;
kafka_security_protocol_e security_protocol;
kafka_sasl_mechanism_e sasl_mechanism;
char * sasl_username;
char * sasl_password;
char * ssl_ca;
char * ssl_cert;
char * ssl_key;
int message_timeout_ms;
int acks;
char *client_id;
} kafka_config_t;
int kafka_config_parse(neu_plugin_t *plugin, const char *setting,
kafka_config_t *config);
void kafka_config_fini(kafka_config_t *config);
const char *kafka_security_protocol_str(kafka_security_protocol_e p);
const char *kafka_sasl_mechanism_str(kafka_sasl_mechanism_e m);
const char *kafka_compression_str(kafka_compression_e c);
#ifdef __cplusplus
}
#endif
#endif
+288
View File
@@ -0,0 +1,288 @@
/**
* NEURON IIoT System for Industry 4.0
* Copyright (C) 2020-2024 EMQ Technologies Co., Ltd All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#include <librdkafka/rdkafka.h>
#include "errcodes.h"
#include "json/neu_json_fn.h"
#include "json/neu_json_rw.h"
#include "kafka_handle.h"
#include "kafka_plugin.h"
static int tag_values_to_json(UT_array *tags, neu_json_read_resp_t *json,
bool filter_error)
{
int index = 0;
int n_valid = 0;
if (filter_error) {
utarray_foreach(tags, neu_resp_tag_value_meta_t *, tv)
{
if (tv->value.type != NEU_TYPE_ERROR) {
n_valid += 1;
}
}
} else {
n_valid = utarray_len(tags);
}
if (0 == n_valid) {
return 0;
}
json->n_tag = n_valid;
json->tags = (neu_json_read_resp_tag_t *) calloc(
json->n_tag, sizeof(neu_json_read_resp_tag_t));
if (NULL == json->tags) {
return -1;
}
utarray_foreach(tags, neu_resp_tag_value_meta_t *, tv)
{
if (filter_error && tv->value.type == NEU_TYPE_ERROR) {
continue;
}
neu_tag_value_to_json(tv, &json->tags[index]);
index += 1;
}
return 0;
}
static char *generate_upload_json(neu_plugin_t * plugin,
neu_reqresp_trans_data_t *data, bool *skip)
{
char *json_str = NULL;
neu_json_read_periodic_t header = {
.group = (char *) data->group,
.node = (char *) data->driver,
.timestamp = global_timestamp,
};
neu_json_read_resp_t json = { 0 };
if (0 !=
tag_values_to_json(data->tags, &json, !plugin->config.upload_err)) {
plog_error(plugin, "tag_values_to_json fail");
return NULL;
}
if (0 == json.n_tag) {
*skip = true;
return NULL;
}
if (plugin->config.format == KAFKA_UPLOAD_FORMAT_VALUES) {
neu_json_encode_with_mqtt(&json, neu_json_encode_read_resp1, &header,
neu_json_encode_read_periodic_resp,
&json_str);
} else {
neu_json_encode_with_mqtt(&json, neu_json_encode_read_resp2, &header,
neu_json_encode_read_periodic_resp,
&json_str);
}
for (int i = 0; i < json.n_tag; i++) {
if (json.tags[i].n_meta > 0) {
free(json.tags[i].metas);
}
}
if (json.tags) {
free(json.tags);
}
return json_str;
}
static int kafka_produce(neu_plugin_t *plugin, const char *topic, char *payload,
size_t len)
{
if (NULL == plugin->rk) {
return -1;
}
rd_kafka_resp_err_t err =
rd_kafka_producev(plugin->rk, RD_KAFKA_V_TOPIC(topic),
RD_KAFKA_V_MSGFLAGS(RD_KAFKA_MSG_F_COPY),
RD_KAFKA_V_VALUE(payload, len),
RD_KAFKA_V_OPAQUE(plugin), RD_KAFKA_V_END);
if (err) {
if (err == RD_KAFKA_RESP_ERR__QUEUE_FULL) {
plog_warn(plugin, "produce queue full, dropping message");
} else {
plog_error(plugin, "produce failed: %s", rd_kafka_err2str(err));
}
return -1;
}
rd_kafka_poll(plugin->rk, 0);
return 0;
}
int handle_trans_data(neu_plugin_t *plugin, neu_reqresp_trans_data_t *data)
{
int rv = 0;
if (NULL == plugin->rk) {
return NEU_ERR_PLUGIN_NOT_RUNNING;
}
const kafka_route_entry_t *route =
kafka_route_tbl_get(&plugin->route_tbl, data->driver, data->group);
const char *topic = route ? route->topic : plugin->config.topic;
bool skip = false;
char *json_str = generate_upload_json(plugin, data, &skip);
if (skip) {
return 0;
}
if (NULL == json_str) {
plog_error(plugin, "generate upload json fail for driver:%s group:%s",
data->driver, data->group);
return NEU_ERR_EINTERNAL;
}
size_t json_len = strlen(json_str);
rv = kafka_produce(plugin, topic, json_str, json_len);
if (0 == rv) {
NEU_PLUGIN_UPDATE_METRIC(plugin, NEU_METRIC_SEND_BYTES_5S,
(int64_t) json_len, NULL);
NEU_PLUGIN_UPDATE_METRIC(plugin, NEU_METRIC_SEND_BYTES_30S,
(int64_t) json_len, NULL);
NEU_PLUGIN_UPDATE_METRIC(plugin, NEU_METRIC_SEND_BYTES_60S,
(int64_t) json_len, NULL);
}
free(json_str);
return rv == 0 ? NEU_ERR_SUCCESS : NEU_ERR_PLUGIN_NOT_RUNNING;
}
int handle_subscribe_group(neu_plugin_t *plugin, neu_req_subscribe_t *sub)
{
int rv = 0;
char *topic = NULL;
neu_json_elem_t el = { .name = "topic", .t = NEU_JSON_STR };
if (NULL != sub->params &&
0 == neu_parse_param(sub->params, NULL, 1, &el)) {
topic = el.v.val_str;
} else {
topic = strdup(plugin->config.topic);
}
if (NULL == topic) {
rv = NEU_ERR_EINTERNAL;
goto end;
}
rv =
kafka_route_tbl_add(&plugin->route_tbl, sub->driver, sub->group, topic);
if (0 != rv) {
plog_error(plugin, "route driver:%s group:%s fail", sub->driver,
sub->group);
goto end;
}
plog_notice(plugin, "route driver:%s group:%s to topic:%s", sub->driver,
sub->group, topic);
end:
free(sub->params);
free(sub->static_tags);
return rv;
}
int handle_update_subscribe(neu_plugin_t *plugin, neu_req_subscribe_t *sub)
{
int rv = 0;
if (NULL == sub->params) {
rv = NEU_ERR_GROUP_PARAMETER_INVALID;
goto end;
}
neu_json_elem_t el = { .name = "topic", .t = NEU_JSON_STR };
if (0 != neu_parse_param(sub->params, NULL, 1, &el)) {
plog_error(plugin, "parse topic fail from `%s`", sub->params);
rv = NEU_ERR_GROUP_PARAMETER_INVALID;
goto end;
}
rv = kafka_route_tbl_update(&plugin->route_tbl, sub->driver, sub->group,
el.v.val_str);
if (0 != rv) {
plog_error(plugin, "update route driver:%s group:%s fail", sub->driver,
sub->group);
goto end;
}
plog_notice(plugin, "update route driver:%s group:%s to topic:%s",
sub->driver, sub->group, el.v.val_str);
end:
free(sub->params);
free(sub->static_tags);
return rv;
}
int handle_unsubscribe_group(neu_plugin_t *plugin, neu_req_unsubscribe_t *unsub)
{
kafka_route_tbl_del(&plugin->route_tbl, unsub->driver, unsub->group);
plog_notice(plugin, "del route driver:%s group:%s", unsub->driver,
unsub->group);
return 0;
}
int handle_update_group(neu_plugin_t *plugin, neu_req_update_group_t *req)
{
kafka_route_tbl_update_group(&plugin->route_tbl, req->driver, req->group,
req->new_name);
plog_notice(plugin, "update route driver:%s group:%s to %s", req->driver,
req->group, req->new_name);
return 0;
}
int handle_del_group(neu_plugin_t *plugin, neu_req_del_group_t *req)
{
kafka_route_tbl_del(&plugin->route_tbl, req->driver, req->group);
plog_notice(plugin, "del route driver:%s group:%s", req->driver,
req->group);
return 0;
}
int handle_update_driver(neu_plugin_t *plugin, neu_req_update_node_t *req)
{
kafka_route_tbl_update_driver(&plugin->route_tbl, req->node, req->new_name);
plog_notice(plugin, "update route driver:%s to %s", req->node,
req->new_name);
return 0;
}
int handle_del_driver(neu_plugin_t *plugin, neu_reqresp_node_deleted_t *req)
{
kafka_route_tbl_del_driver(&plugin->route_tbl, req->node);
plog_notice(plugin, "del route driver:%s", req->node);
return 0;
}
+47
View File
@@ -0,0 +1,47 @@
/**
* NEURON IIoT System for Industry 4.0
* Copyright (C) 2020-2024 EMQ Technologies Co., Ltd All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#ifndef NEURON_PLUGIN_KAFKA_HANDLE_H
#define NEURON_PLUGIN_KAFKA_HANDLE_H
#ifdef __cplusplus
extern "C" {
#endif
#include "kafka_config.h"
#include "neuron.h"
int handle_trans_data(neu_plugin_t *plugin, neu_reqresp_trans_data_t *data);
int handle_subscribe_group(neu_plugin_t *plugin, neu_req_subscribe_t *sub);
int handle_update_subscribe(neu_plugin_t *plugin, neu_req_subscribe_t *sub);
int handle_unsubscribe_group(neu_plugin_t * plugin,
neu_req_unsubscribe_t *unsub);
int handle_update_group(neu_plugin_t *plugin, neu_req_update_group_t *req);
int handle_del_group(neu_plugin_t *plugin, neu_req_del_group_t *req);
int handle_update_driver(neu_plugin_t *plugin, neu_req_update_node_t *req);
int handle_del_driver(neu_plugin_t *plugin, neu_reqresp_node_deleted_t *req);
#ifdef __cplusplus
}
#endif
#endif
+37
View File
@@ -0,0 +1,37 @@
/**
* NEURON IIoT System for Industry 4.0
* Copyright (C) 2020-2024 EMQ Technologies Co., Ltd All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#include "kafka_plugin.h"
#include "kafka_plugin_intf.h"
#define DESCRIPTION "Northbound Kafka plugin based on librdkafka."
#define DESCRIPTION_ZH "基于 librdkafka 的北向应用 Kafka 插件"
const neu_plugin_module_t neu_plugin_module = {
.version = NEURON_PLUGIN_VER_1_0,
.schema = "kafka",
.module_name = "Kafka",
.module_descr = DESCRIPTION,
.module_descr_zh = DESCRIPTION_ZH,
.intf_funs = &kafka_plugin_intf_funs,
.kind = NEU_PLUGIN_KIND_SYSTEM,
.type = NEU_NA_TYPE_APP,
.display = true,
.single = false,
};
+187
View File
@@ -0,0 +1,187 @@
/**
* NEURON IIoT System for Industry 4.0
* Copyright (C) 2020-2024 EMQ Technologies Co., Ltd All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#ifndef NEURON_PLUGIN_KAFKA_H
#define NEURON_PLUGIN_KAFKA_H
#ifdef __cplusplus
extern "C" {
#endif
#include <librdkafka/rdkafka.h>
#include "neuron.h"
#include "kafka_config.h"
typedef struct {
char driver[NEU_NODE_NAME_LEN];
char group[NEU_GROUP_NAME_LEN];
} kafka_route_key_t;
typedef struct {
kafka_route_key_t key;
char * topic;
UT_hash_handle hh;
} kafka_route_entry_t;
struct neu_plugin {
neu_plugin_common_t common;
neu_events_t * events;
neu_event_timer_t * poll_timer;
kafka_config_t config;
rd_kafka_t *rk;
bool connected;
int64_t last_reconn_check_ms;
int64_t delivery_succ;
int64_t delivery_fail;
kafka_route_entry_t *route_tbl;
};
/* route table helpers */
static inline void kafka_route_entry_free(kafka_route_entry_t *e)
{
free(e->topic);
free(e);
}
static inline void kafka_route_tbl_free(kafka_route_entry_t *tbl)
{
kafka_route_entry_t *e = NULL, *tmp = NULL;
HASH_ITER(hh, tbl, e, tmp)
{
HASH_DEL(tbl, e);
kafka_route_entry_free(e);
}
}
static inline kafka_route_entry_t *
kafka_route_tbl_get(kafka_route_entry_t **tbl, const char *driver,
const char *group)
{
kafka_route_entry_t *find = NULL;
kafka_route_key_t key = { 0 };
strncpy(key.driver, driver, sizeof(key.driver));
strncpy(key.group, group, sizeof(key.group));
HASH_FIND(hh, *tbl, &key, sizeof(key), find);
return find;
}
static inline int kafka_route_tbl_add(kafka_route_entry_t **tbl,
const char *driver, const char *group,
char *topic)
{
kafka_route_entry_t *find = kafka_route_tbl_get(tbl, driver, group);
if (find) {
free(topic);
return NEU_ERR_GROUP_ALREADY_SUBSCRIBED;
}
find = calloc(1, sizeof(*find));
if (NULL == find) {
free(topic);
return NEU_ERR_EINTERNAL;
}
strncpy(find->key.driver, driver, sizeof(find->key.driver));
strncpy(find->key.group, group, sizeof(find->key.group));
find->topic = topic;
HASH_ADD(hh, *tbl, key, sizeof(find->key), find);
return 0;
}
static inline int kafka_route_tbl_update(kafka_route_entry_t **tbl,
const char *driver, const char *group,
char *topic)
{
kafka_route_entry_t *find = kafka_route_tbl_get(tbl, driver, group);
if (NULL == find) {
free(topic);
return NEU_ERR_GROUP_NOT_SUBSCRIBE;
}
free(find->topic);
find->topic = topic;
return 0;
}
static inline void kafka_route_tbl_update_driver(kafka_route_entry_t **tbl,
const char * driver,
const char * new_name)
{
kafka_route_entry_t *e = NULL, *tmp = NULL;
HASH_ITER(hh, *tbl, e, tmp)
{
if (0 == strcmp(e->key.driver, driver)) {
HASH_DEL(*tbl, e);
strncpy(e->key.driver, new_name, sizeof(e->key.driver));
HASH_ADD(hh, *tbl, key, sizeof(e->key), e);
}
}
}
static inline void kafka_route_tbl_update_group(kafka_route_entry_t **tbl,
const char * driver,
const char * group,
const char * new_name)
{
kafka_route_entry_t *e = kafka_route_tbl_get(tbl, driver, group);
if (e) {
HASH_DEL(*tbl, e);
strncpy(e->key.group, new_name, sizeof(e->key.group));
HASH_ADD(hh, *tbl, key, sizeof(e->key), e);
}
}
static inline void kafka_route_tbl_del_driver(kafka_route_entry_t **tbl,
const char * driver)
{
kafka_route_entry_t *e = NULL, *tmp = NULL;
HASH_ITER(hh, *tbl, e, tmp)
{
if (0 == strcmp(e->key.driver, driver)) {
HASH_DEL(*tbl, e);
kafka_route_entry_free(e);
}
}
}
static inline void kafka_route_tbl_del(kafka_route_entry_t **tbl,
const char *driver, const char *group)
{
kafka_route_entry_t *find = kafka_route_tbl_get(tbl, driver, group);
if (find) {
HASH_DEL(*tbl, find);
kafka_route_entry_free(find);
}
}
#ifdef __cplusplus
}
#endif
#endif
+465
View File
@@ -0,0 +1,465 @@
/**
* NEURON IIoT System for Industry 4.0
* Copyright (C) 2020-2024 EMQ Technologies Co., Ltd All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#include <inttypes.h>
#include <librdkafka/rdkafka.h>
#include <time.h>
#include "errcodes.h"
#include "utils/asprintf.h"
#include "kafka_config.h"
#include "kafka_handle.h"
#include "kafka_plugin.h"
#include "kafka_plugin_intf.h"
extern const neu_plugin_module_t neu_plugin_module;
/* ----------------------------- rdkafka callbacks -------------------------- */
static void dr_msg_cb(rd_kafka_t *rk, const rd_kafka_message_t *msg,
void *opaque)
{
(void) rk;
neu_plugin_t *plugin = (neu_plugin_t *) opaque;
if (msg->err) {
plugin->delivery_fail++;
NEU_PLUGIN_UPDATE_METRIC(plugin, NEU_METRIC_SEND_MSG_ERRORS_TOTAL, 1,
NULL);
if (plugin->delivery_fail % 100 == 1) {
plog_warn(plugin,
"delivery failed (%s): %s (total_fail=%" PRId64 ")",
rd_kafka_topic_name(msg->rkt), rd_kafka_err2str(msg->err),
plugin->delivery_fail);
}
} else {
plugin->delivery_succ++;
NEU_PLUGIN_UPDATE_METRIC(plugin, NEU_METRIC_SEND_MSGS_TOTAL, 1, NULL);
if (!plugin->connected) {
plugin->connected = true;
plugin->common.link_state = NEU_NODE_LINK_STATE_CONNECTED;
plog_notice(plugin,
"broker reconnected (delivery ok, succ=%" PRId64 ")",
plugin->delivery_succ);
}
}
}
static void error_cb(rd_kafka_t *rk, int err, const char *reason, void *opaque)
{
(void) rk;
neu_plugin_t *plugin = (neu_plugin_t *) opaque;
if (err == RD_KAFKA_RESP_ERR__ALL_BROKERS_DOWN ||
err == RD_KAFKA_RESP_ERR__TRANSPORT) {
if (plugin->connected) {
plugin->connected = false;
plugin->common.link_state = NEU_NODE_LINK_STATE_DISCONNECTED;
NEU_PLUGIN_UPDATE_METRIC(plugin, NEU_METRIC_DISCONNECTION_60S, 1,
NULL);
NEU_PLUGIN_UPDATE_METRIC(plugin, NEU_METRIC_DISCONNECTION_600S, 1,
NULL);
NEU_PLUGIN_UPDATE_METRIC(plugin, NEU_METRIC_DISCONNECTION_1800S, 1,
NULL);
plog_error(plugin, "broker connection lost: %s", reason);
}
} else {
plog_warn(plugin, "rdkafka error %d: %s", err, reason);
}
}
static void stats_cb_check_connected(neu_plugin_t *plugin, rd_kafka_t *rk)
{
(void) rk;
const rd_kafka_metadata_t *md = NULL;
rd_kafka_resp_err_t err = rd_kafka_metadata(plugin->rk, 0, NULL, &md, 2000);
if (err == RD_KAFKA_RESP_ERR_NO_ERROR && md && md->broker_cnt > 0) {
if (!plugin->connected) {
plugin->connected = true;
plugin->common.link_state = NEU_NODE_LINK_STATE_CONNECTED;
plog_notice(plugin, "broker connected (brokers=%d)",
md->broker_cnt);
}
}
if (md) {
rd_kafka_metadata_destroy(md);
}
}
/* ------------------------------ poll timer -------------------------------- */
static int64_t current_time_ms(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (int64_t) ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
#define RECONN_CHECK_INTERVAL_MS 5000
static int poll_timer_cb(void *data)
{
neu_plugin_t *plugin = (neu_plugin_t *) data;
if (plugin->rk) {
rd_kafka_poll(plugin->rk, 0);
if (!plugin->connected) {
int64_t now = current_time_ms();
if (now - plugin->last_reconn_check_ms >=
RECONN_CHECK_INTERVAL_MS) {
plugin->last_reconn_check_ms = now;
stats_cb_check_connected(plugin, plugin->rk);
}
}
}
return 0;
}
static int start_poll_timer(neu_plugin_t *plugin)
{
if (NULL == plugin->events) {
plugin->events = neu_event_new(plugin->common.name);
if (NULL == plugin->events) {
plog_error(plugin, "neu_event_new fail");
return -1;
}
}
neu_event_timer_param_t param = {
.second = 0,
.millisecond = 200,
.cb = poll_timer_cb,
.usr_data = plugin,
};
neu_event_timer_t *timer = neu_event_add_timer(plugin->events, param);
if (NULL == timer) {
plog_error(plugin, "neu_event_add_timer fail");
return -1;
}
if (plugin->poll_timer) {
neu_event_del_timer(plugin->events, plugin->poll_timer);
}
plugin->poll_timer = timer;
return 0;
}
static void stop_poll_timer(neu_plugin_t *plugin)
{
if (plugin->poll_timer) {
neu_event_del_timer(plugin->events, plugin->poll_timer);
plugin->poll_timer = NULL;
}
}
/* ----------------------------- rdkafka setup ------------------------------ */
static int set_conf(neu_plugin_t *plugin, rd_kafka_conf_t *conf,
const char *key, const char *val)
{
char errstr[512];
if (RD_KAFKA_CONF_OK !=
rd_kafka_conf_set(conf, key, val, errstr, sizeof(errstr))) {
plog_warn(plugin, "rd_kafka_conf_set %s=%s fail: %s", key, val, errstr);
return -1;
}
return 0;
}
static void set_rdkafka_conf(neu_plugin_t *plugin, rd_kafka_conf_t *conf,
const kafka_config_t *config)
{
char buf[64];
set_conf(plugin, conf, "bootstrap.servers", config->broker);
snprintf(buf, sizeof(buf), "%d", config->batch_max_messages);
set_conf(plugin, conf, "queue.buffering.max.messages", buf);
snprintf(buf, sizeof(buf), "%d", config->linger_ms);
set_conf(plugin, conf, "linger.ms", buf);
set_conf(plugin, conf, "compression.codec",
kafka_compression_str(config->compression));
snprintf(buf, sizeof(buf), "%d", config->message_timeout_ms);
set_conf(plugin, conf, "message.timeout.ms", buf);
snprintf(buf, sizeof(buf), "%d", config->acks);
set_conf(plugin, conf, "acks", buf);
if (config->client_id) {
set_conf(plugin, conf, "client.id", config->client_id);
}
set_conf(plugin, conf, "security.protocol",
kafka_security_protocol_str(config->security_protocol));
if (config->security_protocol == KAFKA_SECURITY_SASL_PLAINTEXT ||
config->security_protocol == KAFKA_SECURITY_SASL_SSL) {
set_conf(plugin, conf, "sasl.mechanism",
kafka_sasl_mechanism_str(config->sasl_mechanism));
if (config->sasl_username) {
set_conf(plugin, conf, "sasl.username", config->sasl_username);
}
if (config->sasl_password) {
set_conf(plugin, conf, "sasl.password", config->sasl_password);
}
}
if (config->security_protocol == KAFKA_SECURITY_SSL ||
config->security_protocol == KAFKA_SECURITY_SASL_SSL) {
if (config->ssl_ca) {
set_conf(plugin, conf, "ssl.ca.pem", config->ssl_ca);
}
if (config->ssl_cert) {
set_conf(plugin, conf, "ssl.certificate.pem", config->ssl_cert);
}
if (config->ssl_key) {
set_conf(plugin, conf, "ssl.key.pem", config->ssl_key);
}
}
rd_kafka_conf_set_dr_msg_cb(conf, dr_msg_cb);
rd_kafka_conf_set_error_cb(conf, error_cb);
rd_kafka_conf_set_opaque(conf, plugin);
}
static rd_kafka_t *create_producer(neu_plugin_t * plugin,
const kafka_config_t *config)
{
char errstr[512];
rd_kafka_conf_t *conf = rd_kafka_conf_new();
set_rdkafka_conf(plugin, conf, config);
rd_kafka_t *rk =
rd_kafka_new(RD_KAFKA_PRODUCER, conf, errstr, sizeof(errstr));
if (NULL == rk) {
plog_error(plugin, "rd_kafka_new failed: %s", errstr);
return NULL;
}
return rk;
}
/* ---------------------------- plugin interface ---------------------------- */
static neu_plugin_t *kafka_plugin_open(void)
{
neu_plugin_t *plugin = (neu_plugin_t *) calloc(1, sizeof(neu_plugin_t));
neu_plugin_common_init(&plugin->common);
return plugin;
}
static int kafka_plugin_close(neu_plugin_t *plugin)
{
plog_notice(plugin, "plugin closed");
free(plugin);
return NEU_ERR_SUCCESS;
}
static int kafka_plugin_init(neu_plugin_t *plugin, bool load)
{
(void) load;
NEU_PLUGIN_REGISTER_METRIC(plugin, NEU_METRIC_TRANS_DATA_5S, 5000);
NEU_PLUGIN_REGISTER_METRIC(plugin, NEU_METRIC_TRANS_DATA_30S, 30000);
NEU_PLUGIN_REGISTER_METRIC(plugin, NEU_METRIC_TRANS_DATA_60S, 60000);
NEU_PLUGIN_REGISTER_METRIC(plugin, NEU_METRIC_SEND_MSGS_TOTAL, 0);
NEU_PLUGIN_REGISTER_METRIC(plugin, NEU_METRIC_SEND_MSG_ERRORS_TOTAL, 0);
NEU_PLUGIN_REGISTER_METRIC(plugin, NEU_METRIC_SEND_BYTES_5S, 5000);
NEU_PLUGIN_REGISTER_METRIC(plugin, NEU_METRIC_SEND_BYTES_30S, 30000);
NEU_PLUGIN_REGISTER_METRIC(plugin, NEU_METRIC_SEND_BYTES_60S, 60000);
NEU_PLUGIN_REGISTER_METRIC(plugin, NEU_METRIC_DISCONNECTION_60S, 60000);
NEU_PLUGIN_REGISTER_METRIC(plugin, NEU_METRIC_DISCONNECTION_600S, 600000);
NEU_PLUGIN_REGISTER_METRIC(plugin, NEU_METRIC_DISCONNECTION_1800S, 1800000);
plog_notice(plugin, "plugin `%s` initialized",
neu_plugin_module.module_name);
return NEU_ERR_SUCCESS;
}
static int kafka_plugin_uninit(neu_plugin_t *plugin)
{
stop_poll_timer(plugin);
if (plugin->events) {
neu_event_close(plugin->events);
plugin->events = NULL;
}
if (plugin->rk) {
rd_kafka_flush(plugin->rk, 5000);
rd_kafka_destroy(plugin->rk);
plugin->rk = NULL;
}
kafka_config_fini(&plugin->config);
kafka_route_tbl_free(plugin->route_tbl);
plugin->route_tbl = NULL;
plog_notice(plugin, "plugin `%s` uninitialized",
neu_plugin_module.module_name);
return NEU_ERR_SUCCESS;
}
static int kafka_plugin_config(neu_plugin_t *plugin, const char *setting)
{
int rv = 0;
kafka_config_t config = { 0 };
rv = kafka_config_parse(plugin, setting, &config);
if (0 != rv) {
plog_error(plugin, "kafka_config_parse fail");
return NEU_ERR_NODE_SETTING_INVALID;
}
stop_poll_timer(plugin);
if (plugin->rk) {
rd_kafka_flush(plugin->rk, 3000);
rd_kafka_destroy(plugin->rk);
plugin->rk = NULL;
}
plugin->rk = create_producer(plugin, &config);
if (NULL == plugin->rk) {
plog_error(plugin, "create kafka producer fail");
kafka_config_fini(&config);
return NEU_ERR_PLUGIN_NOT_RUNNING;
}
if (0 != start_poll_timer(plugin)) {
plog_error(plugin, "start poll timer fail");
rd_kafka_destroy(plugin->rk);
plugin->rk = NULL;
kafka_config_fini(&config);
return NEU_ERR_EINTERNAL;
}
stats_cb_check_connected(plugin, plugin->rk);
kafka_config_fini(&plugin->config);
plugin->config = config;
plog_notice(plugin, "plugin `%s` configured",
neu_plugin_module.module_name);
return NEU_ERR_SUCCESS;
}
static int kafka_plugin_start(neu_plugin_t *plugin)
{
if (NULL == plugin->rk) {
plog_error(plugin, "kafka producer is NULL");
return NEU_ERR_PLUGIN_NOT_RUNNING;
}
if (0 != start_poll_timer(plugin)) {
return NEU_ERR_EINTERNAL;
}
stats_cb_check_connected(plugin, plugin->rk);
plog_notice(plugin, "plugin `%s` started", neu_plugin_module.module_name);
return NEU_ERR_SUCCESS;
}
static int kafka_plugin_stop(neu_plugin_t *plugin)
{
stop_poll_timer(plugin);
if (plugin->rk) {
rd_kafka_flush(plugin->rk, 3000);
}
plugin->connected = false;
plugin->common.link_state = NEU_NODE_LINK_STATE_DISCONNECTED;
plog_notice(plugin, "plugin `%s` stopped", neu_plugin_module.module_name);
return NEU_ERR_SUCCESS;
}
static int kafka_plugin_request(neu_plugin_t *plugin, neu_reqresp_head_t *head,
void *data)
{
neu_err_code_e error = NEU_ERR_SUCCESS;
switch (head->type) {
case NEU_REQRESP_TRANS_DATA:
if (plugin->rk) {
NEU_PLUGIN_UPDATE_METRIC(plugin, NEU_METRIC_TRANS_DATA_5S, 1, NULL);
NEU_PLUGIN_UPDATE_METRIC(plugin, NEU_METRIC_TRANS_DATA_30S, 1,
NULL);
NEU_PLUGIN_UPDATE_METRIC(plugin, NEU_METRIC_TRANS_DATA_60S, 1,
NULL);
}
error = handle_trans_data(plugin, data);
break;
case NEU_REQ_SUBSCRIBE_GROUP:
error = handle_subscribe_group(plugin, data);
break;
case NEU_REQ_UPDATE_SUBSCRIBE_GROUP:
error = handle_update_subscribe(plugin, data);
break;
case NEU_REQ_UNSUBSCRIBE_GROUP:
error = handle_unsubscribe_group(plugin, data);
break;
case NEU_REQ_UPDATE_GROUP:
error = handle_update_group(plugin, data);
break;
case NEU_REQ_DEL_GROUP:
error = handle_del_group(plugin, data);
break;
case NEU_REQ_UPDATE_NODE:
error = handle_update_driver(plugin, data);
break;
case NEU_REQRESP_NODE_DELETED: {
neu_reqresp_node_deleted_t *req = data;
if (0 != strcmp(plugin->common.name, req->node)) {
error = handle_del_driver(plugin, data);
}
break;
}
default:
error = NEU_ERR_PLUGIN_NOT_RUNNING;
break;
}
return error;
}
const neu_plugin_intf_funs_t kafka_plugin_intf_funs = {
.open = kafka_plugin_open,
.close = kafka_plugin_close,
.init = kafka_plugin_init,
.uninit = kafka_plugin_uninit,
.start = kafka_plugin_start,
.stop = kafka_plugin_stop,
.setting = kafka_plugin_config,
.request = kafka_plugin_request,
.try_connect = NULL,
};
+27
View File
@@ -0,0 +1,27 @@
/**
* NEURON IIoT System for Industry 4.0
* Copyright (C) 2020-2024 EMQ Technologies Co., Ltd All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#ifndef NEURON_PLUGIN_KAFKA_INTF_H
#define NEURON_PLUGIN_KAFKA_INTF_H
#include "plugin.h"
extern const neu_plugin_intf_funs_t kafka_plugin_intf_funs;
#endif
+11 -3
View File
@@ -18,11 +18,11 @@
},
{
"type": 7,
"regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$"
"regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|#WS|#WR|)$"
},
{
"type": 8,
"regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$"
"regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|#WS|#WR|)$"
},
{
"type": 9,
@@ -30,7 +30,7 @@
},
{
"type": 10,
"regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$"
"regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|#WS|#WR|)$"
},
{
"type": 11,
@@ -211,6 +211,14 @@
{
"key": "78 56 34 12",
"value": 4
},
{
"key": "34 12 78 56",
"value": 5
},
{
"key": "56 78 12 34",
"value": 6
}
]
}
+11 -3
View File
@@ -18,11 +18,11 @@
},
{
"type": 7,
"regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$"
"regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|#WS|#WR|)$"
},
{
"type": 8,
"regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$"
"regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|#WS|#WR|)$"
},
{
"type": 9,
@@ -30,7 +30,7 @@
},
{
"type": 10,
"regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|)$"
"regex": "^[0-9]+![3-4][0-9]+(#B|#L|#BB|#BL|#LL|#LB|#WS|#WR|)$"
},
{
"type": 11,
@@ -219,6 +219,14 @@
{
"key": "78 56 34 12",
"value": 4
},
{
"key": "34 12 78 56",
"value": 5
},
{
"key": "56 78 12 34",
"value": 6
}
]
}
+2
View File
@@ -78,6 +78,8 @@ typedef enum modbus_endianess_64 {
MODBUS_LB = 2, // 21 43 65 87
MODBUS_BB = 3, // 87 65 43 21
MODBUS_BL = 4, // 78 56 34 12
MODBUS_WS = 5, // 34 12 78 56
MODBUS_WR = 6, // 56 78 12 34
} modbus_endianess_64;
typedef enum modbus_address_base {
+10
View File
@@ -52,6 +52,10 @@ int modbus_tag_to_point(const neu_datatag_t *tag, modbus_point_t *point,
return NEU_ERR_TAG_ADDRESS_FORMAT_INVALID;
}
if (start_address > 65536) {
return NEU_ERR_TAG_ADDRESS_FORMAT_INVALID;
}
if (start_address == 65536 && address_base == 0) {
point->start_address = 65535;
} else if (start_address == 0 && address_base == 1) {
@@ -581,6 +585,12 @@ void modbus_convert_endianess_64(neu_value_u * value,
case MODBUS_BL:
value->u64 = neu_htonbl(value->u64);
break;
case MODBUS_WS:
value->u64 = neu_htonws(value->u64);
break;
case MODBUS_WR:
value->u64 = neu_htonwr(value->u64);
break;
default:
break;
}
+51 -1
View File
@@ -47,6 +47,41 @@ static int process_protocol_buf_test(neu_plugin_t *plugin, void *req,
modbus_point_t *point,
uint16_t response_size);
static void
convert_point_endianess_64_for_test(neu_value_u *value, modbus_point_t *point,
modbus_endianess_64 default_endianess)
{
modbus_endianess_64 endianess = default_endianess;
if (!point->option.value64.is_default) {
switch (point->option.value64.endian) {
case NEU_DATATAG_ENDIAN_B64:
case NEU_DATATAG_ENDIAN_BB64:
endianess = MODBUS_BB;
break;
case NEU_DATATAG_ENDIAN_LB64:
endianess = MODBUS_LB;
break;
case NEU_DATATAG_ENDIAN_BL64:
endianess = MODBUS_BL;
break;
case NEU_DATATAG_ENDIAN_WS64:
endianess = MODBUS_WS;
break;
case NEU_DATATAG_ENDIAN_WR64:
endianess = MODBUS_WR;
break;
case NEU_DATATAG_ENDIAN_L64:
case NEU_DATATAG_ENDIAN_LL64:
default:
endianess = MODBUS_LL;
break;
}
}
modbus_convert_endianess_64(value, endianess);
}
void modbus_conn_connected(void *data, int fd)
{
struct neu_plugin *plugin = (struct neu_plugin *) data;
@@ -595,6 +630,11 @@ int modbus_value_handle_test(neu_plugin_t *plugin, void *req,
case NEU_TYPE_DOUBLE: {
uint64_t tmp_vald;
memcpy(&tmp_vald, recv_bytes, sizeof(uint64_t));
neu_value_u value = { 0 };
value.u64 = tmp_vald;
convert_point_endianess_64_for_test(&value, point,
plugin->endianess_64);
tmp_vald = value.u64;
jtype = NEU_JSON_DOUBLE;
jvalue.val_int = neu_ntohll(tmp_vald);
break;
@@ -603,6 +643,11 @@ int modbus_value_handle_test(neu_plugin_t *plugin, void *req,
jtype = NEU_JSON_INT;
int64_t tmp_val64;
memcpy(&tmp_val64, recv_bytes, sizeof(int64_t));
neu_value_u value = { 0 };
value.u64 = (uint64_t) tmp_val64;
convert_point_endianess_64_for_test(&value, point,
plugin->endianess_64);
tmp_val64 = (int64_t) value.u64;
jvalue.val_int = (int64_t) neu_ntohll(tmp_val64);
break;
}
@@ -610,6 +655,11 @@ int modbus_value_handle_test(neu_plugin_t *plugin, void *req,
jtype = NEU_JSON_INT;
uint64_t tmp_val64;
memcpy(&tmp_val64, recv_bytes, sizeof(uint64_t));
neu_value_u value = { 0 };
value.u64 = tmp_val64;
convert_point_endianess_64_for_test(&value, point,
plugin->endianess_64);
tmp_val64 = value.u64;
jvalue.val_int = neu_ntohll(tmp_val64);
break;
}
@@ -1025,4 +1075,4 @@ static int process_protocol_buf_test(neu_plugin_t *plugin, void *req,
free(recv_buf);
return ret;
}
}
+9 -8
View File
@@ -44,14 +44,15 @@ static int driver_write(neu_plugin_t *plugin, void *req, neu_datatag_t *tag,
static int driver_write_tags(neu_plugin_t *plugin, void *req, UT_array *tags);
static const neu_plugin_intf_funs_t plugin_intf_funs = {
.open = driver_open,
.close = driver_close,
.init = driver_init,
.uninit = driver_uninit,
.start = driver_start,
.stop = driver_stop,
.setting = driver_config,
.request = driver_request,
.open = driver_open,
.close = driver_close,
.init = driver_init,
.uninit = driver_uninit,
.start = driver_start,
.stop = driver_stop,
.setting = driver_config,
.request = driver_request,
.try_connect = NULL,
.driver.validate_tag = driver_validate_tag,
.driver.group_timer = driver_group_timer,
+25 -8
View File
@@ -36,6 +36,7 @@ static int driver_stop(neu_plugin_t *plugin);
static int driver_config(neu_plugin_t *plugin, const char *config);
static int driver_request(neu_plugin_t *plugin, neu_reqresp_head_t *head,
void *data);
static int driver_connect(neu_plugin_t *plugin);
static int driver_validate_tag(neu_plugin_t *plugin, neu_datatag_t *tag);
static int driver_group_timer(neu_plugin_t *plugin, neu_plugin_group_t *group);
@@ -46,14 +47,15 @@ static int driver_test_read_tag(neu_plugin_t *plugin, void *req,
neu_datatag_t tag);
static const neu_plugin_intf_funs_t plugin_intf_funs = {
.open = driver_open,
.close = driver_close,
.init = driver_init,
.uninit = driver_uninit,
.start = driver_start,
.stop = driver_stop,
.setting = driver_config,
.request = driver_request,
.open = driver_open,
.close = driver_close,
.init = driver_init,
.uninit = driver_uninit,
.start = driver_start,
.stop = driver_stop,
.setting = driver_config,
.request = driver_request,
.try_connect = driver_connect,
.driver.validate_tag = driver_validate_tag,
.driver.group_timer = driver_group_timer,
@@ -388,6 +390,21 @@ static int driver_config(neu_plugin_t *plugin, const char *config)
return 0;
}
static int driver_connect(neu_plugin_t *plugin)
{
if (plugin->conn != NULL) {
if (neu_conn_is_connected(plugin->conn)) {
plugin->common.link_state = NEU_NODE_LINK_STATE_CONNECTED;
} else {
neu_conn_connect(plugin->conn);
if (neu_conn_is_connected(plugin->conn)) {
plugin->common.link_state = NEU_NODE_LINK_STATE_CONNECTED;
}
}
}
return 0;
}
static int driver_request(neu_plugin_t *plugin, neu_reqresp_head_t *head,
void *data)
{
+9 -8
View File
@@ -397,14 +397,15 @@ static int monitor_plugin_stop(neu_plugin_t *plugin)
}
static const neu_plugin_intf_funs_t plugin_intf_funs = {
.open = monitor_plugin_open,
.close = monitor_plugin_close,
.init = monitor_plugin_init,
.uninit = monitor_plugin_uninit,
.start = monitor_plugin_start,
.stop = monitor_plugin_stop,
.setting = monitor_plugin_config,
.request = monitor_plugin_request,
.open = monitor_plugin_open,
.close = monitor_plugin_close,
.init = monitor_plugin_init,
.uninit = monitor_plugin_uninit,
.start = monitor_plugin_start,
.stop = monitor_plugin_stop,
.setting = monitor_plugin_config,
.request = monitor_plugin_request,
.try_connect = NULL,
};
#define DEFAULT_MONITOR_PLUGIN_DESCR \
+1 -1
View File
@@ -111,7 +111,7 @@
"name_zh": "CA 证书",
"description": "CA certificate which signs the server certificate",
"description_zh": "签发服务器证书的 CA 证书",
"attribute": "required",
"attribute": "optional",
"type": "file",
"condition": {
"field": "ssl",
+4 -4
View File
@@ -84,11 +84,11 @@ static inline int parse_ssl_params(neu_plugin_t *plugin, const char *setting,
return 0;
}
// ca, required when ssl enabled
// ca, optional
ret = neu_parse_param(setting, NULL, 1, ca);
if (0 != ret) {
plog_error(plugin, "setting no ca, required when ssl enabled");
return -1;
plog_notice(plugin, "setting no ca");
return 0;
}
if (0 != decode_b64_param(plugin, ca)) {
@@ -106,7 +106,7 @@ static inline int parse_ssl_params(neu_plugin_t *plugin, const char *setting,
return -1;
}
// key, required if cert is set
// key, required if cert enable
ret = parse_b64_param(plugin, setting, key);
if (0 != ret) {
return -1;
+4 -18
View File
@@ -41,11 +41,9 @@ static char *generate_heartbeat_json(neu_plugin_t *plugin, UT_array *states)
char * json_str = NULL;
json.n_state = utarray_len(states);
if (json.n_state > 0) {
json.states = calloc(json.n_state, sizeof(neu_json_node_state_t));
if (NULL == json.states) {
return NULL;
}
json.states = calloc(json.n_state, sizeof(neu_json_node_state_t));
if (NULL == json.states) {
return NULL;
}
utarray_foreach(states, neu_nodes_state_t *, state)
@@ -212,10 +210,6 @@ static char *generate_event_json(neu_plugin_t *plugin, neu_reqresp_type_e event,
json_req.add_gtag.n_group = add_gtag->n_group;
json_req.add_gtag.groups =
calloc(add_gtag->n_group, sizeof(*json_req.add_gtag.groups));
if (NULL == json_req.add_gtag.groups) {
plog_error(plugin, "calloc fail");
break;
}
for (u_int16_t i = 0; i < add_gtag->n_group; i++) {
json_req.add_gtag.groups[i].group =
strdup(add_gtag->groups[i].group);
@@ -224,10 +218,6 @@ static char *generate_event_json(neu_plugin_t *plugin, neu_reqresp_type_e event,
json_req.add_gtag.groups[i].tags =
calloc(add_gtag->groups[i].n_tag,
sizeof(*json_req.add_gtag.groups->tags));
if (NULL == json_req.add_gtag.groups[i].tags) {
plog_error(plugin, "calloc fail");
break;
}
for (u_int16_t j = 0; j < add_gtag->groups[i].n_tag; j++) {
json_req.add_gtag.groups[i].tags[j].type =
add_gtag->groups[i].tags[j].type;
@@ -249,11 +239,7 @@ static char *generate_event_json(neu_plugin_t *plugin, neu_reqresp_type_e event,
neu_json_encode_by_fn(&json_req, neu_json_encode_add_gtags_req,
&json_str);
for (u_int16_t i = 0; i < add_gtag->n_group; i++) {
free(json_req.add_gtag.groups[i].group);
free(json_req.add_gtag.groups[i].tags);
}
free(json_req.add_gtag.groups);
free(json_req.add_tags.tags);
break;
}
case NEU_REQ_ADD_PLUGIN_EVENT: {
+1 -1
View File
@@ -206,7 +206,7 @@
"name_zh": "根 CA 证书",
"description": "AWS IoT data endpoint root CA certificate",
"description_zh": "AWS IoT 设备数据终端节点根 CA 证书",
"attribute": "required",
"attribute": "optional",
"type": "file",
"condition": {
"field": "ssl",
+63
View File
@@ -52,6 +52,65 @@
]
}
},
"offline-cache": {
"name": "Offline Data Caching",
"name_zh": "离线缓存",
"description": "Offline caching switch. Cache MQTT messages when offline, and sync cached messages when back online.",
"description_zh": "离线缓存开关。连接断开时缓存 MQTT 消息,连接重建时同步缓存的 MQTT 消息到服务器。",
"attribute": "optional",
"type": "bool",
"default": false,
"valid": {}
},
"cache-mem-size": {
"name": "Cache Memory Size (MB)",
"name_zh": "缓存内存大小(MB)",
"description": "Max in-memory cache size in megabytes when MQTT connection exception occurs. Should be smaller than cache disk size.",
"description_zh": "当 MQTT 连接异常时,最大的内存缓存大小(单位:MB)。应该小于缓存磁盘大小。",
"type": "int",
"attribute": "required",
"condition": {
"field": "offline-cache",
"value": true
},
"valid": {
"min": 1,
"max": 1024
}
},
"cache-disk-size": {
"name": "Cache Disk Size (MB)",
"name_zh": "缓存磁盘大小(MB)",
"description": "Max in-disk cache size in megabytes when MQTT connection exception occurs. Should be larger than cache memory size. If nonzero, cache memory size should also be nonzero.",
"description_zh": "当 MQTT 连接异常时,最大的磁盘缓存大小(单位:MB)。应该大于缓存内存大小。如果不为 0,缓存内存大小也应该不为 0。",
"type": "int",
"attribute": "required",
"condition": {
"field": "offline-cache",
"value": true
},
"valid": {
"min": 1,
"max": 10240
}
},
"cache-sync-interval": {
"name": "Cache Sync Interval (MS)",
"name_zh": "缓存消息重传间隔(MS)",
"description": "Interval in milliseconds at which cached messages are synced to the broker once the connection is restored.",
"description_zh": "连接恢复后缓存消息向服务端重传的时间间隔(单位:毫秒)。",
"type": "int",
"attribute": "required",
"condition": {
"field": "offline-cache",
"value": true
},
"default": 100,
"valid": {
"min": 10,
"max": 120000
}
},
"host": {
"name": "IoT Hub Hostname",
"name_zh": "IoT 中心域名",
@@ -106,6 +165,10 @@
"description_zh": "签发服务器证书的 CA 证书",
"attribute": "required",
"type": "file",
"condition": {
"field": "auth",
"value": 1
},
"valid": {
"length": 81960
}
+112 -34
View File
@@ -28,11 +28,98 @@
#define AUTH_SAS 0
#define AUTH_CERT 1
static int parse_ssl_params(neu_plugin_t *plugin, const char *setting,
neu_json_elem_t *cert, neu_json_elem_t *key)
#define MB 1000000
static int parse_cache_params(neu_plugin_t *plugin, const char *setting,
mqtt_config_t *config)
{
int ret = 0;
char * err_param = NULL;
neu_json_elem_t offline_cache = { .name = "offline-cache",
.t = NEU_JSON_BOOL };
neu_json_elem_t cache_mem_size = { .name = "cache-mem-size",
.t = NEU_JSON_INT };
neu_json_elem_t cache_disk_size = { .name = "cache-disk-size",
.t = NEU_JSON_INT };
neu_json_elem_t cache_sync_interval = { .name = "cache-sync-interval",
.t = NEU_JSON_INT };
// offline-cache flag, optional (backward compatible: missing -> disabled)
ret = neu_parse_param(setting, NULL, 1, &offline_cache);
if (0 != ret || !offline_cache.v.val_bool) {
config->cache = false;
config->cache_mem_size = 0;
config->cache_disk_size = 0;
config->cache_sync_interval = NEU_MQTT_CACHE_SYNC_INTERVAL_DEFAULT;
return 0;
}
// cache enabled, mem and disk size are required
ret = neu_parse_param(setting, &err_param, 2, &cache_mem_size,
&cache_disk_size);
if (0 != ret) {
plog_error(plugin, "parsing cache size fail, key: `%s`", err_param);
free(err_param);
return -1;
}
if (cache_mem_size.v.val_int < 1 || cache_mem_size.v.val_int > 1024) {
plog_error(plugin, "setting invalid cache memory size: %" PRIi64,
cache_mem_size.v.val_int);
return -1;
}
if (cache_disk_size.v.val_int < 1 || cache_disk_size.v.val_int > 10240) {
plog_error(plugin, "setting invalid cache disk size: %" PRIi64,
cache_disk_size.v.val_int);
return -1;
}
if (cache_mem_size.v.val_int > cache_disk_size.v.val_int) {
plog_error(plugin,
"setting cache memory size %" PRIi64
" larger than cache disk size %" PRIi64,
cache_mem_size.v.val_int, cache_disk_size.v.val_int);
return -1;
}
// cache-sync-interval, optional
ret = neu_parse_param(setting, NULL, 1, &cache_sync_interval);
if (0 == ret) {
if (cache_sync_interval.v.val_int < NEU_MQTT_CACHE_SYNC_INTERVAL_MIN ||
NEU_MQTT_CACHE_SYNC_INTERVAL_MAX < cache_sync_interval.v.val_int) {
plog_error(plugin, "setting invalid cache sync interval: %" PRIi64,
cache_sync_interval.v.val_int);
return -1;
}
} else {
cache_sync_interval.v.val_int = NEU_MQTT_CACHE_SYNC_INTERVAL_DEFAULT;
}
config->cache = true;
config->cache_mem_size = cache_mem_size.v.val_int * MB;
config->cache_disk_size = cache_disk_size.v.val_int * MB;
config->cache_sync_interval = cache_sync_interval.v.val_int;
return 0;
}
static int parse_ssl_params(neu_plugin_t *plugin, const char *setting,
neu_json_elem_t *ca, neu_json_elem_t *cert,
neu_json_elem_t *key)
{
// ca, required
int ret = neu_parse_param(setting, NULL, 1, ca);
if (0 != ret) {
plog_notice(plugin, "setting no ca");
return -1;
}
if (0 != decode_b64_param(plugin, ca)) {
return -1;
}
// cert, required
int ret = neu_parse_param(setting, NULL, 1, cert);
ret = neu_parse_param(setting, NULL, 1, cert);
if (0 != ret) {
plog_notice(plugin, "setting no cert");
return -1;
@@ -125,24 +212,19 @@ static int azure_parse_config(neu_plugin_t *plugin, const char *setting,
password.v.val_str = strdup("");
}
// ca, required for TLS (SSL is always enabled)
ret = neu_parse_param(setting, NULL, 1, &ca);
if (0 != ret) {
plog_error(plugin, "setting no ca");
goto error;
}
if (0 != decode_b64_param(plugin, &ca)) {
goto error;
}
if (AUTH_CERT == auth.v.val_int) {
ret = parse_ssl_params(plugin, setting, &cert, &key);
ret = parse_ssl_params(plugin, setting, &ca, &cert, &key);
if (0 != ret) {
plog_error(plugin, "setting certificates fail");
goto error;
}
}
// offline cache
if (0 != parse_cache_params(plugin, setting, config)) {
goto error;
}
// write request topic
if (0 > neu_asprintf(&write_req_topic, "devices/%s/messages/devicebound/#",
client_id.v.val_str)) {
@@ -163,26 +245,22 @@ static int azure_parse_config(neu_plugin_t *plugin, const char *setting,
goto error;
}
config->version = NEU_MQTT_VERSION_V311;
config->client_id = client_id.v.val_str;
config->qos = qos.v.val_int;
config->format = format.v.val_int;
config->write_req_topic = write_req_topic;
config->write_resp_topic = write_resp_topic;
config->cache = false;
config->cache_mem_size = 0;
config->cache_disk_size = 0;
config->cache_sync_interval = NEU_MQTT_CACHE_SYNC_INTERVAL_DEFAULT;
config->host = host.v.val_str;
config->port = 8883;
config->username = username;
config->password = password.v.val_str;
config->ssl = true;
config->ca = ca.v.val_str;
config->cert = cert.v.val_str;
config->key = key.v.val_str;
config->upload_drv_state = false;
config->enable_topic = true;
config->version = NEU_MQTT_VERSION_V311;
config->client_id = client_id.v.val_str;
config->qos = qos.v.val_int;
config->format = format.v.val_int;
config->write_req_topic = write_req_topic;
config->write_resp_topic = write_resp_topic;
config->host = host.v.val_str;
config->port = 8883;
config->username = username;
config->password = password.v.val_str;
config->ssl = true;
config->ca = ca.v.val_str;
config->cert = cert.v.val_str;
config->key = key.v.val_str;
config->upload_drv_state = false;
config->enable_topic = true;
plog_notice(plugin, "config MQTT version : %d", config->version);
plog_notice(plugin, "config client-id : %s", config->client_id);
+1 -1
View File
@@ -306,7 +306,7 @@
"name_zh": "CA 证书",
"description": "CA certificate which signs the server certificate",
"description_zh": "签发服务器证书的 CA 证书",
"attribute": "required",
"attribute": "optional",
"type": "file",
"condition": {
"field": "ssl",
+4 -4
View File
@@ -81,11 +81,11 @@ static int parse_ssl_params(neu_plugin_t *plugin, const char *setting,
return 0;
}
// ca, required when ssl enabled
// ca, optional
ret = neu_parse_param(setting, NULL, 1, ca);
if (0 != ret) {
plog_error(plugin, "setting no ca, required when ssl enabled");
return -1;
plog_notice(plugin, "setting no ca");
return 0;
}
if (0 != decode_b64_param(plugin, ca)) {
@@ -103,7 +103,7 @@ static int parse_ssl_params(neu_plugin_t *plugin, const char *setting,
return -1;
}
// key, required if cert is set
// key, required if cert enable
ret = parse_b64_param(plugin, setting, key);
if (0 != ret) {
return -1;
+4 -44
View File
@@ -379,8 +379,7 @@ static int json_value_to_tag_value(union neu_json_value *req,
value->value.strs.strs =
calloc(req->val_array_str.length, sizeof(char *));
for (size_t i = 0; i < req->val_array_str.length; i++) {
const char *s = req->val_array_str.p_strs[i];
value->value.strs.strs[i] = strdup(s ? s : "");
value->value.strs.strs[i] = strdup(req->val_array_str.p_strs[i]);
}
break;
case NEU_JSON_OBJECT:
@@ -583,13 +582,6 @@ void handle_write_req(neu_mqtt_qos_e qos, const char *topic,
}
for (size_t i = 0; i < wr->n_tags; i++) {
if (wr->tags[i]->name == NULL ||
wr->tags[i]->item_case != MODEL__DATA_ITEM__ITEM_VALUE ||
wr->tags[i]->value == NULL) {
plog_error(plugin, "write request tag missing name/value");
model__write_request__free_unpacked(wr, NULL);
return;
}
if (strlen(wr->tags[i]->name) >= NEU_TAG_NAME_LEN) {
plog_error(plugin, "tag name too long");
model__write_request__free_unpacked(wr, NULL);
@@ -607,12 +599,7 @@ void handle_write_req(neu_mqtt_qos_e qos, const char *topic,
}
}
mqtt = calloc(1, sizeof(neu_json_mqtt_t));
if (NULL == mqtt) {
plog_error(plugin, "calloc mqtt fail");
model__write_request__free_unpacked(wr, NULL);
return;
}
mqtt = calloc(1, sizeof(neu_json_mqtt_t));
mqtt->uuid = strdup(wr->uuid);
mqtt->payload = NULL;
mqtt->traceparent = NULL;
@@ -628,14 +615,6 @@ void handle_write_req(neu_mqtt_qos_e qos, const char *topic,
cmd.n_tag = wr->n_tags;
if (cmd.n_tag > 0) {
cmd.tags = calloc(cmd.n_tag, sizeof(neu_resp_tag_value_t));
if (NULL == cmd.tags) {
plog_error(plugin, "calloc cmd.tags fail");
free(cmd.driver);
free(cmd.group);
neu_json_decode_mqtt_req_free(mqtt);
model__write_request__free_unpacked(wr, NULL);
return;
}
}
for (int i = 0; i < cmd.n_tag; i++) {
@@ -946,12 +925,7 @@ void handle_driver_action_req(neu_mqtt_qos_e qos, const char *topic,
return;
}
mqtt = calloc(1, sizeof(neu_json_mqtt_t));
if (NULL == mqtt) {
plog_error(plugin, "calloc mqtt fail");
model__driver_action_request__free_unpacked(dar, NULL);
return;
}
mqtt = calloc(1, sizeof(neu_json_mqtt_t));
mqtt->uuid = strdup(dar->uuid);
mqtt->payload = NULL;
mqtt->traceparent = NULL;
@@ -1025,12 +999,7 @@ void handle_read_req(neu_mqtt_qos_e qos, const char *topic,
neu_reqresp_head_t header = { 0 };
mqtt = calloc(1, sizeof(neu_json_mqtt_t));
if (NULL == mqtt) {
plog_error(plugin, "calloc mqtt fail");
model__read_request__free_unpacked(read_req, NULL);
return;
}
mqtt = calloc(1, sizeof(neu_json_mqtt_t));
mqtt->uuid = strdup(read_req->uuid);
mqtt->payload = NULL;
mqtt->traceparent = NULL;
@@ -1047,14 +1016,6 @@ void handle_read_req(neu_mqtt_qos_e qos, const char *topic,
cmd.n_tag = read_req->n_tags;
if (cmd.n_tag > 0) {
cmd.tags = calloc(cmd.n_tag, sizeof(char *));
if (NULL == cmd.tags) {
plog_error(plugin, "calloc cmd.tags fail");
free(cmd.driver);
free(cmd.group);
neu_json_decode_mqtt_req_free(mqtt);
model__read_request__free_unpacked(read_req, NULL);
return;
}
}
for (int i = 0; i < cmd.n_tag; i++) {
cmd.tags[i] = strdup(read_req->tags[i]);
@@ -1063,7 +1024,6 @@ void handle_read_req(neu_mqtt_qos_e qos, const char *topic,
if (0 != neu_plugin_op(plugin, header, &cmd)) {
neu_req_read_group_fini(&cmd);
plog_error(plugin, "neu_plugin_op(NEU_REQ_READ_GROUP) fail");
model__read_request__free_unpacked(read_req, NULL);
return;
}
+13 -8
View File
@@ -98,6 +98,10 @@ static void connect_cb(void *data)
neu_plugin_t *plugin = data;
plugin->common.link_state = NEU_NODE_LINK_STATE_CONNECTED;
plog_notice(plugin, "plugin `%s` connected", neu_plugin_module.module_name);
if (plugin->heartbeat_timer) {
heartbeat_timer_cb(plugin);
}
}
static void disconnect_cb(void *data)
@@ -622,12 +626,13 @@ int mqtt_plugin_request(neu_plugin_t *plugin, neu_reqresp_head_t *head,
}
const neu_plugin_intf_funs_t mqtt_plugin_intf_funs = {
.open = mqtt_plugin_open,
.close = mqtt_plugin_close,
.init = mqtt_plugin_init,
.uninit = mqtt_plugin_uninit,
.start = mqtt_plugin_start,
.stop = mqtt_plugin_stop,
.setting = mqtt_plugin_config,
.request = mqtt_plugin_request,
.open = mqtt_plugin_open,
.close = mqtt_plugin_close,
.init = mqtt_plugin_init,
.uninit = mqtt_plugin_uninit,
.start = mqtt_plugin_start,
.stop = mqtt_plugin_stop,
.setting = mqtt_plugin_config,
.request = mqtt_plugin_request,
.try_connect = NULL,
};
+92
View File
@@ -37,6 +37,46 @@
#include "utils/utextend.h"
#include "utils/uthash.h"
static int tags_length(const char *tags)
{
int length = 0;
char *tag = NULL;
char *tmp = strdup(tags);
char *save = NULL;
tag = strtok_r(tmp, ",", &save);
while (tag != NULL) {
length++;
tag = strtok_r(NULL, ",", &save);
}
free(tmp);
return length;
}
/*
'!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '+', '=', '[', ']', '{',
'}',
';', ':', '\'', '"', ',', '<', '>', '?', '\\', '|', '~', '`', '\t',
'\n',
*/
static bool tags_check_symbol(const char *tags)
{
for (size_t i = 0; i < strlen(tags); i++) {
char c = tags[i];
if (c == '!' || c == '@' || c == '#' || c == '$' || c == '%' ||
c == '^' || c == '&' || c == '*' || c == '(' || c == ')' ||
c == '-' || c == '+' || c == '=' || c == '[' || c == ']' ||
c == '{' || c == '}' || c == ';' || c == ':' || c == '\'' ||
c == '"' || c == '<' || c == '>' || c == '?' || c == '\\' ||
c == '|' || c == '~' || c == '`' || c == '\t' || c == '\n') {
return false;
}
}
return true;
}
void handle_add_adapter(nng_aio *aio)
{
neu_plugin_t *plugin = neu_rest_get_plugin();
@@ -45,6 +85,14 @@ void handle_add_adapter(nng_aio *aio)
aio, neu_json_add_node_req_t, neu_json_decode_add_node_req, {
if (strlen(req->name) >= NEU_NODE_NAME_LEN) {
CHECK_NODE_NAME_LENGTH_ERR;
} else if (req->tags != NULL && !tags_check_symbol(req->tags)) {
NEU_JSON_RESPONSE_ERROR(NEU_ERR_NODE_TAGS_INVALID, {
neu_http_response(aio, error_code.error, result_error);
});
} else if (req->tags != NULL && tags_length(req->tags) > 5) {
NEU_JSON_RESPONSE_ERROR(NEU_ERR_NODE_TAGS_TOO_MANY, {
neu_http_response(aio, error_code.error, result_error);
});
} else if (strcmp(req->name, "monitor") == 0 ||
strcmp(req->name, "DataStorage") == 0) {
CHECK_NODE_SINGLE_ERR;
@@ -59,7 +107,9 @@ void handle_add_adapter(nng_aio *aio)
strncpy(cmd.node, req->name, NEU_NODE_NAME_LEN - 1);
strncpy(cmd.plugin, req->plugin, NEU_PLUGIN_NAME_LEN - 1);
cmd.setting = req->setting;
cmd.tags = req->tags;
req->setting = NULL; // moved
req->tags = NULL; // moved
ret = neu_plugin_op(plugin, header, &cmd);
if (ret != 0) {
neu_req_add_node_fini(&cmd);
@@ -236,6 +286,7 @@ void handle_get_adapter_resp(nng_aio *aio, neu_resp_get_node_t *nodes)
nodes_res.nodes[index].name = info->node;
nodes_res.nodes[index].plugin = info->plugin;
nodes_res.nodes[index].tags = info->tags;
nodes_res.nodes[index].support_import_tags =
neu_plugin_support_import_tags_simple(info->plugin);
}
@@ -502,6 +553,12 @@ static int send_drivers(nng_aio *aio, neu_json_drivers_req_t *req)
ret = NEU_ERR_NODE_NAME_TOO_LONG;
goto check_end;
}
if (driver->node.tags != NULL &&
strlen(driver->node.tags) >= NEU_NODE_TAGS_LEN) {
ret = NEU_ERR_NODE_TAGS_TOO_LONG;
goto check_end;
}
if (strlen(driver->node.plugin) >= NEU_PLUGIN_NAME_LEN) {
ret = NEU_ERR_PLUGIN_NAME_TOO_LONG;
goto check_end;
@@ -593,9 +650,11 @@ check_end:
cmd.drivers[i].plugin = driver->node.plugin;
cmd.drivers[i].setting = driver->node.setting;
cmd.drivers[i].n_group = driver->gtags.len;
cmd.drivers[i].tags = driver->node.tags;
driver->node.name = NULL; // moved
driver->node.plugin = NULL; // moved
driver->node.setting = NULL; // moved
driver->node.tags = NULL; // moved
cmd.n_driver += 1;
ret = json_to_gdatatags(&driver->gtags, &cmd.drivers[i].groups);
@@ -623,4 +682,37 @@ void handle_put_drivers(nng_aio *aio)
ret, { neu_http_response(aio, ret, result_error); });
}
})
}
void handle_put_node_tag(nng_aio *aio)
{
neu_plugin_t *plugin = neu_rest_get_plugin();
NEU_PROCESS_HTTP_REQUEST_VALIDATE_JWT(
aio, neu_json_update_node_tag_req_t,
neu_json_decode_update_node_tag_req, {
if (req->tags != NULL && !tags_check_symbol(req->tags)) {
NEU_JSON_RESPONSE_ERROR(NEU_ERR_NODE_TAGS_INVALID, {
neu_http_response(aio, error_code.error, result_error);
});
} else if (req->tags != NULL && tags_length(req->tags) > 5) {
NEU_JSON_RESPONSE_ERROR(NEU_ERR_NODE_TAGS_TOO_MANY, {
neu_http_response(aio, error_code.error, result_error);
});
neu_json_decode_update_node_tag_req_free(req);
} else {
neu_reqresp_head_t header = { 0 };
neu_req_update_node_tag_t cmd = { 0 };
header.ctx = aio;
header.type = NEU_REQ_UPDATE_NODE_TAG;
strncpy(cmd.node, req->name, NEU_NODE_NAME_LEN - 1);
strncpy(cmd.tags, req->tags, NEU_NODE_TAGS_LEN - 1);
int ret = neu_plugin_op(plugin, header, &cmd);
if (ret != 0) {
NEU_JSON_RESPONSE_ERROR(NEU_ERR_IS_BUSY, {
neu_http_response(aio, NEU_ERR_IS_BUSY, result_error);
});
}
}
})
}
+1
View File
@@ -41,5 +41,6 @@ void handle_get_nodes_state_resp(nng_aio * aio,
neu_resp_get_nodes_state_t *states);
void handle_put_drivers(nng_aio *aio);
void handle_put_node_tag(nng_aio *aio);
#endif
+1 -3
View File
@@ -872,9 +872,7 @@ void handle_get_server_security_policy_resp(
if (resp->policy_name != NULL && strlen(resp->policy_name) > 0) {
// Split policy names by comma
char *token = strtok(resp->policy_name, ",");
while (token != NULL &&
policy_count <
(int) (sizeof(policy_names) / sizeof(policy_names[0]))) {
while (token != NULL) {
policy_names[policy_count] = strdup(token);
policy_count++;
token = strtok(NULL, ",");
+83
View File
@@ -95,6 +95,11 @@ void handle_add_tags(nng_aio *aio)
} else {
cmd.tags[i].description = strdup("");
}
if (req->tags[i].unit == NULL) {
cmd.tags[i].unit = strdup("");
} else {
cmd.tags[i].unit = strdup(req->tags[i].unit);
}
}
int ret = neu_plugin_op(plugin, header, &cmd);
@@ -217,6 +222,12 @@ void handle_add_gtags(nng_aio *aio)
} else {
cmd.groups[i].tags[j].description = strdup("");
}
if (req->groups[i].tags[j].unit == NULL) {
cmd.groups[i].tags[j].unit = strdup("");
} else {
cmd.groups[i].tags[j].unit =
strdup(req->groups[i].tags[j].unit);
}
}
}
@@ -383,6 +394,11 @@ void handle_update_tags(nng_aio *aio)
} else {
cmd.tags[i].description = strdup("");
}
if (req->tags[i].unit == NULL) {
cmd.tags[i].unit = strdup("");
} else {
cmd.tags[i].unit = strdup(req->tags[i].unit);
}
}
ret = neu_plugin_op(plugin, header, &cmd);
@@ -399,6 +415,72 @@ void handle_update_tags_resp(nng_aio *aio, neu_resp_update_tag_t *resp)
handle_add_tags_resp(aio, resp);
}
void handle_rename_tag(nng_aio *aio)
{
neu_plugin_t *plugin = neu_rest_get_plugin();
NEU_PROCESS_HTTP_REQUEST_VALIDATE_JWT(
aio, neu_json_rename_tag_req_t, neu_json_decode_rename_tag_req, {
int ret = 0;
neu_reqresp_head_t header = { 0 };
neu_req_rename_tag_t cmd = { 0 };
int err_type;
header.ctx = aio;
header.type = NEU_REQ_RENAME_TAG;
header.otel_trace_type = NEU_OTEL_TRACE_TYPE_REST_COMM;
if (strlen(req->node) >= NEU_NODE_NAME_LEN) {
err_type = NEU_ERR_NODE_NAME_TOO_LONG;
goto error;
}
if (strlen(req->group) >= NEU_GROUP_NAME_LEN) {
err_type = NEU_ERR_GROUP_NAME_TOO_LONG;
goto error;
}
if (strlen(req->old_name) == 0 || strlen(req->new_name) == 0) {
err_type = NEU_ERR_PARAM_IS_WRONG;
goto error;
}
if (strlen(req->old_name) >= NEU_TAG_NAME_LEN) {
err_type = NEU_ERR_TAG_NAME_TOO_LONG;
goto error;
}
if (strlen(req->new_name) >= NEU_TAG_NAME_LEN) {
err_type = NEU_ERR_TAG_NAME_TOO_LONG;
goto error;
}
strncpy(cmd.driver, req->node, NEU_NODE_NAME_LEN - 1);
strncpy(cmd.group, req->group, NEU_GROUP_NAME_LEN - 1);
strncpy(cmd.old_name, req->old_name, NEU_TAG_NAME_LEN - 1);
strncpy(cmd.new_name, req->new_name, NEU_TAG_NAME_LEN - 1);
ret = neu_plugin_op(plugin, header, &cmd);
if (ret != 0) {
NEU_JSON_RESPONSE_ERROR(NEU_ERR_IS_BUSY, {
neu_http_response(aio, NEU_ERR_IS_BUSY, result_error);
});
}
goto success;
error:
NEU_JSON_RESPONSE_ERROR(
err_type, { neu_http_response(aio, err_type, result_error); });
success:;
})
}
void handle_rename_tag_resp(nng_aio *aio, neu_resp_rename_tag_t *resp)
{
NEU_JSON_RESPONSE_ERROR(
resp->error, { neu_http_response(aio, resp->error, result_error); });
}
void handle_get_tags(nng_aio *aio)
{
neu_plugin_t * plugin = neu_rest_get_plugin();
@@ -464,6 +546,7 @@ void handle_get_tags_resp(nng_aio *aio, neu_resp_get_tag_t *tags)
tags_res.tags[index].decimal = tag->decimal;
tags_res.tags[index].bias = tag->bias;
tags_res.tags[index].t = NEU_JSON_UNDEFINE;
tags_res.tags[index].unit = tag->unit;
}
neu_json_encode_by_fn(&tags_res, neu_json_encode_get_tags_resp, &result);
+2
View File
@@ -33,6 +33,8 @@ void handle_import_tags_resp(nng_aio *aio, neu_resp_add_tag_t *resp);
void handle_del_tags(nng_aio *aio);
void handle_update_tags(nng_aio *aio);
void handle_update_tags_resp(nng_aio *aio, neu_resp_update_tag_t *resp);
void handle_rename_tag(nng_aio *aio);
void handle_rename_tag_resp(nng_aio *aio, neu_resp_rename_tag_t *resp);
void handle_get_tags(nng_aio *aio);
void handle_get_tags_resp(nng_aio *aio, neu_resp_get_tag_t *tags);
+733 -3
View File
@@ -44,6 +44,8 @@ typedef enum {
GET_GLOBAL,
PUT_GLOBAL,
GET_DRIVERS,
GET_APPS,
PUT_APPS,
} context_type_e;
typedef enum {
@@ -58,8 +60,11 @@ typedef enum {
STATE_DEL_APP,
STATE_DEL_DRIVER,
STATE_ADD_NODE,
STATE_UPDATE_DEFAULT_APP_TAGS,
STATE_ADD_GROUP,
STATE_ADD_TAG,
STATE_GET_APP_SUBS,
STATE_UNSUB_APP,
STATE_ADD_SUBSCRIPTION,
STATE_ADD_SETTING,
STATE_END,
@@ -79,14 +84,19 @@ typedef struct {
UT_array * groups;
void * iter;
neu_json_driver_array_t jdrivers; // get drivers
char * names; //
neu_strset_t filter; //
neu_json_app_array_t japps; // get apps
char * names;
neu_strset_t filter;
};
// put
struct {
neu_json_global_config_req_t *req;
neu_json_apps_req_t * apps_req;
UT_array * nodes;
size_t idx;
size_t sub_idx;
size_t unsub_idx;
UT_array * default_app_subs;
};
};
} context_t;
@@ -117,6 +127,7 @@ static int add_group(context_t * ctx,
static int add_tag(context_t *ctx, neu_json_add_tags_req_t *data);
static int add_subscription(context_t *ctx, neu_json_subscribe_req_t *data);
static int add_setting(context_t *ctx, neu_json_node_setting_req_t *data);
static int add_app_tags(context_t *ctx, neu_json_app_t *app);
static inline bool is_static_node(const char *name, const char *plugin)
{
@@ -134,7 +145,8 @@ static context_t *context_new(nng_aio *aio, context_type_e t)
ctx->type = t;
ctx->state = STATE_START;
if (PUT_GLOBAL != ctx->type && !(ctx->json = neu_json_encode_new())) {
if (PUT_GLOBAL != ctx->type && PUT_APPS != ctx->type &&
!(ctx->json = neu_json_encode_new())) {
free(ctx);
return NULL;
}
@@ -164,6 +176,32 @@ static void context_free(context_t *ctx)
utarray_free(ctx->nodes);
}
neu_json_decode_global_config_req_free(ctx->req);
} else if (GET_APPS == ctx->type) {
neu_json_encode_free(ctx->json);
if (ctx->apps) {
utarray_free(ctx->apps);
}
free(ctx->names);
neu_strset_free(&ctx->filter);
for (int i = 0; i < ctx->japps.n_app; ++i) {
neu_json_app_t *app = &ctx->japps.apps[i];
free(app->node.setting);
for (int j = 0; j < app->subscriptions.n_group; ++j) {
free(app->subscriptions.groups[j].driver);
free(app->subscriptions.groups[j].group);
free(app->subscriptions.groups[j].params);
}
free(app->subscriptions.groups);
}
free(ctx->japps.apps);
} else if (PUT_APPS == ctx->type) {
if (ctx->nodes) {
utarray_free(ctx->nodes);
}
if (ctx->default_app_subs) {
utarray_free(ctx->default_app_subs);
}
neu_json_decode_apps_req_free(ctx->apps_req);
} else {
neu_json_encode_free(ctx->json);
if (ctx->drivers) {
@@ -558,12 +596,295 @@ static void get_drivers_context_next(context_t *ctx, neu_reqresp_type_e type,
}
}
static int get_apps_resp(context_t *ctx, neu_resp_get_node_t *resp);
static int get_app_setting_resp(context_t * ctx,
neu_resp_get_node_setting_t *setting);
static int get_app_subscriptions_resp(context_t * ctx,
neu_resp_get_subscribe_group_t *groups);
static int del_app_node(context_t *ctx, neu_json_app_t *app);
static int add_app_node(context_t *ctx, neu_json_app_t *app);
static int add_app_setting(context_t *ctx, neu_json_app_t *app);
static int add_app_subscription(context_t *ctx, neu_json_app_t *app,
neu_json_app_group_t *grp);
static int get_default_app_subs(context_t *ctx, neu_json_app_t *app);
static int get_default_app_subs_resp(context_t * ctx,
neu_resp_get_subscribe_group_t *groups);
static int unsub_app(context_t *ctx, neu_resp_subscribe_info_t *info);
static inline bool is_default_app_node(const char *name, const char *plugin)
{
return (0 == strcmp("DataStorage", name) &&
0 == strcmp("Datalayers", plugin)) ||
(0 == strcmp("DataProcessing", name) &&
0 == strcmp("eKuiper", plugin)) ||
(0 == strcmp("monitor", name) && 0 == strcmp("Monitor", plugin));
}
static void get_apps_context_next(context_t *ctx, neu_reqresp_type_e type,
void *data)
{
char *result = NULL;
switch (ctx->state) {
case STATE_START:
NEXT(ctx, get_nodes, NEU_NA_TYPE_APP);
ctx->state = STATE_GET_APP;
break;
case STATE_GET_APP:
NEXT(ctx, get_apps_resp, data);
ctx->iter = NULL;
ctx->state = STATE_GET_APP_SETTING;
// fall through
case STATE_GET_APP_SETTING:
if (NULL != ctx->iter && NEU_RESP_ERROR != type) {
NEXT(ctx, get_app_setting_resp, data);
}
ctx->iter = utarray_next(ctx->apps, ctx->iter);
if (ctx->iter) {
NEXT(ctx, get_setting, ctx->iter);
break;
}
ctx->iter = NULL;
ctx->state = STATE_GET_SUBSCRIPTION;
// fall through
case STATE_GET_SUBSCRIPTION:
if (NULL != ctx->iter && NEU_RESP_ERROR != type) {
NEXT(ctx, get_app_subscriptions_resp, data);
}
ctx->iter = utarray_next(ctx->apps, ctx->iter);
if (ctx->iter) {
NEXT(ctx, get_subscriptions, ctx->iter);
break;
}
if (0 != neu_json_encode_apps_req(ctx->json, &ctx->japps)) {
ctx->error = NEU_ERR_EINTERNAL;
}
ctx->state = STATE_END;
break;
default:
ctx->error = NEU_ERR_EINTERNAL;
ctx->state = STATE_END;
break;
}
if (STATE_END == ctx->state) {
if (ctx->error) {
NEU_JSON_RESPONSE_ERROR(ctx->error, {
neu_http_response(ctx->aio, ctx->error, result_error);
});
} else {
neu_json_encode(ctx->json, &result);
neu_http_response_file(ctx->aio, result, strlen(result),
"attachment; filename=\"apps.json\"");
free(result);
}
context_free(ctx);
}
}
static void put_apps_context_next(context_t *ctx, neu_reqresp_type_e type,
void *data)
{
(void) type;
switch (ctx->state) {
case STATE_START:
ctx->idx = 0;
ctx->sub_idx = 0;
ctx->unsub_idx = 0;
ctx->state = STATE_DEL_APP;
// fall through
case STATE_DEL_APP: {
neu_resp_error_t * err = data;
neu_json_apps_req_t *apps = ctx->apps_req;
if (ctx->idx > 0 && 0 != err->error &&
NEU_ERR_NODE_NOT_EXIST != err->error) {
ctx->error = err->error;
ctx->state = STATE_END;
break;
}
for (; ctx->idx < (size_t) apps->n_app; ++ctx->idx) {
neu_json_app_t *app = &apps->apps[ctx->idx];
if (!is_default_app_node(app->node.name, app->node.plugin)) {
NEXT(ctx, del_app_node, app);
++ctx->idx;
goto done;
}
}
ctx->idx = 0;
ctx->state = STATE_ADD_NODE;
}
// fall through
case STATE_ADD_NODE: {
neu_resp_error_t * err = data;
neu_json_apps_req_t *apps = ctx->apps_req;
if (ctx->idx > 0 && 0 != err->error) {
ctx->error = err->error;
ctx->state = STATE_END;
break;
}
for (; ctx->idx < (size_t) apps->n_app; ++ctx->idx) {
neu_json_app_t *app = &apps->apps[ctx->idx];
if (is_default_app_node(app->node.name, app->node.plugin)) {
if (NULL == app->node.setting) {
continue;
}
NEXT(ctx, add_app_setting, app);
} else {
NEXT(ctx, add_app_node, app);
}
++ctx->idx;
goto done;
}
// After adding nodes, get subscriptions for default apps to unsub
ctx->idx = 0;
ctx->unsub_idx = 0;
ctx->state = STATE_UPDATE_DEFAULT_APP_TAGS;
}
// fall through
case STATE_UPDATE_DEFAULT_APP_TAGS: {
neu_resp_error_t * err = data;
neu_json_apps_req_t *apps = ctx->apps_req;
if (ctx->idx > 0 && 0 != err->error) {
ctx->error = err->error;
ctx->state = STATE_END;
break;
}
for (; ctx->idx < (size_t) apps->n_app; ++ctx->idx) {
neu_json_app_t *app = &apps->apps[ctx->idx];
NEXT(ctx, add_app_tags, app);
++ctx->idx;
goto done;
}
ctx->idx = 0;
ctx->unsub_idx = 0;
ctx->state = STATE_GET_APP_SUBS;
}
// fall through
case STATE_GET_APP_SUBS: {
neu_json_apps_req_t *apps = ctx->apps_req;
if (NEU_RESP_GET_SUBSCRIBE_GROUP == type && data) {
NEXT(ctx, get_default_app_subs_resp, data);
if (ctx->default_app_subs &&
utarray_len(ctx->default_app_subs) > 0) {
ctx->unsub_idx = 0;
ctx->state = STATE_UNSUB_APP;
// fall through to STATE_UNSUB_APP
}
}
if (STATE_UNSUB_APP != ctx->state) {
// Find next default app to get subscriptions
for (; ctx->idx < (size_t) apps->n_app; ++ctx->idx) {
neu_json_app_t *app = &apps->apps[ctx->idx];
if (is_default_app_node(app->node.name, app->node.plugin) &&
app->subscriptions.n_group > 0) {
NEXT(ctx, get_default_app_subs, app);
++ctx->idx;
goto done;
}
}
// No more default apps, proceed to add subscriptions
ctx->idx = 0;
ctx->sub_idx = 0;
ctx->state = STATE_ADD_SUBSCRIPTION;
}
}
if (STATE_UNSUB_APP != ctx->state &&
STATE_ADD_SUBSCRIPTION != ctx->state) {
break;
}
if (STATE_UNSUB_APP == ctx->state) {
// fall through
case STATE_UNSUB_APP: {
if (ctx->default_app_subs) {
size_t len = utarray_len(ctx->default_app_subs);
if (ctx->unsub_idx < len) {
neu_resp_subscribe_info_t *info =
(neu_resp_subscribe_info_t *) utarray_eltptr(
ctx->default_app_subs, ctx->unsub_idx);
NEXT(ctx, unsub_app, info);
++ctx->unsub_idx;
goto done;
}
utarray_free(ctx->default_app_subs);
ctx->default_app_subs = NULL;
}
ctx->state = STATE_GET_APP_SUBS;
put_apps_context_next(ctx, type, data);
return;
}
}
// fall through
case STATE_ADD_SUBSCRIPTION: {
neu_resp_error_t * err = data;
neu_json_apps_req_t *apps = ctx->apps_req;
if (ctx->sub_idx > 0 && 0 != err->error) {
ctx->error = err->error;
ctx->state = STATE_END;
break;
}
while (ctx->idx < (size_t) apps->n_app) {
neu_json_app_t *app = &apps->apps[ctx->idx];
if (ctx->sub_idx < (size_t) app->subscriptions.n_group) {
NEXT(ctx, add_app_subscription, app,
&app->subscriptions.groups[ctx->sub_idx]);
++ctx->sub_idx;
goto done;
}
++ctx->idx;
ctx->sub_idx = 0;
}
ctx->state = STATE_END;
break;
}
default:
ctx->error = NEU_ERR_EINTERNAL;
ctx->state = STATE_END;
break;
}
done:
if (STATE_END == ctx->state) {
NEU_JSON_RESPONSE_ERROR(ctx->error, {
neu_http_response(ctx->aio, ctx->error, result_error);
});
context_free(ctx);
}
}
static void context_next(context_t *ctx, neu_reqresp_type_e type, void *data)
{
if (GET_GLOBAL == ctx->type) {
get_global_context_next(ctx, type, data);
} else if (PUT_GLOBAL == ctx->type) {
put_global_context_next(ctx, type, data);
} else if (GET_APPS == ctx->type) {
get_apps_context_next(ctx, type, data);
} else if (PUT_APPS == ctx->type) {
put_apps_context_next(ctx, type, data);
} else {
get_drivers_context_next(ctx, type, data);
}
@@ -654,6 +975,7 @@ static int get_drivers_resp(context_t *ctx, neu_resp_get_node_t *resp)
neu_resp_node_info_t *info = utarray_eltptr(nodes, i);
drivers.drivers[i].node.name = info->node;
drivers.drivers[i].node.plugin = info->plugin;
drivers.drivers[i].node.tags = info->tags;
}
end:
@@ -1328,6 +1650,414 @@ error:
context_free(ctx);
}
static int get_apps_resp(context_t *ctx, neu_resp_get_node_t *resp)
{
int rv = 0;
neu_json_app_array_t apps = { 0 };
UT_array * nodes = resp->nodes;
if (ctx->filter) {
utarray_new(nodes, &resp->nodes->icd);
utarray_foreach(resp->nodes, neu_resp_node_info_t *, info)
{
if (neu_strset_test(&ctx->filter, info->node)) {
utarray_push_back(nodes, info);
}
}
utarray_free(resp->nodes);
}
apps.n_app = utarray_len(nodes);
if (apps.n_app > 0) {
apps.apps = calloc(apps.n_app, sizeof(apps.apps[0]));
if (NULL == apps.apps) {
rv = NEU_ERR_EINTERNAL;
goto end;
}
}
for (unsigned i = 0; i < utarray_len(nodes); ++i) {
neu_resp_node_info_t *info = utarray_eltptr(nodes, i);
apps.apps[i].node.name = info->node;
apps.apps[i].node.plugin = info->plugin;
apps.apps[i].node.tags = info->tags;
}
end:
ctx->apps = nodes;
ctx->japps = apps;
return rv;
}
static int get_app_setting_resp(context_t * ctx,
neu_resp_get_node_setting_t *setting)
{
int i = utarray_eltidx(ctx->apps, ctx->iter);
if (i >= ctx->japps.n_app ||
0 != strcmp(ctx->japps.apps[i].node.name, setting->node)) {
if (setting) {
free(setting->setting);
}
return NEU_ERR_NODE_NOT_EXIST;
}
ctx->japps.apps[i].node.setting = setting->setting;
return 0;
}
static int get_app_subscriptions_resp(context_t * ctx,
neu_resp_get_subscribe_group_t *groups)
{
int rv = 0;
int i = utarray_eltidx(ctx->apps, ctx->iter);
if (i >= ctx->japps.n_app) {
rv = NEU_ERR_NODE_NOT_EXIST;
goto end;
}
if (NULL == groups || NULL == groups->groups ||
0 == utarray_len(groups->groups)) {
ctx->japps.apps[i].subscriptions.n_group = 0;
ctx->japps.apps[i].subscriptions.groups = NULL;
goto end;
}
int n_grp = utarray_len(groups->groups);
neu_json_app_group_t *grps = calloc(n_grp, sizeof(*grps));
if (NULL == grps) {
rv = NEU_ERR_EINTERNAL;
goto end;
}
utarray_foreach(groups->groups, neu_resp_subscribe_info_t *, group)
{
int index = utarray_eltidx(groups->groups, group);
grps[index].driver = strdup(group->driver);
grps[index].group = strdup(group->group);
grps[index].params = group->params ? strdup(group->params) : NULL;
grps[index].static_tags =
group->static_tags ? strdup(group->static_tags) : NULL;
if (NULL == grps[index].driver || NULL == grps[index].group) {
for (int j = 0; j <= index; ++j) {
free(grps[j].driver);
free(grps[j].group);
free(grps[j].params);
if (grps[j].static_tags) {
free(grps[j].static_tags);
}
}
free(grps);
rv = NEU_ERR_EINTERNAL;
goto end;
}
}
ctx->japps.apps[i].subscriptions.n_group = n_grp;
ctx->japps.apps[i].subscriptions.groups = grps;
end:
if (groups && groups->groups) {
utarray_free(groups->groups);
}
return rv;
}
static int del_app_node(context_t *ctx, neu_json_app_t *app)
{
neu_plugin_t *plugin = neu_rest_get_plugin();
neu_reqresp_head_t header = {
.type = NEU_REQ_DEL_NODE,
.ctx = ctx->aio,
.otel_trace_type = NEU_OTEL_TRACE_TYPE_REST_COMM,
};
neu_req_del_node_t cmd = { 0 };
strncpy(cmd.node, app->node.name, NEU_NODE_NAME_LEN - 1);
if (0 != neu_plugin_op(plugin, header, &cmd)) {
return NEU_ERR_IS_BUSY;
}
return 0;
}
static int add_app_node(context_t *ctx, neu_json_app_t *app)
{
neu_plugin_t *plugin = neu_rest_get_plugin();
neu_reqresp_head_t header = {
.type = NEU_REQ_ADD_NODE,
.ctx = ctx->aio,
.otel_trace_type = NEU_OTEL_TRACE_TYPE_REST_COMM,
};
neu_req_add_node_t cmd = { 0 };
strncpy(cmd.node, app->node.name, NEU_NODE_NAME_LEN - 1);
strncpy(cmd.plugin, app->node.plugin, NEU_PLUGIN_NAME_LEN - 1);
cmd.setting = app->node.setting;
if (app->node.tags != NULL) {
cmd.tags = strdup(app->node.tags);
}
app->node.setting = NULL;
if (0 != neu_plugin_op(plugin, header, &cmd)) {
return NEU_ERR_IS_BUSY;
}
return 0;
}
static int add_app_tags(context_t *ctx, neu_json_app_t *app)
{
neu_plugin_t *plugin = neu_rest_get_plugin();
neu_reqresp_head_t header = {
.otel_trace_type = NEU_OTEL_TRACE_TYPE_REST_COMM,
.type = NEU_REQ_UPDATE_NODE_TAG,
.ctx = ctx->aio,
};
neu_req_update_node_tag_t cmd = { 0 };
strncpy(cmd.node, app->node.name, NEU_NODE_NAME_LEN - 1);
if (app->node.tags != NULL) {
strncpy(cmd.tags, app->node.tags, NEU_TAG_NAME_LEN - 1);
}
if (0 != neu_plugin_op(plugin, header, &cmd)) {
return NEU_ERR_IS_BUSY;
}
return 0;
}
static int add_app_setting(context_t *ctx, neu_json_app_t *app)
{
neu_plugin_t *plugin = neu_rest_get_plugin();
neu_reqresp_head_t header = {
.ctx = ctx->aio,
.type = NEU_REQ_NODE_SETTING,
.otel_trace_type = NEU_OTEL_TRACE_TYPE_REST_COMM,
};
neu_req_node_setting_t cmd = { 0 };
strncpy(cmd.node, app->node.name, NEU_NODE_NAME_LEN - 1);
cmd.setting = app->node.setting;
app->node.setting = NULL;
if (0 != neu_plugin_op(plugin, header, &cmd)) {
return NEU_ERR_IS_BUSY;
}
return 0;
}
static int add_app_subscription(context_t *ctx, neu_json_app_t *app,
neu_json_app_group_t *grp)
{
neu_plugin_t *plugin = neu_rest_get_plugin();
neu_reqresp_head_t header = {
.ctx = ctx->aio,
.type = NEU_REQ_SUBSCRIBE_GROUP,
.otel_trace_type = NEU_OTEL_TRACE_TYPE_REST_COMM,
};
neu_req_subscribe_t cmd = { 0 };
strncpy(cmd.app, app->node.name, NEU_NODE_NAME_LEN - 1);
strncpy(cmd.driver, grp->driver, NEU_NODE_NAME_LEN - 1);
strncpy(cmd.group, grp->group, NEU_GROUP_NAME_LEN - 1);
cmd.params = grp->params;
cmd.static_tags = grp->static_tags;
grp->params = NULL;
grp->static_tags = NULL;
if (0 != neu_plugin_op(plugin, header, &cmd)) {
return NEU_ERR_IS_BUSY;
}
return 0;
}
static int get_default_app_subs(context_t *ctx, neu_json_app_t *app)
{
neu_plugin_t *plugin = neu_rest_get_plugin();
neu_reqresp_head_t header = {
.ctx = ctx->aio,
.type = NEU_REQ_GET_SUBSCRIBE_GROUP,
.otel_trace_type = NEU_OTEL_TRACE_TYPE_REST_COMM,
};
neu_req_get_subscribe_group_t cmd = { 0 };
strncpy(cmd.app, app->node.name, NEU_NODE_NAME_LEN - 1);
if (0 != neu_plugin_op(plugin, header, &cmd)) {
return NEU_ERR_IS_BUSY;
}
return 0;
}
static int get_default_app_subs_resp(context_t * ctx,
neu_resp_get_subscribe_group_t *resp)
{
if (NULL == resp || NULL == resp->groups) {
return 0;
}
if (ctx->default_app_subs) {
utarray_free(ctx->default_app_subs);
ctx->default_app_subs = NULL;
}
size_t len = utarray_len(resp->groups);
if (len > 0) {
UT_icd icd = { sizeof(neu_resp_subscribe_info_t), NULL, NULL, NULL };
utarray_new(ctx->default_app_subs, &icd);
utarray_foreach(resp->groups, neu_resp_subscribe_info_t *, info)
{
neu_resp_subscribe_info_t copy = { 0 };
strncpy(copy.app, info->app, NEU_NODE_NAME_LEN - 1);
strncpy(copy.driver, info->driver, NEU_NODE_NAME_LEN - 1);
strncpy(copy.group, info->group, NEU_GROUP_NAME_LEN - 1);
utarray_push_back(ctx->default_app_subs, &copy);
}
}
utarray_foreach(resp->groups, neu_resp_subscribe_info_t *, info)
{
neu_resp_subscribe_info_fini(info);
}
utarray_free(resp->groups);
return 0;
}
static int unsub_app(context_t *ctx, neu_resp_subscribe_info_t *info)
{
neu_plugin_t *plugin = neu_rest_get_plugin();
neu_reqresp_head_t header = {
.ctx = ctx->aio,
.type = NEU_REQ_UNSUBSCRIBE_GROUP,
.otel_trace_type = NEU_OTEL_TRACE_TYPE_REST_COMM,
};
neu_req_unsubscribe_t cmd = { 0 };
strncpy(cmd.app, info->app, NEU_NODE_NAME_LEN - 1);
strncpy(cmd.driver, info->driver, NEU_NODE_NAME_LEN - 1);
strncpy(cmd.group, info->group, NEU_GROUP_NAME_LEN - 1);
if (0 != neu_plugin_op(plugin, header, &cmd)) {
return NEU_ERR_IS_BUSY;
}
return 0;
}
void handle_get_apps(nng_aio *aio)
{
int rv = 0;
NEU_VALIDATE_JWT(aio);
context_t *ctx = context_new(aio, GET_APPS);
if (NULL == ctx) {
rv = NEU_ERR_EINTERNAL;
goto error;
}
size_t len = 0;
const char *param = neu_http_get_param(aio, "name", &len);
if (NULL != param) {
char *names = NULL;
len += 1;
if (NULL == (names = calloc(len, 1))) {
rv = NEU_ERR_EINTERNAL;
goto error;
}
ctx->names = names;
if (neu_url_decode(param, len, names, len) < 0) {
rv = NEU_ERR_PARAM_IS_WRONG;
goto error;
}
for (char *next = NULL; names; names = next) {
if (NULL != (next = strchr(names, ','))) {
*next++ = '\0';
}
if (neu_strset_add(&ctx->filter, names) < 0) {
rv = NEU_ERR_EINTERNAL;
goto error;
}
}
}
(void) rv;
nng_aio_set_input(aio, 3, ctx);
context_next(ctx, NEU_REQ_GET_NODE, NULL);
return;
error:
NEU_JSON_RESPONSE_ERROR(rv, { neu_http_response(aio, rv, result_error); });
context_free(ctx);
}
void handle_put_apps(nng_aio *aio)
{
NEU_PROCESS_HTTP_REQUEST_VALIDATE_JWT(
aio, neu_json_apps_req_t, neu_json_decode_apps_req, {
int ret = 0;
for (int i = 0; i < req->n_app && 0 == ret; ++i) {
if (strlen(req->apps[i].node.name) >= NEU_NODE_NAME_LEN) {
ret = NEU_ERR_NODE_NAME_TOO_LONG;
break;
}
if (strlen(req->apps[i].node.plugin) >= NEU_PLUGIN_NAME_LEN) {
ret = NEU_ERR_PLUGIN_NAME_TOO_LONG;
break;
}
for (int j = 0; j < req->apps[i].subscriptions.n_group; ++j) {
neu_json_app_group_t *grp =
&req->apps[i].subscriptions.groups[j];
if (strlen(grp->driver) >= NEU_NODE_NAME_LEN) {
ret = NEU_ERR_NODE_NAME_TOO_LONG;
break;
}
if (strlen(grp->group) >= NEU_GROUP_NAME_LEN) {
ret = NEU_ERR_GROUP_NAME_TOO_LONG;
break;
}
}
}
if (ret != 0) {
NEU_JSON_RESPONSE_ERROR(
ret, { neu_http_response(aio, ret, result_error); });
} else {
context_t *ctx = context_new(aio, PUT_APPS);
if (NULL == ctx) {
NEU_JSON_RESPONSE_ERROR(NEU_ERR_EINTERNAL, {
neu_http_response(aio, NEU_ERR_EINTERNAL, result_error);
});
} else {
nng_aio_set_input(aio, 3, ctx);
ctx->apps_req = req;
req = NULL;
context_next(ctx, NEU_REQ_ADD_NODE, NULL);
}
}
});
}
void handle_global_config_resp(nng_aio *aio, neu_reqresp_type_e type,
void *data)
{
+3
View File
@@ -28,6 +28,9 @@ void handle_put_global_config(nng_aio *aio);
void handle_get_drivers(nng_aio *aio);
void handle_get_apps(nng_aio *aio);
void handle_put_apps(nng_aio *aio);
void handle_global_config_resp(nng_aio *aio, neu_reqresp_type_e type,
void *data);
+95
View File
@@ -38,11 +38,13 @@
#include "plugin_handle.h"
#include "rw_handle.h"
#include "scan_handle.h"
#include "system_handle.h"
#include "tpy_handle.h"
#include "utils/http.h"
#include "version_handle.h"
#include "handle.h"
#include "simulator_handle.h"
struct neu_rest_handle_ctx {
void *plugin;
@@ -126,6 +128,9 @@ static struct neu_http_handler cors_handler[] = {
{
.url = "/api/v2/global/drivers",
},
{
.url = "/api/v2/global/apps",
},
{
.url = "/api/v2/metrics",
},
@@ -141,6 +146,9 @@ static struct neu_http_handler cors_handler[] = {
{
.url = "/api/v2/tpy",
},
{
.url = "/api/v2/system/ctl",
},
{
.url = "/api/v2/users",
},
@@ -180,6 +188,27 @@ static struct neu_http_handler cors_handler[] = {
{
.url = "/api/v2/auth/basic/security_policies",
},
{
.url = "/api/v2/simulator/status",
},
{
.url = "/api/v2/simulator/start",
},
{
.url = "/api/v2/simulator/stop",
},
{
.url = "/api/v2/simulator/config",
},
{
.url = "/api/v2/simulator/tags",
},
{
.url = "/api/v2/simulator/export",
},
{
.url = "/api/v2/tag/rename",
},
};
static struct neu_http_handler rest_handlers[] = {
@@ -262,6 +291,12 @@ static struct neu_http_handler rest_handlers[] = {
.url = "/api/v2/tags",
.value.handler = handle_del_tags,
},
{
.method = NEU_HTTP_METHOD_PUT,
.type = NEU_HTTP_HANDLER_FUNCTION,
.url = "/api/v2/tag/rename",
.value.handler = handle_rename_tag,
},
{
.method = NEU_HTTP_METHOD_POST,
.type = NEU_HTTP_HANDLER_FUNCTION,
@@ -484,6 +519,18 @@ static struct neu_http_handler rest_handlers[] = {
.url = "/api/v2/global/drivers",
.value.handler = handle_get_drivers,
},
{
.method = NEU_HTTP_METHOD_GET,
.type = NEU_HTTP_HANDLER_FUNCTION,
.url = "/api/v2/global/apps",
.value.handler = handle_get_apps,
},
{
.method = NEU_HTTP_METHOD_PUT,
.type = NEU_HTTP_HANDLER_FUNCTION,
.url = "/api/v2/global/apps",
.value.handler = handle_put_apps,
},
{
.method = NEU_HTTP_METHOD_GET,
.type = NEU_HTTP_HANDLER_FUNCTION,
@@ -514,6 +561,12 @@ static struct neu_http_handler rest_handlers[] = {
.url = "/api/v2/otel",
.value.handler = handle_otel_get,
},
{
.method = NEU_HTTP_METHOD_POST,
.type = NEU_HTTP_HANDLER_FUNCTION,
.url = "/api/v2/system/ctl",
.value.handler = handle_system_ctl,
},
{
.method = NEU_HTTP_METHOD_GET,
.type = NEU_HTTP_HANDLER_FUNCTION,
@@ -658,6 +711,48 @@ static struct neu_http_handler rest_handlers[] = {
.url = "/api/v2/auth/basic/security_policies",
.value.handler = handle_server_security_policy,
},
{
.method = NEU_HTTP_METHOD_GET,
.type = NEU_HTTP_HANDLER_FUNCTION,
.url = "/api/v2/simulator/status",
.value.handler = handle_simulator_status,
},
{
.method = NEU_HTTP_METHOD_GET,
.type = NEU_HTTP_HANDLER_FUNCTION,
.url = "/api/v2/simulator/tags",
.value.handler = handle_simulator_list_tags,
},
{
.method = NEU_HTTP_METHOD_POST,
.type = NEU_HTTP_HANDLER_FUNCTION,
.url = "/api/v2/simulator/start",
.value.handler = handle_simulator_start,
},
{
.method = NEU_HTTP_METHOD_POST,
.type = NEU_HTTP_HANDLER_FUNCTION,
.url = "/api/v2/simulator/stop",
.value.handler = handle_simulator_stop,
},
{
.method = NEU_HTTP_METHOD_POST,
.type = NEU_HTTP_HANDLER_FUNCTION,
.url = "/api/v2/simulator/config",
.value.handler = handle_simulator_set_config,
},
{
.method = NEU_HTTP_METHOD_GET,
.type = NEU_HTTP_HANDLER_FUNCTION,
.url = "/api/v2/simulator/export",
.value.handler = handle_simulator_export,
},
{
.method = NEU_HTTP_METHOD_PUT,
.type = NEU_HTTP_HANDLER_FUNCTION,
.url = "/api/v2/node/tag",
.value.handler = handle_put_node_tag,
},
};
void neu_rest_handler(const struct neu_http_handler **handlers, uint32_t *size)
-11
View File
@@ -52,16 +52,6 @@ void handle_ping(nng_aio *aio)
void handle_login(nng_aio *aio)
{
if (disable_jwt) {
neu_json_login_resp_t login_resp = { .token = "" };
char * result = NULL;
neu_json_encode_by_fn(&login_resp, neu_json_encode_login_resp, &result);
neu_http_ok(aio, result);
free(result);
return;
}
NEU_PROCESS_HTTP_REQUEST(
aio, neu_json_login_req_t, neu_json_decode_login_req, {
neu_json_login_resp_t login_resp = { 0 };
@@ -927,7 +917,6 @@ void handle_export_db(nng_aio *aio)
size_t len = 0;
rv = read_file("/tmp/neuron.zip", &data, &len);
nlog_info("read_file /tmp/neuron.zip rv=%d, len=%zu", rv, len);
if (rv != 0) {
NEU_JSON_RESPONSE_ERROR(NEU_ERR_FILE_READ_FAILURE, {
neu_http_response(aio, error_code.error, result_error);
+2 -2
View File
@@ -41,7 +41,7 @@ void handle_add_plugin(nng_aio *aio)
header.ctx = aio;
header.type = NEU_REQ_ADD_PLUGIN;
header.otel_trace_type = NEU_OTEL_TRACE_TYPE_REST_COMM;
strncpy(cmd.library, req->library, NEU_PLUGIN_LIBRARY_LEN - 1);
strncpy(cmd.library, req->library, NEU_PLUGIN_LIBRARY_LEN);
cmd.schema_file = req->schema_file;
cmd.so_file = req->so_file;
ret = neu_plugin_op(plugin, header, &cmd);
@@ -66,7 +66,7 @@ void handle_update_plugin(nng_aio *aio)
header.ctx = aio;
header.type = NEU_REQ_UPDATE_PLUGIN;
header.otel_trace_type = NEU_OTEL_TRACE_TYPE_REST_COMM;
strncpy(cmd.library, req->library, NEU_PLUGIN_LIBRARY_LEN - 1);
strncpy(cmd.library, req->library, NEU_PLUGIN_LIBRARY_LEN);
cmd.schema_file = req->schema_file;
cmd.so_file = req->so_file;
ret = neu_plugin_op(plugin, header, &cmd);
+7
View File
@@ -280,6 +280,13 @@ static int dashb_plugin_request(neu_plugin_t * plugin,
scope, "error", ((neu_resp_update_tag_t *) data)->error);
}
break;
case NEU_RESP_RENAME_TAG:
handle_rename_tag_resp(header->ctx, (neu_resp_rename_tag_t *) data);
if (neu_otel_control_is_started() && trace) {
neu_otel_scope_add_span_attr_int(
scope, "error", ((neu_resp_rename_tag_t *) data)->error);
}
break;
case NEU_RESP_GET_TAG:
handle_get_tags_resp(header->ctx, (neu_resp_get_tag_t *) data);
if (neu_otel_control_is_started() && trace) {
+13 -9
View File
@@ -365,8 +365,8 @@ void handle_write(nng_aio *aio)
cmd.value.value.strs.strs =
calloc(req->value.val_array_str.length, sizeof(char *));
for (size_t i = 0; i < req->value.val_array_str.length; i++) {
const char *s = req->value.val_array_str.p_strs[i];
cmd.value.value.strs.strs[i] = strdup(s ? s : "");
cmd.value.value.strs.strs[i] =
strdup(req->value.val_array_str.p_strs[i]);
}
break;
case NEU_JSON_OBJECT:
@@ -588,10 +588,8 @@ void handle_write_tags(nng_aio *aio)
sizeof(char *));
for (size_t k = 0;
k < req->tags[i].value.val_array_str.length; k++) {
const char *s =
req->tags[i].value.val_array_str.p_strs[k];
cmd.tags[i].value.value.strs.strs[k] =
strdup(s ? s : "");
strdup(req->tags[i].value.val_array_str.p_strs[k]);
}
break;
case NEU_JSON_OBJECT:
@@ -734,10 +732,8 @@ static void trans(neu_json_write_gtags_req_t *req, neu_req_write_gtags_t *cmd)
for (size_t j = 0;
j < req->groups[i].tags[k].value.val_array_str.length;
j++) {
const char *s =
req->groups[i].tags[k].value.val_array_str.p_strs[j];
cmd->groups[i].tags[k].value.value.strs.strs[j] =
strdup(s ? s : "");
cmd->groups[i].tags[k].value.value.strs.strs[j] = strdup(
req->groups[i].tags[k].value.val_array_str.p_strs[j]);
}
break;
@@ -901,6 +897,12 @@ void handle_read_paginate_resp(nng_aio * aio,
strdup(tag_value->datatag.address);
new_tag_value.datatag.description =
strdup(tag_value->datatag.description);
if (tag_value->datatag.unit) {
new_tag_value.datatag.unit =
strdup(tag_value->datatag.unit);
} else {
new_tag_value.datatag.unit = strdup("");
}
utarray_push_back(filtered_tags, &new_tag_value);
}
@@ -915,6 +917,7 @@ void handle_read_paginate_resp(nng_aio * aio,
free(orig_tag_value->datatag.name);
free(orig_tag_value->datatag.address);
free(orig_tag_value->datatag.description);
free(orig_tag_value->datatag.unit);
}
}
@@ -1004,6 +1007,7 @@ void handle_read_paginate_resp(nng_aio * aio,
free(final_tag_value->datatag.name);
free(final_tag_value->datatag.address);
free(final_tag_value->datatag.description);
free(final_tag_value->datatag.unit);
}
}
+3 -1
View File
@@ -54,6 +54,8 @@ void handle_scan_tags(nng_aio *aio)
strncpy(cmd.ctx, req->ctx, NEU_VALUE_SIZE);
}
cmd.load_index = req->load_index;
if (0 != neu_plugin_op(plugin, header, &cmd)) {
NEU_JSON_RESPONSE_ERROR(NEU_ERR_IS_BUSY, {
neu_http_response(aio, NEU_ERR_IS_BUSY, result_error);
@@ -73,6 +75,6 @@ void handle_scan_tags_resp(nng_aio *aio, neu_resp_scan_tags_t *resp)
{
char *result = NULL;
neu_json_encode_by_fn(resp, neu_json_encode_scan_tags_resp, &result);
neu_http_ok(aio, result);
neu_http_response(aio, resp->error, result);
free(result);
}
+217
View File
@@ -0,0 +1,217 @@
#include <ctype.h>
#include <jansson.h>
#include <stdlib.h>
#include <string.h>
#include "handle.h"
#include "modbus_tcp_simulator.h"
#include "utils/http.h"
#include "utils/log.h"
#include "json/neu_json_fn.h"
static int extract_hold_start_from_addr(const char *addr_str,
uint16_t * out_idx0)
{
if (!addr_str || !out_idx0)
return -1;
const char *p = strstr(addr_str, "!4");
if (!p)
return -1;
p += 2;
if (!isdigit((unsigned char) *p))
return -1;
long v = strtol(p, NULL, 10);
if (v <= 0 || v > 65535)
return -1;
*out_idx0 = (uint16_t)(v - 1);
return 0;
}
void handle_simulator_status(nng_aio *aio)
{
NEU_VALIDATE_JWT(aio);
neu_modbus_simulator_status_t st = { 0 };
neu_modbus_simulator_get_status(&st);
char buf[256] = { 0 };
snprintf(buf, sizeof(buf),
"{\"running\":%d,\"port\":%u,\"tag_count\":%d,\"error\":%d}",
st.running ? 1 : 0, st.port, st.tag_count, st.error);
neu_http_ok(aio, buf);
}
void handle_simulator_start(nng_aio *aio)
{
NEU_VALIDATE_JWT(aio);
int rc = neu_modbus_simulator_start(NULL, 0);
neu_modbus_simulator_status_t st = { 0 };
neu_modbus_simulator_get_status(&st);
if (rc != 0 || (!st.running && st.error != 0)) {
char buf[64] = { 0 };
snprintf(buf, sizeof(buf), "{\"error\":%d}", st.error);
neu_http_ok(aio, buf);
return;
}
neu_http_ok(aio, "{\"error\":0}");
}
void handle_simulator_list_tags(nng_aio *aio)
{
NEU_VALIDATE_JWT(aio);
char *out = neu_modbus_simulator_list_tags_json();
if (!out) {
NEU_JSON_RESPONSE_ERROR(NEU_ERR_EINTERNAL, {
neu_http_response(aio, NEU_ERR_EINTERNAL, result_error);
});
return;
}
neu_http_ok(aio, out);
free(out);
}
void handle_simulator_stop(nng_aio *aio)
{
NEU_VALIDATE_JWT(aio);
neu_modbus_simulator_stop();
neu_http_ok(aio, "{\"error\":0}");
}
void handle_simulator_set_config(nng_aio *aio)
{
NEU_VALIDATE_JWT(aio);
char * body = NULL;
size_t sz = 0;
if (neu_http_get_body(aio, (void **) &body, &sz) != 0 || !body) {
NEU_JSON_RESPONSE_ERROR(NEU_ERR_PARAM_IS_WRONG, {
neu_http_response(aio, NEU_ERR_PARAM_IS_WRONG, result_error);
});
return;
}
json_error_t jerr;
json_t * root = json_loads(body, 0, &jerr);
free(body);
if (!root) {
NEU_JSON_RESPONSE_ERROR(NEU_ERR_PARAM_IS_WRONG, {
neu_http_response(aio, NEU_ERR_PARAM_IS_WRONG, result_error);
});
return;
}
json_t *tags = json_object_get(root, "tags");
if (!tags || !json_is_array(tags)) {
json_decref(root);
NEU_JSON_RESPONSE_ERROR(NEU_ERR_PARAM_IS_WRONG, {
neu_http_response(aio, NEU_ERR_PARAM_IS_WRONG, result_error);
});
return;
}
int count = 0;
for (size_t i = 0; i < json_array_size(tags); ++i) {
json_t *obj = json_array_get(tags, i);
if (!obj || !json_is_object(obj))
continue;
json_t *jaddr = json_object_get(obj, "address");
json_t *jtype = json_object_get(obj, "type");
if (!jaddr || !json_is_string(jaddr) || !jtype)
continue;
const char *addr_str = json_string_value(jaddr);
uint16_t idx0 = 0;
if (extract_hold_start_from_addr(addr_str, &idx0) != 0)
continue;
count++;
}
char ** names = NULL;
char ** addresses = NULL;
uint16_t * indices = NULL;
neu_sim_tag_type_e *types = NULL;
if (count > 0) {
names = calloc((size_t) count, sizeof(char *));
addresses = calloc((size_t) count, sizeof(char *));
indices = calloc((size_t) count, sizeof(uint16_t));
types = calloc((size_t) count, sizeof(neu_sim_tag_type_e));
if (!names || !addresses || !indices || !types) {
free(names);
free(addresses);
free(indices);
free(types);
json_decref(root);
NEU_JSON_RESPONSE_ERROR(NEU_ERR_EINTERNAL, {
neu_http_response(aio, NEU_ERR_EINTERNAL, result_error);
});
return;
}
}
int m = 0;
for (size_t i = 0; i < json_array_size(tags); ++i) {
json_t *obj = json_array_get(tags, i);
if (!obj || !json_is_object(obj))
continue;
json_t *jname = json_object_get(obj, "name");
json_t *jaddr = json_object_get(obj, "address");
json_t *jtype = json_object_get(obj, "type");
if (!jaddr || !json_is_string(jaddr) || !jtype)
continue;
const char *addr_str = json_string_value(jaddr);
uint16_t idx0 = 0;
if (extract_hold_start_from_addr(addr_str, &idx0) != 0)
continue;
neu_sim_tag_type_e tp = NEU_SIM_TAG_NONE;
if (json_is_string(jtype)) {
const char *ts = json_string_value(jtype);
if (ts) {
if (strcmp(ts, "sine") == 0)
tp = NEU_SIM_TAG_SINE;
else if (strcmp(ts, "saw") == 0)
tp = NEU_SIM_TAG_SAW;
else if (strcmp(ts, "square") == 0)
tp = NEU_SIM_TAG_SQUARE;
else if (strcmp(ts, "random") == 0)
tp = NEU_SIM_TAG_RANDOM;
}
} else if (json_is_integer(jtype)) {
tp = (neu_sim_tag_type_e) json_integer_value(jtype);
}
if (tp == NEU_SIM_TAG_NONE || idx0 >= 1000)
continue;
indices[m] = idx0;
types[m] = tp;
names[m] = jname && json_is_string(jname)
? strdup(json_string_value(jname))
: strdup("");
addresses[m] = strdup(addr_str);
m++;
}
json_decref(root);
neu_modbus_simulator_config_tags(
(const char **) names, (const char **) addresses, indices, types, m);
for (int i = 0; i < m; ++i) {
free(names[i]);
free(addresses[i]);
}
free(names);
free(addresses);
free(indices);
free(types);
neu_http_ok(aio, "{\"error\":0}");
}
void handle_simulator_export(nng_aio *aio)
{
NEU_VALIDATE_JWT(aio);
char *drivers = neu_modbus_simulator_export_drivers_json();
if (!drivers) {
NEU_JSON_RESPONSE_ERROR(NEU_ERR_EINTERNAL, {
neu_http_response(aio, NEU_ERR_EINTERNAL, result_error);
});
return;
}
neu_http_response_file(
aio, drivers, strlen(drivers),
"attachment; filename=\"simulator_modbus_south.json\"");
free(drivers);
}
+13
View File
@@ -0,0 +1,13 @@
#ifndef NEU_PLUGIN_REST_SIMULATOR_HANDLE_H
#define NEU_PLUGIN_REST_SIMULATOR_HANDLE_H
#include <nng/nng.h>
void handle_simulator_status(nng_aio *aio);
void handle_simulator_start(nng_aio *aio);
void handle_simulator_stop(nng_aio *aio);
void handle_simulator_set_config(nng_aio *aio);
void handle_simulator_export(nng_aio *aio);
void handle_simulator_list_tags(nng_aio *aio);
#endif
+32
View File
@@ -0,0 +1,32 @@
#include <dirent.h>
#include <errno.h>
#include <getopt.h>
#include <jwt.h>
#include <libgen.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "json/neu_json_fn.h"
#include "handle.h"
#include "utils/http.h"
#include "utils/neu_jwt.h"
#include "parser/neu_json_system.h"
#include "system_handle.h"
void handle_system_ctl(nng_aio *aio)
{
NEU_PROCESS_HTTP_REQUEST_VALIDATE_JWT(
aio, neu_json_system_ctl_req_t, neu_json_decode_system_ctl_req, {
if (req->action == 1) {
neu_http_ok(aio, "{\"error\": 0 }");
exit(-2);
}
})
}
+27
View File
@@ -0,0 +1,27 @@
/**
* NEURON IIoT System for Industry 4.0
* Copyright (C) 2020-2022 EMQ Technologies Co., Ltd All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#ifndef _NEU_SYSTEM_HANDLE_H_
#define _NEU_SYSTEM_HANDLE_H_
#include <nng/nng.h>
void handle_system_ctl(nng_aio *aio);
#endif
+6
View File
@@ -90,6 +90,7 @@ static void sig_handler(int sig)
for (uint32_t i = 0; i < sizeof(c_events) / sizeof(struct client_event);
i++) {
if (c_events[i].fd > 0) {
close(c_events[i].fd);
neu_event_del_io(events, c_events[i].client);
}
}
@@ -149,6 +150,11 @@ int main(int argc, char *argv[])
events = neu_event_new("modbus_simulator");
conn = neu_conn_new(&param, NULL, connected, disconnected);
while (conn_tcp_server_is_listen(conn) == false) {
neu_msleep(1000);
neu_conn_destory(conn);
conn = neu_conn_new(&param, NULL, connected, disconnected);
}
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
File diff suppressed because it is too large Load Diff
+63
View File
@@ -548,6 +548,11 @@ static int adapter_command(neu_adapter_t *adapter, neu_reqresp_head_t header,
strcpy(pheader->receiver, cmd->driver);
break;
}
case NEU_REQ_RENAME_TAG: {
neu_req_rename_tag_t *cmd = (neu_req_rename_tag_t *) data;
strcpy(pheader->receiver, cmd->driver);
break;
}
case NEU_REQ_IMPORT_TAGS: {
neu_req_import_tags_t *cmd = (neu_req_import_tags_t *) data;
strcpy(pheader->receiver, cmd->node);
@@ -887,6 +892,7 @@ static int adapter_loop(enum neu_event_io_type type, int fd, void *usr_data)
case NEU_RESP_ADD_GTAG:
case NEU_RESP_IMPORT_TAGS:
case NEU_RESP_UPDATE_TAG:
case NEU_RESP_RENAME_TAG:
case NEU_RESP_GET_TAG:
case NEU_RESP_GET_NODE:
case NEU_RESP_GET_PLUGIN:
@@ -912,6 +918,7 @@ static int adapter_loop(enum neu_event_io_type type, int fd, void *usr_data)
case NEU_REQ_ADD_TAG_EVENT:
case NEU_REQ_DEL_TAG_EVENT:
case NEU_REQ_UPDATE_TAG_EVENT:
case NEU_REQ_RENAME_TAG_EVENT:
case NEU_REQ_ADD_GTAG_EVENT:
case NEU_REQ_ADD_PLUGIN_EVENT:
case NEU_REQ_DEL_PLUGIN_EVENT:
@@ -1772,6 +1779,40 @@ static int adapter_loop(enum neu_event_io_type type, int fd, void *usr_data)
reply(adapter, header, &resp);
break;
}
case NEU_REQ_RENAME_TAG: {
neu_req_rename_tag_t *cmd = (neu_req_rename_tag_t *) &header[1];
neu_resp_rename_tag_t resp = { 0 };
if (adapter->module->type == NEU_NA_TYPE_DRIVER) {
resp.error = neu_adapter_driver_rename_tag(
(neu_adapter_driver_t *) adapter, cmd->group, cmd->old_name,
cmd->new_name);
if (resp.error == 0) {
rv = adapter_storage_rename_tag(cmd->driver, cmd->group,
cmd->old_name, cmd->new_name);
if (rv != 0) {
// rollback in-memory rename on persistence failure
nlog_error("persist rename failed, rolling back "
"tag:%s->%s node:%s grp:%s",
cmd->old_name, cmd->new_name, cmd->driver,
cmd->group);
neu_adapter_driver_rename_tag(
(neu_adapter_driver_t *) adapter, cmd->group,
cmd->new_name, cmd->old_name);
resp.error = NEU_ERR_EINTERNAL;
} else if (header->monitor) {
notify_monitor(adapter, NEU_REQ_RENAME_TAG_EVENT, cmd);
}
}
} else {
resp.error = NEU_ERR_GROUP_NOT_ALLOW;
}
neu_msg_exchange(header);
header->type = NEU_RESP_RENAME_TAG;
reply(adapter, header, &resp);
break;
}
case NEU_REQ_NODE_UNINIT: {
neu_req_node_uninit_t *cmd = (neu_req_node_uninit_t *) &header[1];
char name[NEU_NODE_NAME_LEN] = { 0 };
@@ -2097,6 +2138,10 @@ int neu_adapter_uninit(neu_adapter_t *adapter)
neu_event_del_io(adapter->events, adapter->control_io);
if (adapter->module->type == NEU_NA_TYPE_DRIVER) {
if (adapter->timer_connect != NULL) {
neu_event_del_timer(adapter->events, adapter->timer_connect);
adapter->timer_connect = NULL;
}
neu_adapter_driver_destroy((neu_adapter_driver_t *) adapter);
}
@@ -2133,6 +2178,19 @@ int neu_adapter_start(neu_adapter_t *adapter)
neu_adapter_driver_start_group_timer(
(neu_adapter_driver_t *) adapter);
}
if (adapter->module->type == NEU_NA_TYPE_DRIVER) {
neu_event_timer_param_t param = {
.second = 5,
.millisecond = 0,
.usr_data = (void *) adapter,
.type = NEU_EVENT_TIMER_BLOCK,
};
param.cb = neu_adapter_driver_try_connect;
adapter->timer_connect =
neu_event_add_timer(adapter->events, param);
}
}
return error;
@@ -2176,6 +2234,11 @@ int neu_adapter_stop(neu_adapter_t *adapter)
(neu_adapter_driver_t *) adapter);
}
neu_adapter_reset_metrics(adapter);
if (adapter->timer_connect != NULL) {
neu_event_del_timer(adapter->events, adapter->timer_connect);
adapter->timer_connect = NULL;
}
}
return error;
+1
View File
@@ -55,6 +55,7 @@ struct neu_adapter {
neu_event_timer_t *timer_lev;
int64_t timestamp_lev;
neu_event_timer_t *timer_connect;
// metrics
neu_node_metrics_t *metrics;
+6 -6
View File
@@ -160,8 +160,8 @@ void neu_driver_cache_add(neu_driver_cache_t *cache, const char *group,
if (elem == NULL) {
elem = calloc(1, sizeof(struct elem));
snprintf(elem->key.group, sizeof(elem->key.group), "%s", group);
snprintf(elem->key.tag, sizeof(elem->key.tag), "%s", tag);
strcpy(elem->key.group, group);
strcpy(elem->key.tag, tag);
HASH_ADD(hh, cache->table, key, sizeof(tkey_t), elem);
}
@@ -179,7 +179,7 @@ void neu_driver_cache_update_trace(neu_driver_cache_t *cache, const char *group,
group_trace_t *elem = NULL;
char key[NEU_GROUP_NAME_LEN] = { 0 };
snprintf(key, sizeof(key), "%s", group);
strcpy(key, group);
pthread_mutex_lock(&cache->mtx);
HASH_FIND(hh, cache->trace_table, &key, sizeof(key), elem);
@@ -187,7 +187,7 @@ void neu_driver_cache_update_trace(neu_driver_cache_t *cache, const char *group,
if (elem == NULL) {
elem = calloc(1, sizeof(group_trace_t));
snprintf(elem->key, sizeof(elem->key), "%s", group);
strcpy(elem->key, group);
elem->trace_ctx = trace_ctx;
HASH_ADD(hh, cache->trace_table, key, sizeof(key), elem);
@@ -205,7 +205,7 @@ void *neu_driver_cache_get_trace(neu_driver_cache_t *cache, const char *group)
void *trace = NULL;
snprintf(key, sizeof(key), "%s", group);
strcpy(key, group);
pthread_mutex_lock(&cache->mtx);
HASH_FIND(hh, cache->trace_table, &key, sizeof(key), elem);
@@ -275,7 +275,7 @@ bool neu_driver_cache_update_change(neu_driver_cache_t *cache,
elem->changed = true;
tag_changed = true;
} else {
if (memcmp(elem->value_old.value.bytes.bytes,
if (memcpy(elem->value_old.value.bytes.bytes,
value.value.bytes.bytes,
value.value.bytes.length) != 0) {
elem->changed = true;
+2
View File
@@ -43,6 +43,8 @@ bool neu_driver_cache_update_change(neu_driver_cache_t *cache,
void neu_driver_cache_del(neu_driver_cache_t *cache, const char *group,
const char *tag);
void neu_driver_cache_rename(neu_driver_cache_t *cache, const char *group,
const char *old_tag, const char *new_tag);
void neu_driver_cache_update_trace(neu_driver_cache_t *cache, const char *group,
void *trace_ctx);
+78 -2
View File
@@ -741,6 +741,31 @@ int neu_adapter_driver_uninit(neu_adapter_driver_t *driver)
return 0;
}
int neu_adapter_driver_try_connect(void *param)
{
neu_adapter_driver_t *driver = (neu_adapter_driver_t *) param;
if (driver->tag_cnt > 0) {
return 0;
}
if (driver->adapter.state != NEU_NODE_RUNNING_STATE_RUNNING) {
return 0;
}
neu_plugin_common_t *common =
neu_plugin_to_plugin_common(driver->adapter.plugin);
if (common->link_state == NEU_NODE_LINK_STATE_CONNECTED) {
return 0;
}
if (driver->adapter.module->intf_funs->try_connect == NULL) {
return 0;
}
return driver->adapter.module->intf_funs->try_connect(
driver->adapter.plugin);
}
static inline void start_group_timer(neu_adapter_driver_t *driver, group_t *grp)
{
uint32_t interval = neu_group_get_interval(grp->group);
@@ -934,6 +959,7 @@ void neu_adapter_driver_read_group_paginate(neu_adapter_driver_t *driver,
tag_value.datatag.bias = tag->bias;
tag_value.datatag.description = strdup(tag->description);
tag_value.datatag.option = tag->option;
tag_value.datatag.unit = strdup(tag->unit);
memcpy(tag_value.datatag.meta, tag->meta, NEU_TAG_META_LENGTH);
utarray_push_back(resp.tags, &tag_value);
@@ -958,6 +984,7 @@ void neu_adapter_driver_read_group_paginate(neu_adapter_driver_t *driver,
tag_value.datatag.bias = tag->bias;
tag_value.datatag.description = strdup(tag->description);
tag_value.datatag.option = tag->option;
tag_value.datatag.unit = strdup(tag->unit);
memcpy(tag_value.datatag.meta, tag->meta, NEU_TAG_META_LENGTH);
utarray_push_back(resp.tags, &tag_value);
@@ -1094,6 +1121,12 @@ static void fix_value(neu_datatag_t *tag, neu_type_e value_type,
case NEU_DATATAG_ENDIAN_BL64:
value->value.u64 = neu_htonbl(value->value.u64);
break;
case NEU_DATATAG_ENDIAN_WS64:
value->value.u64 = neu_htonws(value->value.u64);
break;
case NEU_DATATAG_ENDIAN_WR64:
value->value.u64 = neu_htonwr(value->value.u64);
break;
case NEU_DATATAG_ENDIAN_L64:
case NEU_DATATAG_ENDIAN_LL64:
default:
@@ -2295,6 +2328,30 @@ int neu_adapter_driver_update_tag(neu_adapter_driver_t *driver,
return ret;
}
int neu_adapter_driver_rename_tag(neu_adapter_driver_t *driver,
const char *group, const char *old_name,
const char *new_name)
{
int ret = NEU_ERR_SUCCESS;
group_t *find = NULL;
if (strlen(new_name) >= NEU_TAG_NAME_LEN) {
return NEU_ERR_TAG_NAME_TOO_LONG;
}
HASH_FIND_STR(driver->groups, group, find);
if (find != NULL) {
ret = neu_group_rename_tag(find->group, old_name, new_name);
if (ret == NEU_ERR_SUCCESS) {
neu_driver_cache_rename(driver->cache, group, old_name, new_name);
}
} else {
ret = NEU_ERR_GROUP_NOT_EXIST;
}
return ret;
}
int neu_adapter_driver_get_tag(neu_adapter_driver_t *driver, const char *group,
UT_array **tags)
{
@@ -2845,6 +2902,12 @@ static void read_report_group(int64_t timestamp, int64_t timeout,
case NEU_DATATAG_ENDIAN_BL64:
value.value.value.u64 = neu_htonbl(value.value.value.u64);
break;
case NEU_DATATAG_ENDIAN_WS64:
value.value.value.u64 = neu_htonws(value.value.value.u64);
break;
case NEU_DATATAG_ENDIAN_WR64:
value.value.value.u64 = neu_htonwr(value.value.value.u64);
break;
case NEU_DATATAG_ENDIAN_L64:
case NEU_DATATAG_ENDIAN_LL64:
default:
@@ -3020,6 +3083,12 @@ static void read_group(int64_t timestamp, int64_t timeout,
case NEU_DATATAG_ENDIAN_BL64:
value.value.value.u64 = neu_htonbl(value.value.value.u64);
break;
case NEU_DATATAG_ENDIAN_WS64:
value.value.value.u64 = neu_htonws(value.value.value.u64);
break;
case NEU_DATATAG_ENDIAN_WR64:
value.value.value.u64 = neu_htonwr(value.value.value.u64);
break;
case NEU_DATATAG_ENDIAN_L64:
case NEU_DATATAG_ENDIAN_LL64:
default:
@@ -3129,6 +3198,7 @@ static void read_group_paginate(int64_t timestamp, int64_t timeout,
tag_value.datatag.bias = tag->bias;
tag_value.datatag.description = strdup(tag->description);
tag_value.datatag.option = tag->option;
tag_value.datatag.unit = strdup(tag->unit);
memcpy(tag_value.datatag.meta, tag->meta, NEU_TAG_META_LENGTH);
if (tag->attribute == NEU_ATTRIBUTE_WRITE) {
@@ -3208,6 +3278,12 @@ static void read_group_paginate(int64_t timestamp, int64_t timeout,
case NEU_DATATAG_ENDIAN_BL64:
value.value.value.u64 = neu_htonbl(value.value.value.u64);
break;
case NEU_DATATAG_ENDIAN_WS64:
value.value.value.u64 = neu_htonws(value.value.value.u64);
break;
case NEU_DATATAG_ENDIAN_WR64:
value.value.value.u64 = neu_htonwr(value.value.value.u64);
break;
case NEU_DATATAG_ENDIAN_L64:
case NEU_DATATAG_ENDIAN_LL64:
default:
@@ -3394,8 +3470,8 @@ void neu_adapter_driver_scan_tags(neu_adapter_driver_t *driver,
}
neu_req_scan_tags_t *cmd = (neu_req_scan_tags_t *) &req[1];
driver->adapter.module->intf_funs->driver.scan_tags(driver->adapter.plugin,
req, cmd->id, cmd->ctx);
driver->adapter.module->intf_funs->driver.scan_tags(
driver->adapter.plugin, req, cmd->id, cmd->ctx, cmd->load_index);
}
void neu_adapter_driver_test_read_tag(neu_adapter_driver_t *driver,
+5
View File
@@ -28,6 +28,8 @@ void neu_adapter_driver_destroy(neu_adapter_driver_t *driver);
int neu_adapter_driver_init(neu_adapter_driver_t *driver);
int neu_adapter_driver_uninit(neu_adapter_driver_t *driver);
int neu_adapter_driver_try_connect(void *param);
void neu_adapter_driver_start_group_timer(neu_adapter_driver_t *driver);
void neu_adapter_driver_stop_group_timer(neu_adapter_driver_t *driver);
@@ -76,6 +78,9 @@ int neu_adapter_driver_del_tag(neu_adapter_driver_t *driver, const char *group,
const char *tag);
int neu_adapter_driver_update_tag(neu_adapter_driver_t *driver,
const char *group, neu_datatag_t *tag);
int neu_adapter_driver_rename_tag(neu_adapter_driver_t *driver,
const char *group, const char *old_name,
const char *new_name);
int neu_adapter_driver_get_tag(neu_adapter_driver_t *driver, const char *group,
UT_array **tags);
UT_array *neu_adapter_driver_get_ptag(neu_adapter_driver_t *driver,
+11
View File
@@ -127,6 +127,17 @@ void adapter_storage_del_tag(const char *node, const char *group,
}
}
int adapter_storage_rename_tag(const char *node, const char *group,
const char *old_name, const char *new_name)
{
int rv = neu_persister_rename_tag(node, group, old_name, new_name);
if (0 != rv) {
nlog_error("fail rename tag:%s->%s adapter:%s grp:%s", old_name,
new_name, node, group);
}
return rv;
}
int adapter_load_setting(const char *node, char **setting)
{
int rv = neu_persister_load_node_setting(node, (const char **) setting);
+2
View File
@@ -41,6 +41,8 @@ void adapter_storage_update_tag_value(const char *node, const char *group,
const neu_datatag_t *tag);
void adapter_storage_del_tag(const char *node, const char *group,
const char *name);
int adapter_storage_rename_tag(const char *node, const char *group,
const char *old_name, const char *new_name);
int adapter_load_setting(const char *node, char **setting);
int adapter_load_group_and_tag(neu_adapter_driver_t *driver);
+52 -22
View File
@@ -49,15 +49,8 @@ static void update_timestamp(neu_group_t *group);
neu_group_t *neu_group_new(const char *name, uint32_t interval)
{
neu_group_t *group = calloc(1, sizeof(neu_group_t));
if (NULL == group) {
return NULL;
}
group->name = strdup(name);
if (NULL == group->name) {
free(group);
return NULL;
}
group->name = strdup(name);
group->interval = interval;
pthread_mutex_init(&group->mtx, NULL);
@@ -135,22 +128,9 @@ int neu_group_add_tag(neu_group_t *group, const neu_datatag_t *tag)
return NEU_ERR_TAG_NAME_CONFLICT;
}
el = calloc(1, sizeof(tag_elem_t));
if (NULL == el) {
pthread_mutex_unlock(&group->mtx);
return NEU_ERR_EINTERNAL;
}
el = calloc(1, sizeof(tag_elem_t));
el->name = strdup(tag->name);
el->tag = neu_tag_dup(tag);
if (NULL == el->name || NULL == el->tag) {
free(el->name);
if (el->tag) {
neu_tag_free(el->tag);
}
free(el);
pthread_mutex_unlock(&group->mtx);
return NEU_ERR_EINTERNAL;
}
HASH_ADD_STR(group->tags, name, el);
update_timestamp(group);
@@ -177,6 +157,56 @@ int neu_group_update_tag(neu_group_t *group, const neu_datatag_t *tag)
return ret;
}
int neu_group_rename_tag(neu_group_t *group, const char *old_name,
const char *new_name)
{
tag_elem_t *el = NULL;
tag_elem_t *conflict = NULL;
int ret = NEU_ERR_TAG_NOT_EXIST;
pthread_mutex_lock(&group->mtx);
HASH_FIND_STR(group->tags, old_name, el);
if (el == NULL) {
pthread_mutex_unlock(&group->mtx);
return ret;
}
// short-circuit: renaming to the same name is a no-op
if (strcmp(old_name, new_name) == 0) {
pthread_mutex_unlock(&group->mtx);
return NEU_ERR_SUCCESS;
}
HASH_FIND_STR(group->tags, new_name, conflict);
if (conflict != NULL) {
pthread_mutex_unlock(&group->mtx);
return NEU_ERR_TAG_NAME_CONFLICT;
}
// allocate new names before freeing old ones to ensure atomicity on OOM
char *new_el_name = strdup(new_name);
char *new_tag_name = strdup(new_name);
if (new_el_name == NULL || new_tag_name == NULL) {
free(new_el_name);
free(new_tag_name);
pthread_mutex_unlock(&group->mtx);
return NEU_ERR_EINTERNAL;
}
HASH_DEL(group->tags, el);
free(el->name);
el->name = new_el_name;
free(el->tag->name);
el->tag->name = new_tag_name;
HASH_ADD_STR(group->tags, name, el);
update_timestamp(group);
ret = NEU_ERR_SUCCESS;
pthread_mutex_unlock(&group->mtx);
return ret;
}
int neu_group_del_tag(neu_group_t *group, const char *tag_name)
{
tag_elem_t *el = NULL;
+2
View File
@@ -37,6 +37,8 @@ void neu_group_destroy(neu_group_t *group);
int neu_group_update(neu_group_t *group, uint32_t interval);
int neu_group_add_tag(neu_group_t *group, const neu_datatag_t *tag);
int neu_group_update_tag(neu_group_t *group, const neu_datatag_t *tag);
int neu_group_rename_tag(neu_group_t *group, const char *old_name,
const char *new_name);
int neu_group_del_tag(neu_group_t *group, const char *tag_name);
UT_array * neu_group_get_tag(neu_group_t *group);
UT_array * neu_group_query_tag(neu_group_t *group, const char *name);
+3 -4
View File
@@ -76,7 +76,7 @@ static void find_os_info()
}
buf[strcspn(buf, "\n")] = 0;
strncpy(g_metrics_.machine, buf, sizeof(g_metrics_.machine));
g_metrics_.machine[sizeof(g_metrics_.machine) - 1] = 0;
g_metrics_.kernel[sizeof(g_metrics_.machine) - 1] = 0;
pclose(f);
@@ -87,7 +87,6 @@ static void find_os_info()
strncpy(g_metrics_.clib, "glibc", sizeof(g_metrics_.clib));
strncpy(g_metrics_.clib_version, gnu_get_libc_version(),
sizeof(g_metrics_.clib_version));
g_metrics_.clib_version[sizeof(g_metrics_.clib_version) - 1] = 0;
#endif
}
@@ -97,7 +96,7 @@ static size_t parse_memory_fields(int col)
char buf[64] = {};
size_t val = 0;
snprintf(buf, sizeof(buf), "free -b | awk 'NR==2 {print $%i}'", col);
sprintf(buf, "free -b | awk 'NR==2 {print $%i}'", col);
f = popen(buf, "r");
if (NULL == f) {
@@ -132,7 +131,7 @@ static inline size_t neuron_memory_used()
size_t val = 0;
pid_t pid = getpid();
snprintf(buf, sizeof(buf), "ps -o rss= %ld", (long) pid);
sprintf(buf, "ps -o rss= %ld", (long) pid);
f = popen(buf, "r");
if (NULL == f) {
+5 -1
View File
@@ -57,6 +57,7 @@ extern "C" {
XX(NEU_RESP_NODE_UNINIT, neu_resp_node_uninit_t) \
XX(NEU_REQ_ADD_NODE, neu_req_add_node_t) \
XX(NEU_REQ_UPDATE_NODE, neu_req_update_node_t) \
XX(NEU_REQ_UPDATE_NODE_TAG, neu_req_update_node_tag_t) \
XX(NEU_REQ_DEL_NODE, neu_req_del_node_t) \
XX(NEU_REQ_GET_NODE, neu_req_get_node_t) \
XX(NEU_RESP_GET_NODE, neu_resp_get_node_t) \
@@ -131,6 +132,7 @@ extern "C" {
XX(NEU_REQ_ADD_TAG_EVENT, neu_req_add_tag_t) \
XX(NEU_REQ_DEL_TAG_EVENT, neu_req_del_tag_t) \
XX(NEU_REQ_UPDATE_TAG_EVENT, neu_req_update_tag_t) \
XX(NEU_REQ_RENAME_TAG_EVENT, neu_req_rename_tag_t) \
XX(NEU_REQ_ADD_GTAG_EVENT, neu_req_add_gtag_t) \
XX(NEU_REQ_IMPORT_TAGS_EVENT, neu_req_import_tags_t) \
XX(NEU_REQ_ADD_PLUGIN_EVENT, neu_req_add_plugin_t) \
@@ -170,7 +172,9 @@ extern "C" {
XX(NEU_REQ_SERVER_AUTH_USER_DELETE, neu_req_server_auth_user_del_t) \
XX(NEU_REQ_SERVER_AUTH_USER_UPDATE_PWD, neu_req_server_auth_user_update_t) \
XX(NEU_REQ_SERVER_AUTH_USER_INFO, neu_req_server_auth_users_info_t) \
XX(NEU_RESP_SERVER_AUTH_USER_INFO, neu_resp_server_auth_users_info_t)
XX(NEU_RESP_SERVER_AUTH_USER_INFO, neu_resp_server_auth_users_info_t) \
XX(NEU_REQ_RENAME_TAG, neu_req_rename_tag_t) \
XX(NEU_RESP_RENAME_TAG, neu_resp_rename_tag_t)
static inline size_t neu_reqresp_size(neu_reqresp_type_e t)
{
+14 -4
View File
@@ -40,6 +40,11 @@ static void tag_array_copy(void *_dst, const void *_src)
dst->address = strdup(src->address);
dst->name = strdup(src->name);
dst->description = strdup(src->description);
if (src->unit == NULL) {
dst->unit = strdup("");
} else {
dst->unit = strdup(src->unit);
}
memcpy(dst->format, src->format, sizeof(src->format));
dst->n_format = src->n_format;
@@ -53,6 +58,9 @@ static void tag_array_free(void *_elt)
free(elt->name);
free(elt->address);
free(elt->description);
if (elt->unit) {
free(elt->unit);
}
}
void neu_tag_format_str(const neu_datatag_t *tag, char *buf, int len)
@@ -99,9 +107,6 @@ UT_icd *neu_tag_get_icd()
neu_datatag_t *neu_tag_dup(const neu_datatag_t *tag)
{
neu_datatag_t *new = calloc(1, sizeof(*new));
if (NULL == new) {
return NULL;
}
tag_array_copy(new, tag);
return new;
}
@@ -285,6 +290,12 @@ int neu_datatag_parse_addr_option(const neu_datatag_t * datatag,
} else if (e1 == 'L' && e2 == 'B') {
option->value64.endian = NEU_DATATAG_ENDIAN_LB64;
option->value64.is_default = false;
} else if (e1 == 'W' && e2 == 'S') {
option->value64.endian = NEU_DATATAG_ENDIAN_WS64;
option->value64.is_default = false;
} else if (e1 == 'W' && e2 == 'R') {
option->value64.endian = NEU_DATATAG_ENDIAN_WR64;
option->value64.is_default = false;
}
} else if (n == 1) {
if (e1 == 'B') {
@@ -300,7 +311,6 @@ int neu_datatag_parse_addr_option(const neu_datatag_t * datatag,
break;
}
case NEU_TYPE_BOOL:
case NEU_TYPE_BIT: {
char *op = find_last_character(datatag->address, '.');
+7 -4
View File
@@ -732,6 +732,11 @@ static void conn_init_param(neu_conn_t *conn, neu_conn_param_t *param)
}
}
bool conn_tcp_server_is_listen(neu_conn_t *conn)
{
return conn->tcp_server.is_listen;
}
static void conn_tcp_server_listen(neu_conn_t *conn)
{
if (conn->param.type == NEU_CONN_TCP_SERVER &&
@@ -746,6 +751,7 @@ static void conn_tcp_server_listen(neu_conn_t *conn)
};
fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 }, sizeof(int));
ret = bind(fd, (struct sockaddr *) &local, sizeof(local));
} else if (is_ipv6(conn->param.params.tcp_server.ip)) {
@@ -756,7 +762,7 @@ static void conn_tcp_server_listen(neu_conn_t *conn)
&local.sin6_addr);
fd = socket(AF_INET6, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 }, sizeof(int));
ret = bind(fd, (struct sockaddr *) &local, sizeof(local));
} else {
zlog_error(conn->param.log, "invalid ip: %s",
@@ -1402,9 +1408,6 @@ int neu_conn_stream_tcp_server_consume(neu_conn_t *conn, int fd, void *context,
int neu_conn_wait_msg(neu_conn_t *conn, void *context, uint16_t n_byte,
neu_conn_process_msg fn)
{
if (n_byte > conn->buf_size) {
n_byte = conn->buf_size;
}
ssize_t ret = neu_conn_recv(conn, conn->buf, n_byte);
neu_protocol_unpack_buf_t pbuf = { 0 };
conn->offset = 0;

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