mirror of
https://github.com/emqx/neuron.git
synced 2026-10-30 15:53:57 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd3b96a568 | ||
|
|
36fe84ab6e | ||
|
|
ccc9492f82 | ||
|
|
4b97979d38 | ||
|
|
2a4e6a39e9 | ||
|
|
b1bf3830ad | ||
|
|
0e850fe85a | ||
|
|
8ac388f414 | ||
|
|
3400c7546f | ||
|
|
a4b863818f | ||
|
|
62ed730ba6 | ||
|
|
e9c318296b | ||
|
|
848a38e962 | ||
|
|
dd689ca4d2 | ||
|
|
e878a3fd8e | ||
|
|
8b1d72b9dc | ||
|
|
638a2d08f9 | ||
|
|
7000e3f7a6 | ||
|
|
9e9b5be252 | ||
|
|
ed67628e92 | ||
|
|
9f8f4db9e0 | ||
|
|
c42e0a8be5 | ||
|
|
6e243ab60f | ||
|
|
c99999b8c9 | ||
|
|
af61f406c0 | ||
|
|
243778b229 | ||
|
|
207da6dbc1 | ||
|
|
7c03aba923 | ||
|
|
522db9fc9d | ||
|
|
118dfc08b8 | ||
|
|
84b0a5c6a2 | ||
|
|
a485bb6f56 | ||
|
|
feca615f61 | ||
|
|
f300668e76 | ||
|
|
8cce511761 | ||
|
|
0c9ff7fb8c | ||
|
|
937f536194 | ||
|
|
3cc99d9b00 | ||
|
|
ae4623ed7e | ||
|
|
b8a9ce4b4e | ||
|
|
f730eb0369 | ||
|
|
2dcdadc40e | ||
|
|
4174f3eee4 | ||
|
|
0ff2c57bfd | ||
|
|
8a5426b707 | ||
|
|
3f6517a8b9 | ||
|
|
171b1226e9 | ||
|
|
39c39a5371 | ||
|
|
e40aa30a83 | ||
|
|
f6dd2bdb94 | ||
|
|
32325490c8 | ||
|
|
24fb80efd7 | ||
|
|
317463b816 | ||
|
|
878d1d42c7 | ||
|
|
b506fdceb6 | ||
|
|
aebbf6cfd3 | ||
|
|
a5ae436104 | ||
|
|
b4ef7d631c | ||
|
|
f17068886b | ||
|
|
e490fe3980 | ||
|
|
f3eecd01af | ||
|
|
a9beb81c41 | ||
|
|
0159b2590b | ||
|
|
98dfb6601c | ||
|
|
a2bb561cd6 | ||
|
|
a6764d7395 | ||
|
|
9687801d5a | ||
|
|
9d2f763bea | ||
|
|
3dee50ddeb | ||
|
|
c9bae5e737 | ||
|
|
4533901078 | ||
|
|
2231d4eac7 | ||
|
|
533c01d07d | ||
|
|
4b26d00106 | ||
|
|
355cfd589b | ||
|
|
a6fdcb545f | ||
|
|
de03827d6b | ||
|
|
b6e707fb23 | ||
|
|
0bca9e39a5 | ||
|
|
3086974da4 | ||
|
|
938efcfa0b | ||
|
|
1eb1332989 | ||
|
|
ebc11baafd | ||
|
|
4a13b39ec6 | ||
|
|
66ab7bc2eb | ||
|
|
7490e6a42e | ||
|
|
9d93a4d410 | ||
|
|
e3d2cb7336 | ||
|
|
abb76d3e80 | ||
|
|
0b3f0f8088 | ||
|
|
f080ec2711 | ||
|
|
f6921ee2fb | ||
|
|
c52fae40c8 | ||
|
|
3bb42f900f | ||
|
|
bbda93038c | ||
|
|
9a5c1862b8 | ||
|
|
0652bbe00b | ||
|
|
6c088356f6 | ||
|
|
7441908f3a | ||
|
|
0a32d72fcd | ||
|
|
bda5ef1b2e | ||
|
|
6b47dc6a4a | ||
|
|
16ad3f6873 | ||
|
|
63f9a0eb9c | ||
|
|
942fcf8663 | ||
|
|
05b6d6be5a | ||
|
|
6c34076976 | ||
|
|
96ebfa4034 | ||
|
|
2ca52aecec | ||
|
|
989421f000 | ||
|
|
88e5fc85b7 | ||
|
|
14b55309e4 | ||
|
|
f761bb9c85 | ||
|
|
c38ae03b2b | ||
|
|
b522583f51 | ||
|
|
9f2b04300f | ||
|
|
d94afeedf6 | ||
|
|
ffe0316e5f | ||
|
|
7457543471 | ||
|
|
82615c0908 | ||
|
|
584732fe8c | ||
|
|
8c11f83540 | ||
|
|
c9bf291fa2 | ||
|
|
ae959acc52 | ||
|
|
d1b3838695 | ||
|
|
159be9f511 | ||
|
|
e07e5ad2e8 | ||
|
|
68c9dc0b0e | ||
|
|
ef859090bb | ||
|
|
04defee4da | ||
|
|
811ba04408 | ||
|
|
1dc53e3ff1 | ||
|
|
14326c8da1 | ||
|
|
d0f7335c2b | ||
|
|
157618269f | ||
|
|
c8f5feb28e | ||
|
|
acd9f87ee8 | ||
|
|
763783b4d9 | ||
|
|
4d42b2ab16 | ||
|
|
472044a2a3 | ||
|
|
87817e080d | ||
|
|
65b87e37ff | ||
|
|
6c8b684f80 | ||
|
|
161172632c | ||
|
|
84ecdde3d4 |
+125
-4
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -1,104 +0,0 @@
|
||||
# Neuron
|
||||
|
||||
[](https://github.com/neugates/neuron/releases)
|
||||
[](https://hub.docker.com/r/emqx/neuron)
|
||||
[](https://slack-invite.emqx.io/)
|
||||
[](https://discord.gg/xYGf3fQnES)
|
||||
[](https://twitter.com/EMQTech)
|
||||
[](https://www.youtube.com/channel/UC5FjR77ErAxvZENEWzQaO5Q)
|
||||
|
||||
|
||||
|
||||
[English](https://github.com/emqx/neuron/blob/main/README.md) | 简体中文
|
||||
|
||||
Neuron 是一款开源的、轻量级工业协议网关软件,支持数十种工业协议的一站式设备连接、数据接入、MQTT 协议转换,为工业设备赋予工业 4.0 时代关键的物联网连接能力。
|
||||
|
||||

|
||||
|
||||
以下是 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)。
|
||||
@@ -8,33 +8,54 @@
|
||||
[](https://twitter.com/EMQTech)
|
||||
[](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.
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"libplugin-ekuiper.so",
|
||||
"libplugin-modbus-tcp.so",
|
||||
"libplugin-modbus-rtu.so",
|
||||
"libplugin-file.so"
|
||||
"libplugin-file.so",
|
||||
"libplugin-kafka.so"
|
||||
]
|
||||
}
|
||||
@@ -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 默认的防火墙拦截,确保防火墙能开放此接口或关闭防火墙。
|
||||
|
||||

|
||||
|
||||
## Neuron 中创建南向设备
|
||||
|
||||
### 创建设备
|
||||
|
||||
**南向设备管理** -> **添加设备** 在弹出的对话框中填写设备信息后创建设备,**插件** 选择 `Modbus TCP`,**名称** 填写 `modbus-tcp-1` 即可。
|
||||
|
||||
### 配置设备
|
||||
创建设备后,进入设备详情页面配置设备参数。
|
||||
|
||||
**IP 地址**: 填写 Modbus TCP 模拟器运行机器的 IP 地址。如果 Neuron 与模拟器运行在同一台机器上,可以填写 `127.0.0.1`。
|
||||
**端口**: 默认502,根据模拟器的监听端口进行修改。
|
||||
|
||||
其他参数默认即可。
|
||||
|
||||
### 配置数据点
|
||||
|
||||
在 **南向设备管理** 页面,点击 **设备名称** 进入点位组页面,添加一个点位组,名称填写 `group1` 即可。
|
||||
|
||||
进入点位组后,点击 **组名称** 进入点位配置页面,创建点位。
|
||||
|
||||

|
||||
|
||||
**名称**: `tag-1`
|
||||
**地址**: `1!40001` ( Slave ID 1, 保持寄存器40001 )
|
||||
**属性**: `Read` 与 `Write`
|
||||
**数据类型**: `INT16`
|
||||
|
||||
### 查看点位值
|
||||
|
||||
配置完点位后,进入 **数据监控** 页面,选择刚才创建的设备以及点位组,即可看到点位的值。
|
||||
|
||||

|
||||
|
||||
## 创建北向应用
|
||||
|
||||
使用 MQTT,把刚才 modbus 设备中采集到的数据发送到 MQTT Broker。
|
||||
|
||||
**北向应用管理** -> **添加应用**,在弹出的对话框中填写应用信息创建应用。
|
||||
**插件** 选择 `MQTT`,**名称** 填写 `mqtt` 即可。
|
||||
|
||||
### 配置北向应用
|
||||
|
||||
创建北向应用后,进入北向配置页面,默认服务器地址为 `broker.emqx.io`,端口为 `1883`,如果需要连接其他 MQTT Broker,请根据实际情况修改。
|
||||
|
||||
### 订阅南向数据组
|
||||
|
||||
在**北向应用管理**页面中,点击北向应用,进入**订阅管理**页面,点击**添加订阅**,在弹出的对话框中选择刚才创建的 Modbus 设备以及点位组。
|
||||
|
||||

|
||||
|
||||
记住订阅时的 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 修改),即可看到点位数据。
|
||||
|
||||

|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -4,5 +4,8 @@
|
||||
"disable_auth": 0,
|
||||
"syslog_host": "",
|
||||
"syslog_port": 541,
|
||||
"sub_filter_error": 0
|
||||
"sub_filter_error": 0,
|
||||
"modbus_simulator": {
|
||||
"port": 502
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
alter TABLE tags add column unit TEXT NULL check(length(unit) <= 128);
|
||||
|
||||
COMMIT;
|
||||
Binary file not shown.
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
@@ -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, ",");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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, ©);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
@@ -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(¶m, NULL, connected, disconnected);
|
||||
while (conn_tcp_server_is_listen(conn) == false) {
|
||||
neu_msleep(1000);
|
||||
neu_conn_destory(conn);
|
||||
conn = neu_conn_new(¶m, NULL, connected, disconnected);
|
||||
}
|
||||
|
||||
signal(SIGINT, sig_handler);
|
||||
signal(SIGTERM, sig_handler);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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, '.');
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user