create-mirta 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +114 -9
- package/dist/locales/en-US.json +12 -2
- package/dist/locales/ru-RU.json +12 -2
- package/dist/templates/base/package.json +2 -2
- package/dist/templates/config/store/package.json +1 -1
- package/dist/templates/config/typescript/package.json +2 -2
- package/dist/templates/config/typescript/tsconfig.json +4 -3
- package/dist/templates/config/vitest/tsconfig.json +7 -0
- package/dist/templates/example/base/src/wb-rules/01-virtual-device.ts +37 -0
- package/dist/templates/example/base/src/wb-rules/02-control-monitoring.ts +36 -0
- package/dist/templates/example/base/src/wb-rules/03-master-switch.ts +55 -0
- package/dist/templates/example/base/src/wb-rules/04-pir-timeout.ts +37 -0
- package/dist/templates/example/base/src/wb-rules/05-same-rules.ts +58 -0
- package/dist/templates/example/base/src/wb-rules/06-timed-activation.ts +51 -0
- package/dist/templates/example/base/src/wb-rules/07-roller-shutters.ts +85 -0
- package/dist/templates/example/base/src/wb-rules/08-count-discrete.ts +57 -0
- package/dist/templates/example/base/src/wb-rules/09-impulse-counter.ts +46 -0
- package/dist/templates/example/base/src/wb-rules/10-invert-control-value.ts +33 -0
- package/dist/templates/example/base/src/wb-rules/11-press-counter.ts +112 -0
- package/dist/templates/example/base/src/wb-rules/12-msw-rule-co2.ts +47 -0
- package/dist/templates/example/base/src/wb-rules/13-msw-rule-co2-multi.ts +51 -0
- package/dist/templates/example/base/src/wb-rules/14-max-motion.ts +33 -0
- package/dist/templates/example/base/src/wb-rules/15-system-buzzer.ts +74 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -140,7 +140,7 @@ function usePrompts(messages) {
|
|
|
140
140
|
cancel,
|
|
141
141
|
prompt,
|
|
142
142
|
step: p.log.step,
|
|
143
|
-
|
|
143
|
+
message: p.log.message,
|
|
144
144
|
inlineSub,
|
|
145
145
|
};
|
|
146
146
|
}
|
|
@@ -174,6 +174,10 @@ ${yellow$1('Feature flags:')}
|
|
|
174
174
|
${dim$1('Add store for state management')}
|
|
175
175
|
|
|
176
176
|
${yellow$1('Options:')}
|
|
177
|
+
--ssh
|
|
178
|
+
${dim$1('Set SSH destination of deployment process as [username@][hostname][:port]')}
|
|
179
|
+
--rutoken
|
|
180
|
+
${dim$1('Use Rutoken ECP as encrypted store of SSH private key')}
|
|
177
181
|
-v, --version
|
|
178
182
|
${dim$1('Display the version number of this CLI')}
|
|
179
183
|
-f, --force
|
|
@@ -203,6 +207,10 @@ ${yellow$1('Флаги функционала:')}
|
|
|
203
207
|
${dim$1('Добавить хранилище состояний')}
|
|
204
208
|
|
|
205
209
|
${yellow$1('Опции:')}
|
|
210
|
+
--ssh
|
|
211
|
+
${dim$1('Настроить подключение SSH для деплоя в формате [username@][hostname][:port]')}
|
|
212
|
+
--rutoken
|
|
213
|
+
${dim$1('Использовать Рутокен ЭЦП в качестве хранилища для закрытого ключа SSH')}
|
|
206
214
|
-v, --version
|
|
207
215
|
${dim$1('Отобразить номер версии данного CLI')}
|
|
208
216
|
-f, --force
|
|
@@ -232,6 +240,12 @@ const featureFlags = ({
|
|
|
232
240
|
});
|
|
233
241
|
const allOptions = ({
|
|
234
242
|
...featureFlags,
|
|
243
|
+
ssh: {
|
|
244
|
+
type: 'string',
|
|
245
|
+
},
|
|
246
|
+
rutoken: {
|
|
247
|
+
type: 'boolean',
|
|
248
|
+
},
|
|
235
249
|
version: {
|
|
236
250
|
type: 'boolean',
|
|
237
251
|
short: 'v',
|
|
@@ -240,16 +254,23 @@ const allOptions = ({
|
|
|
240
254
|
type: 'boolean',
|
|
241
255
|
short: 'f',
|
|
242
256
|
},
|
|
243
|
-
help: {
|
|
244
|
-
type: 'boolean',
|
|
245
|
-
short: 'h',
|
|
246
|
-
},
|
|
247
257
|
bare: {
|
|
248
258
|
type: 'boolean',
|
|
249
259
|
short: 'b',
|
|
250
260
|
},
|
|
261
|
+
help: {
|
|
262
|
+
type: 'boolean',
|
|
263
|
+
short: 'h',
|
|
264
|
+
},
|
|
251
265
|
});
|
|
252
266
|
|
|
267
|
+
const urlRegex = /(?:(?<username>.+?)@)?(?:(?<hostname>[^:@\s]+))?(?::(?<port>\d+))?/;
|
|
268
|
+
function parseUrl(value) {
|
|
269
|
+
if (!value)
|
|
270
|
+
return {};
|
|
271
|
+
return (urlRegex.exec(value))?.groups ?? {};
|
|
272
|
+
}
|
|
273
|
+
|
|
253
274
|
const isObject = (val) => typeof val === 'object';
|
|
254
275
|
|
|
255
276
|
function sortDependencies(packageJson) {
|
|
@@ -329,7 +350,7 @@ function renderTemplate(sourcePath, targetPath) {
|
|
|
329
350
|
renderJson(sourcePath, targetPath, result => sortDependencies(result));
|
|
330
351
|
return;
|
|
331
352
|
}
|
|
332
|
-
if (['extensions.json', 'settings.json', 'tasks.json'].includes(filename) && fs.existsSync(targetPath)) {
|
|
353
|
+
if (['extensions.json', 'settings.json', 'tasks.json', 'tsconfig.json'].includes(filename) && fs.existsSync(targetPath)) {
|
|
333
354
|
renderJson(sourcePath, targetPath);
|
|
334
355
|
return;
|
|
335
356
|
}
|
|
@@ -500,7 +521,7 @@ const featureOptions = [
|
|
|
500
521
|
hint: messages.addVitest.hint,
|
|
501
522
|
},
|
|
502
523
|
];
|
|
503
|
-
const { prompt, cancel, step, inlineSub } = usePrompts(messages);
|
|
524
|
+
const { prompt, cancel, step, message, inlineSub } = usePrompts(messages);
|
|
504
525
|
function isValidPackageName(packageName) {
|
|
505
526
|
return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/
|
|
506
527
|
.test(packageName);
|
|
@@ -536,17 +557,26 @@ async function run() {
|
|
|
536
557
|
const hasPositionalDir = targetDir && targetDir !== '.';
|
|
537
558
|
const defaultProjectName = hasPositionalDir ? targetDir : 'wb-rules-mirta';
|
|
538
559
|
const shouldOverwrite = argv.force;
|
|
560
|
+
const sshAddress = parseUrl(argv.ssh);
|
|
561
|
+
const sshDefaultUser = 'root';
|
|
562
|
+
const sshDefaultHost = '10.200.200.1';
|
|
563
|
+
const sshDefaultPort = '22';
|
|
564
|
+
const sshFullyDefined = !!(sshAddress.username && sshAddress.hostname);
|
|
539
565
|
const scope = {
|
|
540
566
|
projectName: defaultProjectName,
|
|
541
567
|
projectRoot: '',
|
|
542
568
|
packageName: defaultProjectName,
|
|
543
569
|
shouldOverwrite,
|
|
544
570
|
features: [],
|
|
571
|
+
sshUsername: sshAddress.username,
|
|
572
|
+
sshHostname: sshAddress.hostname,
|
|
573
|
+
sshPort: sshAddress.port,
|
|
574
|
+
rutoken: argv.rutoken,
|
|
545
575
|
};
|
|
546
576
|
console.log(banner);
|
|
547
577
|
console.log(messages.title);
|
|
548
578
|
console.log();
|
|
549
|
-
intro(chalk.
|
|
579
|
+
intro(chalk.bgBlackBright.black(` ${messages.captions.intro} `));
|
|
550
580
|
if (!targetDir) {
|
|
551
581
|
const answer = await prompt(text({
|
|
552
582
|
message: messages.projectName.message,
|
|
@@ -590,6 +620,58 @@ async function run() {
|
|
|
590
620
|
process.exit(0);
|
|
591
621
|
}
|
|
592
622
|
}
|
|
623
|
+
message(chalk.bgBlackBright.black(` ${messages.captions.deploy} `));
|
|
624
|
+
if (!scope.sshUsername) {
|
|
625
|
+
scope.sshUsername = await prompt(text({
|
|
626
|
+
message: messages.ssh.username,
|
|
627
|
+
placeholder: sshDefaultUser,
|
|
628
|
+
defaultValue: sshDefaultUser,
|
|
629
|
+
validate: (value) => {
|
|
630
|
+
if (value.length !== 0 && value.trim().length === 0)
|
|
631
|
+
return messages.validation.required;
|
|
632
|
+
},
|
|
633
|
+
}));
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
step(`${messages.ssh.username}\n${dim(scope.sshUsername)}`);
|
|
637
|
+
}
|
|
638
|
+
if (!scope.sshHostname) {
|
|
639
|
+
scope.sshHostname = await prompt(text({
|
|
640
|
+
message: messages.ssh.host,
|
|
641
|
+
placeholder: sshDefaultHost,
|
|
642
|
+
defaultValue: sshDefaultHost,
|
|
643
|
+
validate: (value) => {
|
|
644
|
+
if (value.length !== 0 && value.trim().length === 0)
|
|
645
|
+
return messages.validation.required;
|
|
646
|
+
},
|
|
647
|
+
}));
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
step(`${messages.ssh.host}\n${dim(scope.sshHostname)}`);
|
|
651
|
+
}
|
|
652
|
+
if (!sshFullyDefined && !scope.sshPort) {
|
|
653
|
+
scope.sshPort = await prompt(text({
|
|
654
|
+
message: messages.ssh.port,
|
|
655
|
+
placeholder: sshDefaultPort,
|
|
656
|
+
defaultValue: sshDefaultPort,
|
|
657
|
+
validate: (value) => {
|
|
658
|
+
if (value.length !== 0 && value.trim().length === 0)
|
|
659
|
+
return messages.validation.required;
|
|
660
|
+
},
|
|
661
|
+
}));
|
|
662
|
+
}
|
|
663
|
+
else if (scope.sshPort) {
|
|
664
|
+
step(`${messages.ssh.port}\n${dim(scope.sshPort)}`);
|
|
665
|
+
}
|
|
666
|
+
if (!sshFullyDefined && !scope.rutoken) {
|
|
667
|
+
scope.rutoken = await prompt(confirm({
|
|
668
|
+
message: `${messages.ssh.useRutoken} ${dim(messages.accent.ifConfigured)}`,
|
|
669
|
+
initialValue: false,
|
|
670
|
+
}));
|
|
671
|
+
}
|
|
672
|
+
else if (scope.rutoken) {
|
|
673
|
+
step(`${messages.ssh.useRutoken} ${dim(messages.accent.ifConfigured)}\n${dim('Yes')}`);
|
|
674
|
+
}
|
|
593
675
|
if (!isFeatureFlagsUsed) {
|
|
594
676
|
scope.features = await prompt(multiselect({
|
|
595
677
|
message: `${messages.featureSelection.message}${inlineSub(dim(messages.featureSelection.hint))}`,
|
|
@@ -610,7 +692,30 @@ async function run() {
|
|
|
610
692
|
fs.mkdirSync(root);
|
|
611
693
|
}
|
|
612
694
|
step(`${messages.status.scaffolding} ${yellow(root)}`);
|
|
613
|
-
const pkg = {
|
|
695
|
+
const pkg = {
|
|
696
|
+
name: scope.packageName,
|
|
697
|
+
version: '0.0.0',
|
|
698
|
+
scripts: {
|
|
699
|
+
'wb:deploy': '',
|
|
700
|
+
},
|
|
701
|
+
};
|
|
702
|
+
const deployScript = ['rsync'];
|
|
703
|
+
if (process.platform === 'win32') {
|
|
704
|
+
// Run rsync trough WSL on Windows systems.
|
|
705
|
+
deployScript.unshift('wsl ');
|
|
706
|
+
}
|
|
707
|
+
if (scope.rutoken || scope.sshPort) {
|
|
708
|
+
deployScript.push(' -e \'ssh');
|
|
709
|
+
if (scope.rutoken)
|
|
710
|
+
deployScript.push(' -I /usr/lib/librtpkcs11ecp.so');
|
|
711
|
+
if (scope.sshPort)
|
|
712
|
+
deployScript.push(` -p ${scope.sshPort}`);
|
|
713
|
+
deployScript.push('\'');
|
|
714
|
+
}
|
|
715
|
+
deployScript.push(' -rltzvgO --progress --delete --exclude=\'alarms.conf\'');
|
|
716
|
+
deployScript.push(' --groupmap=\'*:developers\' dist/es5/*');
|
|
717
|
+
deployScript.push(` '${scope.sshUsername}@${scope.sshHostname}:/mnt/data/etc/'`);
|
|
718
|
+
pkg.scripts['wb:deploy'] = deployScript.join('');
|
|
614
719
|
fs.writeFileSync(resolve(root, 'package.json'), JSON.stringify(pkg, null, 2));
|
|
615
720
|
const templateRoot = fileURLToPath(new URL(templatesPath, import.meta.url));
|
|
616
721
|
const render = function (templateName) {
|
package/dist/locales/en-US.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"title": "The Framework to write wb-rules in TypeScript",
|
|
3
|
-
"
|
|
3
|
+
"captions": {
|
|
4
|
+
"intro": "Project Settings",
|
|
5
|
+
"deploy": "Deploy Settings"
|
|
6
|
+
},
|
|
4
7
|
"projectName": {
|
|
5
8
|
"message": "Project Name (target directory):"
|
|
6
9
|
},
|
|
@@ -16,6 +19,12 @@
|
|
|
16
19
|
"message": "is not empty.",
|
|
17
20
|
"confirmDelete": "Remove existing files before continue?"
|
|
18
21
|
},
|
|
22
|
+
"ssh": {
|
|
23
|
+
"username": "🔑 SSH Username:",
|
|
24
|
+
"host": "🌐 SSH Host:",
|
|
25
|
+
"port": "🚩 SSH Port:",
|
|
26
|
+
"useRutoken": "Use Rutoken ECP"
|
|
27
|
+
},
|
|
19
28
|
"featureSelection": {
|
|
20
29
|
"message": "Select features to include in your project:",
|
|
21
30
|
"hint": "↑/↓ to navigate, space to select, A to toggle all, Enter to confirm"
|
|
@@ -44,7 +53,8 @@
|
|
|
44
53
|
"installingDependencies": "Calling the package manager"
|
|
45
54
|
},
|
|
46
55
|
"accent": {
|
|
47
|
-
"recommended": "(recommended)"
|
|
56
|
+
"recommended": "(recommended)",
|
|
57
|
+
"ifConfigured": "(if configured)"
|
|
48
58
|
},
|
|
49
59
|
"dependencies": {
|
|
50
60
|
"question": "Install project dependencies?",
|
package/dist/locales/ru-RU.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"title": "Фреймворк для создания правил wb-rules на TypeScript",
|
|
3
|
-
"
|
|
3
|
+
"captions": {
|
|
4
|
+
"intro": "Параметры проекта",
|
|
5
|
+
"deploy": "Параметры деплоя на контроллер"
|
|
6
|
+
},
|
|
4
7
|
"projectName": {
|
|
5
8
|
"message": "Название (целевая директория):",
|
|
6
9
|
"errorMessage": "Требуется указать значение"
|
|
@@ -17,6 +20,12 @@
|
|
|
17
20
|
"message": "содержит файлы.",
|
|
18
21
|
"confirmDelete": "Удалить их?"
|
|
19
22
|
},
|
|
23
|
+
"ssh": {
|
|
24
|
+
"username": "🔑 Пользователь SSH:",
|
|
25
|
+
"host": "🌐 Хост SSH:",
|
|
26
|
+
"port": "🚩 Порт SSH:",
|
|
27
|
+
"useRutoken": "Использовать Рутокен ЭЦП?"
|
|
28
|
+
},
|
|
20
29
|
"featureSelection": {
|
|
21
30
|
"message": "Выберите добавляемый функционал:",
|
|
22
31
|
"hint": "↑/↓ для навигации, пробел для выбора, A - выбрать всё, Enter - подтвердить"
|
|
@@ -45,7 +54,8 @@
|
|
|
45
54
|
"installingDependencies": "Вызов менеджера пакетов"
|
|
46
55
|
},
|
|
47
56
|
"accent": {
|
|
48
|
-
"recommended": "(рекомендуется)"
|
|
57
|
+
"recommended": "(рекомендуется)",
|
|
58
|
+
"ifConfigured": "(если настроен)"
|
|
49
59
|
},
|
|
50
60
|
"dependencies": {
|
|
51
61
|
"question": "Установить требуемые зависимости?",
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
"build:dev": "cross-env NODE_ENV=development rollup -c"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"mirta": "0.0.
|
|
11
|
+
"mirta": "0.0.4"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@mirta/rollup": "0.0.
|
|
14
|
+
"@mirta/rollup": "0.0.4",
|
|
15
15
|
"cross-env": "^7.0.3",
|
|
16
16
|
"rollup": "^4.45.1"
|
|
17
17
|
}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
"extends": "@mirta/tsconfig",
|
|
3
3
|
"compilerOptions": {
|
|
4
4
|
"paths": {
|
|
5
|
-
"@wbm/*": [
|
|
5
|
+
"@wbm/*": [
|
|
6
|
+
"./src/wb-rules-modules/*"
|
|
7
|
+
]
|
|
6
8
|
},
|
|
7
9
|
"types": [
|
|
8
10
|
"@mirta/globals",
|
|
9
|
-
"node"
|
|
10
|
-
"vitest/globals"
|
|
11
|
+
"node"
|
|
11
12
|
]
|
|
12
13
|
}
|
|
13
14
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Виртуальное устройство
|
|
2
|
+
//
|
|
3
|
+
// Адаптированная версия примера
|
|
4
|
+
// https://wirenboard.com/wiki/index.php?title=Rule_Examples
|
|
5
|
+
|
|
6
|
+
const deviceName = 'my-virtual-device'
|
|
7
|
+
|
|
8
|
+
defineVirtualDevice(deviceName, {
|
|
9
|
+
title: { 'en': 'My Virtual Device', 'ru': 'Мое виртуальное устройство' },
|
|
10
|
+
cells: {
|
|
11
|
+
value: {
|
|
12
|
+
title: { 'en': 'Value', 'ru': 'Значение' },
|
|
13
|
+
type: 'range',
|
|
14
|
+
value: 1,
|
|
15
|
+
min: 1,
|
|
16
|
+
max: 3,
|
|
17
|
+
},
|
|
18
|
+
state: {
|
|
19
|
+
title: { 'en': 'State', 'ru': 'Состояние' },
|
|
20
|
+
type: 'value',
|
|
21
|
+
value: 1,
|
|
22
|
+
enum: {
|
|
23
|
+
1: { 'en': 'Normal', 'ru': 'В норме' },
|
|
24
|
+
2: { 'en': 'Warning', 'ru': 'Внимание' },
|
|
25
|
+
3: { 'en': 'Crash', 'ru': 'Авария' } },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
defineRule({
|
|
31
|
+
whenChanged: deviceName + '/value',
|
|
32
|
+
then: function (newValue) {
|
|
33
|
+
|
|
34
|
+
dev[`${deviceName}/state`] = newValue
|
|
35
|
+
|
|
36
|
+
},
|
|
37
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Слежение за контролом
|
|
2
|
+
//
|
|
3
|
+
// Адаптированная версия примера
|
|
4
|
+
// https://wirenboard.com/wiki/index.php?title=Rule_Examples
|
|
5
|
+
|
|
6
|
+
defineRule({
|
|
7
|
+
whenChanged: 'wb-gpio/D1_IN',
|
|
8
|
+
then: function (newValue) {
|
|
9
|
+
|
|
10
|
+
dev['wb-gpio/Relay_2'] = newValue
|
|
11
|
+
dev['wb-mrm2_6/Relay 1'] = newValue
|
|
12
|
+
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
// То же самое, но с виртуальным девайсом в качестве источника событий.
|
|
17
|
+
|
|
18
|
+
defineVirtualDevice('simple_test', {
|
|
19
|
+
title: 'Simple Switch',
|
|
20
|
+
cells: {
|
|
21
|
+
enabled: {
|
|
22
|
+
type: 'switch',
|
|
23
|
+
value: false,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
defineRule('simple_switch', {
|
|
29
|
+
whenChanged: 'simple_test/enabled',
|
|
30
|
+
then: function (newValue) {
|
|
31
|
+
|
|
32
|
+
dev['wb-gpio/Relay_2'] = newValue
|
|
33
|
+
dev['wb-mrm2_6/Relay 1'] = newValue
|
|
34
|
+
|
|
35
|
+
},
|
|
36
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Мастер-выключатель с восстановлением последнего состояния
|
|
2
|
+
//
|
|
3
|
+
// Адаптированная версия примера
|
|
4
|
+
// https://wirenboard.com/wiki/index.php?title=Rule_Examples
|
|
5
|
+
|
|
6
|
+
defineVirtualDevice('power_off', {
|
|
7
|
+
title: 'Мастер-выключатель',
|
|
8
|
+
cells: {
|
|
9
|
+
power_off: {
|
|
10
|
+
type: 'pushbutton',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const ps = new PersistentStorage('power-storage', { global: true })
|
|
16
|
+
|
|
17
|
+
// Для обеспечения возможности распознавания обращения
|
|
18
|
+
// к контролу в dev, после массива добавляется 'as const'.
|
|
19
|
+
|
|
20
|
+
const lights = [
|
|
21
|
+
'wb-mdm3_50/K1',
|
|
22
|
+
'wb-mdm3_50/K2',
|
|
23
|
+
'wb-mdm3_50/K3',
|
|
24
|
+
] as const
|
|
25
|
+
|
|
26
|
+
let isPowerOff = true
|
|
27
|
+
|
|
28
|
+
defineRule({
|
|
29
|
+
whenChanged: ['wb-gpio/A1_IN', 'power_off/power_off'],
|
|
30
|
+
then: function () {
|
|
31
|
+
|
|
32
|
+
if (isPowerOff) {
|
|
33
|
+
|
|
34
|
+
lights.forEach(function (light) {
|
|
35
|
+
|
|
36
|
+
ps[light] = dev[light]
|
|
37
|
+
dev[light] = false
|
|
38
|
+
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
|
|
44
|
+
lights.forEach(function (light) {
|
|
45
|
+
|
|
46
|
+
dev[light] = ps[light] as WbRules.MqttValue
|
|
47
|
+
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
isPowerOff = !isPowerOff
|
|
53
|
+
|
|
54
|
+
},
|
|
55
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Детектор движения c таймаутом
|
|
2
|
+
//
|
|
3
|
+
// Адаптированная версия примера
|
|
4
|
+
// https://wirenboard.com/wiki/index.php?title=Rule_Examples
|
|
5
|
+
|
|
6
|
+
const motion_timer_1_timeout_ms = 30 * 1000
|
|
7
|
+
let motion_timer_1_id: NodeJS.Timeout | undefined
|
|
8
|
+
|
|
9
|
+
defineRule('motion_detector_1', {
|
|
10
|
+
whenChanged: 'wb-gpio/D2_IN',
|
|
11
|
+
then: function (newValue) {
|
|
12
|
+
|
|
13
|
+
// Обнаружено движение
|
|
14
|
+
if (newValue) {
|
|
15
|
+
|
|
16
|
+
// Включаем свет
|
|
17
|
+
dev['wb-gpio/Relay_1'] = true
|
|
18
|
+
|
|
19
|
+
if (motion_timer_1_id)
|
|
20
|
+
clearTimeout(motion_timer_1_id)
|
|
21
|
+
|
|
22
|
+
motion_timer_1_id = setTimeout(function () {
|
|
23
|
+
|
|
24
|
+
// Выключаем свет
|
|
25
|
+
dev['wb-gpio/Relay_1'] = false
|
|
26
|
+
|
|
27
|
+
// Очистка идентификатора таймера.
|
|
28
|
+
// (прим. void 0 - то же самое, что и undefined).
|
|
29
|
+
//
|
|
30
|
+
motion_timer_1_id = void 0
|
|
31
|
+
|
|
32
|
+
}, motion_timer_1_timeout_ms)
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
},
|
|
37
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Создание однотипных правил
|
|
2
|
+
//
|
|
3
|
+
// Адаптированная версия примера
|
|
4
|
+
// https://wirenboard.com/wiki/index.php?title=Rule_Examples
|
|
5
|
+
//
|
|
6
|
+
// Если таких детекторов движения нужно несколько,
|
|
7
|
+
// то чтобы не копировать код, можно обернуть создание
|
|
8
|
+
// правила и переменных в функцию:
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Создаёт правило реакции на сработку датчика движения.
|
|
12
|
+
* Какой датчик и какое реле, станет известно при вызове функции.
|
|
13
|
+
*
|
|
14
|
+
* @param name Наименование правила.
|
|
15
|
+
* @param timeout_ms Задержка срабатывания, в мс.
|
|
16
|
+
* @param detector_control Идентификатор датчика.
|
|
17
|
+
* @param relay_control Идентификатор реле.
|
|
18
|
+
*/
|
|
19
|
+
function makeMotionDetector(
|
|
20
|
+
name: string,
|
|
21
|
+
timeout_ms: number,
|
|
22
|
+
detector_control: string,
|
|
23
|
+
relay_control: string
|
|
24
|
+
) {
|
|
25
|
+
|
|
26
|
+
let motion_timer_id: NodeJS.Timeout | undefined
|
|
27
|
+
|
|
28
|
+
defineRule(name, {
|
|
29
|
+
whenChanged: `wb-gpio/${detector_control}`,
|
|
30
|
+
then: function (newValue) {
|
|
31
|
+
|
|
32
|
+
if (!newValue) {
|
|
33
|
+
|
|
34
|
+
dev[`wb-gpio/${relay_control}`] = true
|
|
35
|
+
|
|
36
|
+
if (motion_timer_id) {
|
|
37
|
+
|
|
38
|
+
clearTimeout(motion_timer_id)
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
motion_timer_id = setTimeout(function () {
|
|
43
|
+
|
|
44
|
+
dev['wb-gpio/relay_control'] = false
|
|
45
|
+
motion_timer_id = void 0
|
|
46
|
+
|
|
47
|
+
}, timeout_ms)
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
makeMotionDetector('motion_detector_1', 20000, 'EXT1_DR1', 'EXT2_R3A1')
|
|
57
|
+
makeMotionDetector('motion_detector_2', 10000, 'EXT1_DR2', 'EXT2_R3A2')
|
|
58
|
+
makeMotionDetector('motion_detector_3', 10000, 'EXT1_DR3', 'EXT2_R3A3')
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Активация правила только в определённое время
|
|
2
|
+
//
|
|
3
|
+
// Адаптированная версия примера
|
|
4
|
+
// https://wirenboard.com/wiki/index.php?title=Rule_Examples
|
|
5
|
+
|
|
6
|
+
const motion_timer_1_timeout_ms = 5 * 1000
|
|
7
|
+
let motion_timer_1_id: NodeJS.Timeout | undefined
|
|
8
|
+
|
|
9
|
+
defineRule('motion_detector_1', {
|
|
10
|
+
whenChanged: 'wb-gpio/A1_IN',
|
|
11
|
+
then: function (newValue) {
|
|
12
|
+
|
|
13
|
+
const date = new Date()
|
|
14
|
+
|
|
15
|
+
// time point marking the beginning of the interval
|
|
16
|
+
// i.e. "today, at HH:MM". All dates are in UTC!
|
|
17
|
+
const date_start = new Date(date)
|
|
18
|
+
date_start.setHours(9)
|
|
19
|
+
date_start.setMinutes(30)
|
|
20
|
+
|
|
21
|
+
// time point marking the end of the interval
|
|
22
|
+
const date_end = new Date(date)
|
|
23
|
+
date_end.setHours(17)
|
|
24
|
+
date_end.setMinutes(10)
|
|
25
|
+
|
|
26
|
+
// if time is between 09:30 and 17:10 UTC
|
|
27
|
+
if ((date > date_start) && (date < date_end)) {
|
|
28
|
+
|
|
29
|
+
if (newValue) {
|
|
30
|
+
|
|
31
|
+
dev['wb-gpio/EXT1_R3A1'] = 1
|
|
32
|
+
|
|
33
|
+
if (motion_timer_1_id) {
|
|
34
|
+
|
|
35
|
+
clearTimeout(motion_timer_1_id)
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
motion_timer_1_id = setTimeout(function () {
|
|
40
|
+
|
|
41
|
+
dev['wb-gpio/EXT1_R3A1'] = 0
|
|
42
|
+
motion_timer_1_id = void 0
|
|
43
|
+
|
|
44
|
+
}, motion_timer_1_timeout_ms)
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
},
|
|
51
|
+
})
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Роллеты
|
|
2
|
+
//
|
|
3
|
+
// Адаптированная версия примера
|
|
4
|
+
// https://wirenboard.com/wiki/index.php?title=Rule_Examples
|
|
5
|
+
|
|
6
|
+
const suffix = '1' // must be different in different JS files
|
|
7
|
+
|
|
8
|
+
const relay_up = 'lc103_4/Relay 1'
|
|
9
|
+
const relay_down = 'lc103_4/Relay 2'
|
|
10
|
+
|
|
11
|
+
const timeout_s = 15
|
|
12
|
+
|
|
13
|
+
// End of settings
|
|
14
|
+
|
|
15
|
+
let relay_up_timer_id: NodeJS.Timeout | undefined
|
|
16
|
+
let relay_down_timer_id: NodeJS.Timeout | undefined
|
|
17
|
+
|
|
18
|
+
defineRule('roller_shutter_up_on' + suffix, {
|
|
19
|
+
asSoonAs: function () {
|
|
20
|
+
|
|
21
|
+
return dev[relay_up] as boolean
|
|
22
|
+
|
|
23
|
+
},
|
|
24
|
+
then: function () {
|
|
25
|
+
|
|
26
|
+
if (relay_up_timer_id) {
|
|
27
|
+
|
|
28
|
+
clearTimeout(relay_up_timer_id)
|
|
29
|
+
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
relay_up_timer_id = setTimeout(function () {
|
|
33
|
+
|
|
34
|
+
return dev[relay_up] = 0
|
|
35
|
+
|
|
36
|
+
}, timeout_s * 1000)
|
|
37
|
+
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
defineRule('roller_shutter_down_on' + suffix, {
|
|
42
|
+
asSoonAs: function () {
|
|
43
|
+
|
|
44
|
+
return dev[relay_down] as boolean
|
|
45
|
+
|
|
46
|
+
},
|
|
47
|
+
then: function () {
|
|
48
|
+
|
|
49
|
+
if (relay_down_timer_id) {
|
|
50
|
+
|
|
51
|
+
clearTimeout(relay_down_timer_id)
|
|
52
|
+
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
relay_down_timer_id = setTimeout(function () {
|
|
56
|
+
|
|
57
|
+
dev[relay_down] = 0
|
|
58
|
+
|
|
59
|
+
}, timeout_s * 1000)
|
|
60
|
+
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
defineRule('roller_shutter_both_on' + suffix, {
|
|
65
|
+
asSoonAs: function () {
|
|
66
|
+
|
|
67
|
+
return dev[relay_up] as boolean
|
|
68
|
+
&& dev[relay_down] as boolean
|
|
69
|
+
|
|
70
|
+
},
|
|
71
|
+
then: function () {
|
|
72
|
+
|
|
73
|
+
if (relay_up_timer_id)
|
|
74
|
+
clearTimeout(relay_up_timer_id)
|
|
75
|
+
|
|
76
|
+
if (relay_down_timer_id)
|
|
77
|
+
clearTimeout(relay_down_timer_id)
|
|
78
|
+
|
|
79
|
+
dev[relay_up] = 0
|
|
80
|
+
dev[relay_down] = 0
|
|
81
|
+
|
|
82
|
+
log('Both roller shutter relays on, switching them off')
|
|
83
|
+
|
|
84
|
+
},
|
|
85
|
+
})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Подсчет импульсов на дискретном входе
|
|
2
|
+
//
|
|
3
|
+
// Адаптированная версия примера
|
|
4
|
+
// https://wirenboard.com/wiki/index.php?title=Rule_Examples
|
|
5
|
+
|
|
6
|
+
import { isNumber } from 'mirta'
|
|
7
|
+
|
|
8
|
+
// --- Виртуальное устройство для отображения количества импульсов с кнопкой сброса ---
|
|
9
|
+
defineVirtualDevice('pulse_counter', {
|
|
10
|
+
title: 'Pulse counter',
|
|
11
|
+
cells: {
|
|
12
|
+
impulses: {
|
|
13
|
+
type: 'value',
|
|
14
|
+
value: 0,
|
|
15
|
+
},
|
|
16
|
+
reset: {
|
|
17
|
+
type: 'pushbutton',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// --- Логика подсчета импульсов ---
|
|
23
|
+
let impulseCount = 0 // Счетчик импульсов
|
|
24
|
+
let lastInputState = 0 // Последнее состояние входа
|
|
25
|
+
const inputChannel = 'wb-gpio/MOD1_IN2' // Топик дискретного входа
|
|
26
|
+
|
|
27
|
+
// Обработка импульсов: с 0 на 1
|
|
28
|
+
defineRule('count_impulses', {
|
|
29
|
+
whenChanged: inputChannel, // Правило срабатывает при любом изменении на дискретном входе, с 0 на 1 и наоборот
|
|
30
|
+
then: function (newValue) {
|
|
31
|
+
|
|
32
|
+
// Сужение диапазона типов до number.
|
|
33
|
+
if (!isNumber(newValue))
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
if (lastInputState == 0 && newValue == 1) { // Проверка фронта импульса с 0 на 1
|
|
37
|
+
|
|
38
|
+
impulseCount += 1 // Счетчик импульсов
|
|
39
|
+
dev.pulse_counter.impulses = impulseCount // Отображение импульсов в виртуальном устройстве
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
lastInputState = newValue // Сохранение значения фронта импульса, используется в условии выше
|
|
44
|
+
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Обработка кнопки сброса
|
|
49
|
+
defineRule('reset_counter', {
|
|
50
|
+
whenChanged: 'pulse_counter/reset',
|
|
51
|
+
then: function () {
|
|
52
|
+
|
|
53
|
+
impulseCount = 0 // Сброс счетчика
|
|
54
|
+
dev.pulse_counter.impulses = impulseCount
|
|
55
|
+
|
|
56
|
+
},
|
|
57
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Импульсные счетчики
|
|
2
|
+
//
|
|
3
|
+
// Адаптированная версия примера
|
|
4
|
+
// https://wirenboard.com/wiki/index.php?title=Rule_Examples
|
|
5
|
+
|
|
6
|
+
import { isNumber, isString } from 'mirta'
|
|
7
|
+
|
|
8
|
+
const meterCorrection = 123120 // Корректировочное значение счетчика в литрах
|
|
9
|
+
const counterCorrection = 7 // Корректировочное значение WB-MCM8 в импульсах
|
|
10
|
+
const inpulseValue = 10 // Количество литров на один импульс
|
|
11
|
+
|
|
12
|
+
defineVirtualDevice('water_meters', { // Создаем виртуальный девайс для отображения в веб интерфейсе.
|
|
13
|
+
title: 'Счетчики воды',
|
|
14
|
+
cells: {
|
|
15
|
+
water_meter_1: {
|
|
16
|
+
type: 'value',
|
|
17
|
+
value: 0,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
defineRule('water_meter_1', {
|
|
23
|
+
whenChanged: 'wb-mcm8_29/Input 1 counter',
|
|
24
|
+
then: function (newValue) {
|
|
25
|
+
|
|
26
|
+
if (newValue) {
|
|
27
|
+
|
|
28
|
+
// Здесь было не совсем очевидно из оригинального примера,
|
|
29
|
+
// что приходит в качестве newValue.
|
|
30
|
+
//
|
|
31
|
+
// Поэтому перестраховка с проверкой типов - распознаёт как строки, так и числа.
|
|
32
|
+
|
|
33
|
+
const intValue = isString(newValue)
|
|
34
|
+
? parseInt(newValue)
|
|
35
|
+
: (
|
|
36
|
+
isNumber(newValue)
|
|
37
|
+
? newValue
|
|
38
|
+
: 0
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
dev['water_meters/water_meter_1'] = ((intValue - counterCorrection) * inpulseValue) + meterCorrection // Умножаем значение счетчика на количество литров/импульс и прибавляем корректировочное значение.
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
},
|
|
46
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Инвертирование значения контрола
|
|
2
|
+
//
|
|
3
|
+
// Адаптированная версия примера
|
|
4
|
+
// https://wirenboard.com/wiki/index.php?title=Rule_Examples
|
|
5
|
+
|
|
6
|
+
defineVirtualDevice('my-invert-buzzer', {
|
|
7
|
+
title: 'Buzzer Invert',
|
|
8
|
+
cells: {
|
|
9
|
+
Disabled: {
|
|
10
|
+
title: 'disabled',
|
|
11
|
+
type: 'switch',
|
|
12
|
+
value: !dev['buzzer/enabled'],
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
defineRule({
|
|
18
|
+
whenChanged: ['buzzer/enabled'],
|
|
19
|
+
then: function (newValue) {
|
|
20
|
+
|
|
21
|
+
dev['my-invert-buzzer/Disabled'] = !newValue
|
|
22
|
+
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
defineRule({
|
|
27
|
+
whenChanged: ['my-invert-buzzer/Disabled'],
|
|
28
|
+
then: function (newValue) {
|
|
29
|
+
|
|
30
|
+
dev['buzzer/enabled'] = !newValue
|
|
31
|
+
|
|
32
|
+
},
|
|
33
|
+
})
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Обработка счётчиков нажатий
|
|
2
|
+
//
|
|
3
|
+
// Адаптированная версия примера
|
|
4
|
+
// https://wirenboard.com/wiki/index.php?title=Rule_Examples
|
|
5
|
+
|
|
6
|
+
/* ---------------------------------- */
|
|
7
|
+
/* 1. Single Press Counter: On action */
|
|
8
|
+
/* ---------------------------------- */
|
|
9
|
+
|
|
10
|
+
defineRule({
|
|
11
|
+
whenChanged: 'wb-mcm8_20/Input 1 Single Press Counter',
|
|
12
|
+
then: function () {
|
|
13
|
+
|
|
14
|
+
dev['wb-mdm3_58/K1'] = true
|
|
15
|
+
|
|
16
|
+
},
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
/* ----------------------------------- */
|
|
20
|
+
/* 2. Double Press Counter: Off action */
|
|
21
|
+
/* ----------------------------------- */
|
|
22
|
+
|
|
23
|
+
defineRule({
|
|
24
|
+
whenChanged: 'wb-mcm8_20/Input 1 Double Press Counter',
|
|
25
|
+
then: function () {
|
|
26
|
+
|
|
27
|
+
dev['wb-mdm3_58/K1'] = false
|
|
28
|
+
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
/* ------------------------------------------ */
|
|
33
|
+
/* 3. Long Press Counter: Increase brightness */
|
|
34
|
+
/* ------------------------------------------ */
|
|
35
|
+
|
|
36
|
+
defineRule({
|
|
37
|
+
whenChanged: 'wb-mcm8_20/Input 1 Long Press Counter',
|
|
38
|
+
then: function () {
|
|
39
|
+
|
|
40
|
+
// Start a timer that will increase the value of the control
|
|
41
|
+
startTicker('input1_long_press', 75)
|
|
42
|
+
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// A rule that will increase the brightness on a timer
|
|
47
|
+
defineRule({
|
|
48
|
+
when: function () {
|
|
49
|
+
|
|
50
|
+
return timers.input1_long_press.firing
|
|
51
|
+
|
|
52
|
+
},
|
|
53
|
+
then: function () {
|
|
54
|
+
|
|
55
|
+
let i = dev['wb-mdm3_58/Channel 1'] as number
|
|
56
|
+
|
|
57
|
+
if (i < 100 && dev['wb-mcm8_20/Input 1']) {
|
|
58
|
+
|
|
59
|
+
i += 1
|
|
60
|
+
|
|
61
|
+
dev['wb-mdm3_58/Channel 1'] = i
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
|
|
66
|
+
timers.input1_long_press.stop()
|
|
67
|
+
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
/* ------------===-------------------------------- */
|
|
74
|
+
/* 4. Shortlong Press Counter: Decrease brightness */
|
|
75
|
+
/* ---------------===----------------------------- */
|
|
76
|
+
|
|
77
|
+
defineRule({
|
|
78
|
+
whenChanged: 'wb-mcm8_20/Input 1 Shortlong Press Counter',
|
|
79
|
+
then: function () {
|
|
80
|
+
|
|
81
|
+
// Start a timer that will decrease the value of the control
|
|
82
|
+
startTicker('input1_shortlong_press', 75)
|
|
83
|
+
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// A rule that will decrease the brightness on a timer
|
|
88
|
+
defineRule({
|
|
89
|
+
when: function () {
|
|
90
|
+
|
|
91
|
+
return timers.input1_shortlong_press.firing
|
|
92
|
+
|
|
93
|
+
},
|
|
94
|
+
then: function () {
|
|
95
|
+
|
|
96
|
+
let i = dev['wb-mdm3_58/Channel 1'] as number
|
|
97
|
+
|
|
98
|
+
if (i > 0 && dev['wb-mcm8_20/Input 1']) {
|
|
99
|
+
|
|
100
|
+
i -= 1
|
|
101
|
+
|
|
102
|
+
dev['wb-mdm3_58/Channel 1'] = i
|
|
103
|
+
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
|
|
107
|
+
timers.input1_shortlong_press.stop()
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
},
|
|
112
|
+
})
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Датчик MSW v.3, CO2
|
|
2
|
+
//
|
|
3
|
+
// Адаптированная версия примера
|
|
4
|
+
// https://wirenboard.com/wiki/index.php?title=Rule_Examples
|
|
5
|
+
|
|
6
|
+
import { isNumber, isString } from 'mirta'
|
|
7
|
+
|
|
8
|
+
defineRule('msw3_co2', {
|
|
9
|
+
whenChanged: 'wb-msw-v3_97/CO2',
|
|
10
|
+
then: function (newValue, devName) {
|
|
11
|
+
|
|
12
|
+
// Предохранитель типов, чтобы TypeScript не ворчал почём зря.
|
|
13
|
+
// TODO: Переосмыслить типизацию 'then'
|
|
14
|
+
//
|
|
15
|
+
if (!isNumber(newValue) || !isString(devName))
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
const co2_good = newValue < 650
|
|
19
|
+
const co2_middle = newValue < 1000 && newValue > 651
|
|
20
|
+
const co2_bad = newValue > 1001
|
|
21
|
+
|
|
22
|
+
if (co2_good) {
|
|
23
|
+
|
|
24
|
+
dev[devName]['/Green LED'] = true
|
|
25
|
+
dev[devName]['/Red LED'] = false
|
|
26
|
+
dev[devName]['/LED Period (s)'] = 10
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (co2_middle) {
|
|
31
|
+
|
|
32
|
+
dev[devName]['/Green LED'] = true
|
|
33
|
+
dev[devName]['/Red LED'] = true
|
|
34
|
+
dev[devName]['/LED Period (s)'] = 5
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (co2_bad) {
|
|
39
|
+
|
|
40
|
+
dev[devName]['/Green LED'] = false
|
|
41
|
+
dev[devName]['/Red LED'] = true
|
|
42
|
+
dev[devName]['/LED Period (s)'] = 1
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
},
|
|
47
|
+
})
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Датчик MSW v.3, CO2 - несколько датчиков сразу
|
|
2
|
+
//
|
|
3
|
+
// Адаптированная версия примера
|
|
4
|
+
// https://wirenboard.com/wiki/index.php?title=Rule_Examples
|
|
5
|
+
|
|
6
|
+
import { isNumber } from 'mirta'
|
|
7
|
+
|
|
8
|
+
function ruleCO2(devCO2: string, minCO2: number, maxCO2: number) {
|
|
9
|
+
|
|
10
|
+
log.debug('rule create', devCO2)
|
|
11
|
+
defineRule ('ruleCO2' + devCO2, {
|
|
12
|
+
whenChanged: devCO2 + '/CO2',
|
|
13
|
+
then: function (newValue) {
|
|
14
|
+
|
|
15
|
+
if (!isNumber(newValue))
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
log.info('ruleCO2 ' + devCO2 + ' enter with', newValue)
|
|
19
|
+
if (newValue < minCO2) {
|
|
20
|
+
|
|
21
|
+
dev[devCO2]['/LED Glow Duration (ms)'] = 50
|
|
22
|
+
dev[devCO2]['/Green LED'] = true
|
|
23
|
+
dev[devCO2]['/Red LED'] = false
|
|
24
|
+
dev[devCO2]['/LED Period (s)'] = 3
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
if ((newValue > minCO2) && (newValue < maxCO2)) {
|
|
28
|
+
|
|
29
|
+
dev[devCO2]['/LED Glow Duration (ms)'] = 50
|
|
30
|
+
dev[devCO2]['/Green LED'] = true
|
|
31
|
+
dev[devCO2]['/Red LED'] = true
|
|
32
|
+
dev[devCO2]['/LED Period (s)'] = 2
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
if (newValue > maxCO2) {
|
|
36
|
+
|
|
37
|
+
dev[devCO2]['/LED Glow Duration (ms)'] = 50
|
|
38
|
+
dev[devCO2]['/Green LED'] = false
|
|
39
|
+
dev[devCO2]['/Red LED'] = true
|
|
40
|
+
dev[devCO2]['/LED Period (s)'] = 1
|
|
41
|
+
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
ruleCO2('wb-msw-v3_97', 650, 1000)
|
|
50
|
+
ruleCO2('wb-msw-v3_98', 650, 1000)
|
|
51
|
+
ruleCO2('wb-msw-v3_11', 500, 700)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Датчик MSW v.3, Max Motion
|
|
2
|
+
//
|
|
3
|
+
// Адаптированная версия примера
|
|
4
|
+
// https://wirenboard.com/wiki/index.php?title=Rule_Examples
|
|
5
|
+
|
|
6
|
+
import { isNumber } from 'mirta'
|
|
7
|
+
|
|
8
|
+
defineRule('msw3_Motion', {
|
|
9
|
+
whenChanged: 'wb-msw-v3_97/Max Motion',
|
|
10
|
+
then: function (newValue) {
|
|
11
|
+
|
|
12
|
+
if (!isNumber(newValue))
|
|
13
|
+
return
|
|
14
|
+
|
|
15
|
+
if (newValue > 50) {
|
|
16
|
+
|
|
17
|
+
// TODO: Переосмыслить типизацию. Кому понравится постоянно дописывать 'as ...'?
|
|
18
|
+
//
|
|
19
|
+
if ((dev['wb-msw-v3_97/Illuminance'] as number) < 50) {
|
|
20
|
+
|
|
21
|
+
dev['wb-mr3_11/K1'] = true
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
|
|
28
|
+
dev['wb-mr3_11/K1'] = false
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
},
|
|
33
|
+
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Системные правила, пищалка
|
|
2
|
+
//
|
|
3
|
+
// Адаптированная версия примера
|
|
4
|
+
// https://wirenboard.com/wiki/index.php?title=Rule_Examples
|
|
5
|
+
|
|
6
|
+
defineVirtualDevice('buzzer', {
|
|
7
|
+
title: 'Buzzer', //
|
|
8
|
+
|
|
9
|
+
cells: {
|
|
10
|
+
frequency: {
|
|
11
|
+
type: 'range',
|
|
12
|
+
value: 3000,
|
|
13
|
+
max: 7000,
|
|
14
|
+
},
|
|
15
|
+
volume: {
|
|
16
|
+
type: 'range',
|
|
17
|
+
value: 10,
|
|
18
|
+
max: 100,
|
|
19
|
+
},
|
|
20
|
+
enabled: {
|
|
21
|
+
type: 'switch',
|
|
22
|
+
value: false,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// setup pwm2
|
|
28
|
+
runShellCommand('echo 2 > /sys/class/pwm/pwmchip0/export')
|
|
29
|
+
|
|
30
|
+
function _buzzer_set_params() {
|
|
31
|
+
|
|
32
|
+
const period = 1.0 / (dev.buzzer.frequency as number) * 1E9
|
|
33
|
+
const duty_cycle = (dev.buzzer.volume as number) * 1.0 / 100 * period * 0.5
|
|
34
|
+
|
|
35
|
+
runShellCommand(`echo ${period} > /sys/class/pwm/pwmchip0/pwm2/period`)
|
|
36
|
+
runShellCommand(`echo ${duty_cycle} > /sys/class/pwm/pwmchip0/pwm2/duty_cycle`)
|
|
37
|
+
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
defineRule('_system_buzzer_params', {
|
|
41
|
+
whenChanged: [
|
|
42
|
+
'buzzer/frequency',
|
|
43
|
+
'buzzer/volume',
|
|
44
|
+
],
|
|
45
|
+
|
|
46
|
+
then: function () {
|
|
47
|
+
|
|
48
|
+
if (dev.buzzer.enabled) {
|
|
49
|
+
|
|
50
|
+
_buzzer_set_params()
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
defineRule('_system_buzzer_onof', {
|
|
58
|
+
whenChanged: 'buzzer/enabled',
|
|
59
|
+
then: function () {
|
|
60
|
+
|
|
61
|
+
if (dev.buzzer.enabled) {
|
|
62
|
+
|
|
63
|
+
_buzzer_set_params()
|
|
64
|
+
runShellCommand('echo 1 > /sys/class/pwm/pwmchip0/pwm2/enable')
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
|
|
69
|
+
runShellCommand('echo 0 > /sys/class/pwm/pwmchip0/pwm2/enable')
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
},
|
|
74
|
+
})
|