├── files
├── 1.png
├── 2.png
├── 3.png
├── 4.png
├── 5.png
├── 6.png
└── 7.png
├── module.json
├── examples
├── readme.md
├── add_input.json
└── reset2original.json
├── device_menu.php
├── .travis.yml
├── composer.json
├── device_schema.php
├── data
├── OpenEnergyMonitor
│ ├── emontx-HP.json
│ ├── emonpi-HEM.json
│ ├── emontx-HEM.json
│ ├── emonth.json
│ ├── emonpi-SPV2.json
│ ├── emonpi-SPV1.json
│ ├── emontx-SPV2.json
│ └── emontx-SPV1.json
├── Control
│ ├── wifirelay.json
│ └── smartplug.json
├── CircuitSetup
│ ├── circuitsetup_split-phase.json
│ ├── circuitsetup-6_channel.json
│ ├── circuitsetup-split-phase_solar.json
│ └── circuitsetup-6_channel_solar.json
├── OpenEVSE
│ └── openevse.json
├── amrm.json
└── amig.json
├── Views
├── device.js
├── device_api.php
├── device_view.php
├── device_dialog.css
├── device_dialog.php
└── device_dialog.js
├── README.md
├── device_controller.php
├── device_template.php
└── device_model.php
/files/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CircuitSetup/device/master/files/1.png
--------------------------------------------------------------------------------
/files/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CircuitSetup/device/master/files/2.png
--------------------------------------------------------------------------------
/files/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CircuitSetup/device/master/files/3.png
--------------------------------------------------------------------------------
/files/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CircuitSetup/device/master/files/4.png
--------------------------------------------------------------------------------
/files/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CircuitSetup/device/master/files/5.png
--------------------------------------------------------------------------------
/files/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CircuitSetup/device/master/files/6.png
--------------------------------------------------------------------------------
/files/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CircuitSetup/device/master/files/7.png
--------------------------------------------------------------------------------
/module.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "Device",
3 | "version" : "2.0.7"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/readme.md:
--------------------------------------------------------------------------------
1 | These are some very simple device files that show how specific functions work.
2 |
3 | It's not an exhaustive list, but hopefully it's indicative of the style.
4 |
--------------------------------------------------------------------------------
/device_menu.php:
--------------------------------------------------------------------------------
1 | _("Device Setup"),
5 | 'path' => 'device/view',
6 | 'icon' => 'device',
7 | 'order' => 'b6'
8 | );*/
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | php:
3 | - "7.0"
4 | - "7.1"
5 | - "7.2"
6 | - "7.3"
7 | env:
8 | global:
9 | - COMPOSER_DISABLE_XDEBUG_WARN=1
10 | install:
11 | - composer install
12 | script:
13 | - composer test
14 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "emoncms/device",
3 | "homepage": "https://emoncms.org",
4 | "support": {
5 | "forum": "https://community.openenergymonitor.org/"
6 | },
7 | "require-dev": {
8 | "php-parallel-lint/php-parallel-lint": "^1.2.0"
9 | },
10 | "scripts": {
11 | "test": [
12 | "parallel-lint . --exclude vendor"
13 | ]
14 | },
15 | "license": "AGPL-3.0-or-later"
16 | }
17 |
--------------------------------------------------------------------------------
/device_schema.php:
--------------------------------------------------------------------------------
1 | array('type' => 'int(11)', 'Null'=>false, 'Key'=>'PRI', 'Extra'=>'auto_increment'),
5 | 'userid' => array('type' => 'int(11)'),
6 | 'nodeid' => array('type' => 'text'),
7 | 'name' => array('type' => 'text'),
8 | 'description' => array('type' => 'text'),
9 | 'type' => array('type' => 'varchar(32)'),
10 | 'devicekey' => array('type' => 'varchar(64)'),
11 | 'time' => array('type' => 'int(10)')
12 | );
13 |
--------------------------------------------------------------------------------
/data/OpenEnergyMonitor/emontx-HP.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Heatpump Monitor",
3 | "category": "OpenEnergyMonitor",
4 | "group": "EmonTx",
5 | "description": "Heatpump Monitor firmware for EmonTx v3",
6 | "inputs": [
7 | {
8 | "name": "P1",
9 | "description": "Consumption",
10 | "processList": [
11 | {
12 | "process": "log_to_feed",
13 | "arguments": {"type": "ProcessArg::FEEDID", "value": "use" }
14 | },
15 | {
16 | "process": "power_to_kwh",
17 | "arguments": {"type": "ProcessArg::FEEDID", "value": "use_kwh" }
18 | }
19 | ]
20 | }
21 | ],
22 |
23 | "feeds": [
24 | {
25 | "name": "use",
26 | "type": "DataType::REALTIME",
27 | "engine": "Engine::PHPFINA",
28 | "interval": "10",
29 | "unit": "W"
30 | },
31 | {
32 | "name": "use_kwh",
33 | "type": "DataType::REALTIME",
34 | "engine": "Engine::PHPFINA",
35 | "interval": "10",
36 | "unit": "kWh"
37 | }
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/data/OpenEnergyMonitor/emonpi-HEM.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "HomeEnergyMonitor",
3 | "category": "OpenEnergyMonitor",
4 | "group": "EmonPi",
5 | "description": "Basic EmonPi Home Energy Monitor configuration",
6 | "inputs": [
7 | {
8 | "name": "power1",
9 | "description": "House consumption",
10 | "processList": [
11 | {
12 | "process": "log_to_feed",
13 | "arguments": { "type": "ProcessArg::FEEDID", "value": "use" }
14 | },
15 | {
16 | "process": "power_to_kwh",
17 | "arguments": { "type": "ProcessArg::FEEDID", "value": "use_kwh" }
18 | }
19 | ]
20 | }
21 | ],
22 |
23 | "feeds": [
24 | {
25 | "name": "use",
26 | "type": "DataType::REALTIME",
27 | "engine": "Engine::PHPFINA",
28 | "interval": "10",
29 | "unit": "W"
30 | },
31 | {
32 | "name": "use_kwh",
33 | "type": "DataType::REALTIME",
34 | "engine": "Engine::PHPFINA",
35 | "interval": "10",
36 | "unit": "kWh"
37 | }
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/data/OpenEnergyMonitor/emontx-HEM.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "HomeEnergyMonitor",
3 | "category": "OpenEnergyMonitor",
4 | "group": "EmonTx",
5 | "description": "Basic EmonTx v3 Home Energy Monitor configuration",
6 | "inputs": [
7 | {
8 | "name": "power1",
9 | "description": "House consumption",
10 | "processList": [
11 | {
12 | "process": "log_to_feed",
13 | "arguments": {"type": "ProcessArg::FEEDID", "value": "use" }
14 | },
15 | {
16 | "process": "power_to_kwh",
17 | "arguments": {"type": "ProcessArg::FEEDID", "value": "use_kwh" }
18 | }
19 | ]
20 | }
21 | ],
22 |
23 | "feeds": [
24 | {
25 | "name": "use",
26 | "type": "DataType::REALTIME",
27 | "engine": "Engine::PHPFINA",
28 | "interval": "10",
29 | "unit": "W"
30 | },
31 | {
32 | "name": "use_kwh",
33 | "type": "DataType::REALTIME",
34 | "engine": "Engine::PHPFINA",
35 | "interval": "10",
36 | "unit": "kWh"
37 | }
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/data/Control/wifirelay.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Wi-Fi relay",
3 | "category": "Control",
4 | "group": "MQTT",
5 | "description": "Wi-Fi relay control",
6 | "inputs": [
7 | {
8 | "name": "period",
9 | "description": "Control period",
10 | "processList": []
11 | },
12 | {
13 | "name": "end",
14 | "description": "Control end",
15 | "processList": []
16 | },
17 | {
18 | "name": "interruptible",
19 | "description": "Control interruptible",
20 | "processList": []
21 | },
22 | {
23 | "name": "status",
24 | "description": "Control status",
25 | "processList": []
26 | }
27 | ],
28 |
29 | "feeds": [],
30 |
31 | "control":
32 | {
33 | "active": {"name":"Active","type":"checkbox","default":1},
34 | "period": {"name":"Run period", "type":"time","default":0,"resolution":0.5},
35 | "end": {"name":"Complete by", "type":"time","default":0,"resolution":0.5},
36 | "repeat": {"type":"weekly-scheduler","default":[1,1,1,1,1,0,0]},
37 | "interruptible": {"name":"Ok to interrupt schedule","type":"checkbox","default":0},
38 | "runonce": {"type":"","default":true},
39 | "basic": {"type":"","default":0}
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/data/Control/smartplug.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Smartplug",
3 | "category": "Control",
4 | "group": "MQTT",
5 | "description": "Smartplug control",
6 | "inputs": [
7 | {
8 | "name": "period",
9 | "description": "Control period",
10 | "processList": []
11 | },
12 | {
13 | "name": "end",
14 | "description": "Control end",
15 | "processList": []
16 | },
17 | {
18 | "name": "interruptible",
19 | "description": "Control interruptible",
20 | "processList": []
21 | },
22 | {
23 | "name": "status",
24 | "description": "Control status",
25 | "processList": []
26 | }
27 | ],
28 |
29 | "feeds": [],
30 |
31 | "control":
32 | {
33 | "active": {"name":"Active","type":"checkbox","default":1},
34 | "period": {"name":"Run period", "type":"time","default":0,"resolution":0.5},
35 | "end": {"name":"Complete by", "type":"time","default":0,"resolution":0.5},
36 | "repeat": {"type":"weekly-scheduler","default":[1,1,1,1,1,0,0]},
37 | "interruptible": {"name":"Ok to interrupt schedule","type":"checkbox","default":0},
38 | "runonce": {"type":"","default":true},
39 | "basic": {"type":"","default":0},
40 | "signal": {"name":"Signal","type":"select","default":"carbonintensity"}
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/data/OpenEnergyMonitor/emonth.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "EmonTH",
3 | "category": "OpenEnergyMonitor",
4 | "group": "Temperature & Humidity",
5 | "description": "Automatic inputs and feeds creation for emonTH device.",
6 | "inputs": [
7 | {
8 | "name": "temperature",
9 | "description": "Temperature C",
10 | "processList": [
11 | {
12 | "process": "log_to_feed",
13 | "arguments": {"type": "ProcessArg::FEEDID", "value": "emonth_temperature" }
14 | }
15 | ]
16 | },
17 | {
18 | "name": "humidity",
19 | "description": "Humidity Rh%",
20 | "processList": [
21 | {
22 | "process": "log_to_feed",
23 | "arguments": {"type": "ProcessArg::FEEDID", "value": "emonth_humidity" }
24 | }
25 | ]
26 | }
27 | ],
28 |
29 | "feeds": [
30 | {
31 | "name": "emonth_temperature",
32 | "type": "DataType::REALTIME",
33 | "engine": "Engine::PHPFINA",
34 | "interval": "60",
35 | "unit": "°C"
36 | },
37 | {
38 | "name": "emonth_humidity",
39 | "type": "DataType::REALTIME",
40 | "engine": "Engine::PHPFINA",
41 | "interval": "60",
42 | "unit": "%"
43 | }
44 | ]
45 | }
46 |
--------------------------------------------------------------------------------
/Views/device.js:
--------------------------------------------------------------------------------
1 | var device = {
2 | 'list':function() {
3 | var result = {};
4 | $.ajax({ url: path+"device/list.json", dataType: 'json', async: false, success: function(data) {result = data;} });
5 | return result;
6 | },
7 |
8 | 'get':function(id) {
9 | var result = {};
10 | $.ajax({ url: path+"device/get.json", data: "id="+id, async: false, success: function(data) {result = data;} });
11 | return result;
12 | },
13 |
14 | 'set':function(id, fields) {
15 | var result = {};
16 | $.ajax({ url: path+"device/set.json", data: "id="+id+"&fields="+JSON.stringify(fields), async: false, success: function(data) {result = data;} });
17 | return result;
18 | },
19 |
20 | 'setNewDeviceKey':function(id) {
21 | var result = {};
22 | $.ajax({ url: path+"device/setnewdevicekey.json", data: "id="+id, async: false, success: function(data) {result = data;} });
23 | return result;
24 | },
25 |
26 | 'remove':function(id) {
27 | var result = {};
28 | $.ajax({ url: path+"device/delete.json", data: "id="+id, async: false, success: function(data) {result = data;} });
29 | return result;
30 | },
31 |
32 | 'create':function(nodeid, name, description, type) {
33 | var result = {};
34 | $.ajax({ url: path+"device/create.json", data: "nodeid="+nodeid+"&name="+name+"&description="+description+"&type="+type, async: false, success: function(data) {result = data;} });
35 | return result;
36 | },
37 |
38 | 'init':function(id, template) {
39 | var result = {};
40 | $.ajax({ url: path+"device/init.json?id="+id, type: 'POST', data: "template="+JSON.stringify(template), dataType: 'json', async: false, success: function(data) {result = data;} });
41 | return result;
42 | },
43 |
44 | 'prepareTemplate':function(id) {
45 | var result = {};
46 | $.ajax({ url: path+"device/template/prepare.json", data: "id="+id, dataType: 'json', async: false, success: function(data) {result = data;} });
47 | return result;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/examples/add_input.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "add_input",
3 | "category": "Examples",
4 | "group": "Simple Functions",
5 | "description": "Show how two inputs can be added",
6 | "inputs": [
7 | {
8 | "name": "inputA",
9 | "description": "inputA",
10 | "processList": [
11 | {
12 | "process": "log_to_feed",
13 | "arguments": {
14 | "type": "ProcessArg::FEEDID",
15 | "value": "inputA"
16 | }
17 | }
18 | ]
19 | },
20 | {
21 | "name": "inputB",
22 | "description": "inputB",
23 | "processList": [
24 | {
25 | "process": "log_to_feed",
26 | "arguments": {
27 | "type": "ProcessArg::FEEDID",
28 | "value": "inputB"
29 | }
30 | },
31 | {
32 | "process": "add_input",
33 | "arguments": {
34 | "type": "ProcessArg::INPUTID",
35 | "value": "inputA"
36 | }
37 | },
38 | {
39 | "process": "log_to_feed",
40 | "arguments": {
41 | "type": "ProcessArg::FEEDID",
42 | "value": "total"
43 | }
44 | }
45 | ]
46 | }
47 | ],
48 | "feeds": [
49 | {
50 | "name": "inputA",
51 | "type": "DataType::REALTIME",
52 | "engine": "Engine::PHPFINA",
53 | "interval": "60"
54 | },
55 | {
56 | "name": "inputB",
57 | "type": "DataType::REALTIME",
58 | "engine": "Engine::PHPFINA",
59 | "interval": "60"
60 | },
61 | {
62 | "name": "total",
63 | "type": "DataType::REALTIME",
64 | "engine": "Engine::PHPFINA",
65 | "interval": "60"
66 | }
67 | ]
68 | }
69 |
--------------------------------------------------------------------------------
/examples/reset2original.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reset2original",
3 | "category": "Examples",
4 | "group": "Simple Functions",
5 | "description": "Show how the reset gives the original value to an input to a feed",
6 | "inputs": [
7 | {
8 | "name": "inputA",
9 | "description": "inputA",
10 | "processList": [
11 | {
12 | "process": "log_to_feed",
13 | "arguments": {
14 | "type": "ProcessArg::FEEDID",
15 | "value": "inputA"
16 | }
17 | }
18 | ]
19 | },
20 | {
21 | "name": "inputB",
22 | "description": "inputB",
23 | "processList": [
24 | {
25 | "process": "log_to_feed",
26 | "arguments": {
27 | "type": "ProcessArg::FEEDID",
28 | "value": "inputB"
29 | }
30 | },
31 | {
32 | "process": "add_input",
33 | "arguments": {
34 | "type": "ProcessArg::INPUTID",
35 | "value": "inputA"
36 | }
37 | },
38 | {
39 | "process": "log_to_feed",
40 | "arguments": {
41 | "type": "ProcessArg::FEEDID",
42 | "value": "total"
43 | }
44 | },
45 | {
46 | "process": "reset2original",
47 | "arguments": {
48 | "type": "ProcessArg::NONE"
49 | }
50 | },
51 | {
52 | "process": "log_to_feed",
53 | "arguments": {
54 | "type": "ProcessArg::FEEDID",
55 | "value": "resetOfInputB"
56 | }
57 | }
58 | ]
59 | }
60 | ],
61 | "feeds": [
62 | {
63 | "name": "inputA",
64 | "type": "DataType::REALTIME",
65 | "engine": "Engine::PHPFINA",
66 | "interval": "60"
67 | },
68 | {
69 | "name": "inputB",
70 | "type": "DataType::REALTIME",
71 | "engine": "Engine::PHPFINA",
72 | "interval": "60"
73 | },
74 | {
75 | "name": "total",
76 | "type": "DataType::REALTIME",
77 | "engine": "Engine::PHPFINA",
78 | "interval": "60"
79 | },
80 | {
81 | "name": "resetOfInputB",
82 | "type": "DataType::REALTIME",
83 | "engine": "Engine::PHPFINA",
84 | "interval": "60"
85 | }
86 | ]
87 | }
--------------------------------------------------------------------------------
/data/OpenEnergyMonitor/emonpi-SPV2.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Solar PV Type 2",
3 | "category": "OpenEnergyMonitor",
4 | "group": "EmonPi",
5 | "description": "EmonPi Solar PV Type 2 template",
6 | "inputs": [
7 | {
8 | "name": "power1",
9 | "description": "House consumption",
10 | "processList": [
11 | {
12 | "process": "allowpositive",
13 | "arguments": {"type": "ProcessArg::NONE"}
14 | },
15 | {
16 | "process": "log_to_feed",
17 | "arguments": {"type": "ProcessArg::FEEDID", "value": "import" }
18 | },
19 | {
20 | "process": "power_to_kwh",
21 | "arguments": {"type": "ProcessArg::FEEDID", "value": "import_kwh" }
22 | }
23 | ]
24 | },
25 | {
26 | "name": "power2",
27 | "description": "Solar generation",
28 | "processList": [
29 | {
30 | "process": "log_to_feed",
31 | "arguments": {"type": "ProcessArg::FEEDID", "value": "solar" }
32 | },
33 | {
34 | "process": "power_to_kwh",
35 | "arguments": {"type": "ProcessArg::FEEDID", "value": "solar_kwh" }
36 | },
37 | {
38 | "process": "add_input",
39 | "arguments": {"type": "ProcessArg::INPUTID", "value": "power1" }
40 | },
41 | {
42 | "process": "log_to_feed",
43 | "arguments": {"type": "ProcessArg::FEEDID", "value": "use" }
44 | },
45 | {
46 | "process": "power_to_kwh",
47 | "arguments": {"type": "ProcessArg::FEEDID", "value": "use_kwh" }
48 | }
49 | ]
50 | }
51 | ],
52 |
53 | "feeds": [
54 | {
55 | "name": "use",
56 | "type": "DataType::REALTIME",
57 | "engine": "Engine::PHPFINA",
58 | "interval": "10",
59 | "unit": "W"
60 | },
61 | {
62 | "name": "use_kwh",
63 | "type": "DataType::REALTIME",
64 | "engine": "Engine::PHPFINA",
65 | "interval": "10",
66 | "unit": "kWh"
67 | },
68 | {
69 | "name": "solar",
70 | "type": "DataType::REALTIME",
71 | "engine": "Engine::PHPFINA",
72 | "interval": "10",
73 | "unit": "W"
74 | },
75 | {
76 | "name": "solar_kwh",
77 | "type": "DataType::REALTIME",
78 | "engine": "Engine::PHPFINA",
79 | "interval": "10",
80 | "unit": "kWh"
81 | },
82 | {
83 | "name": "import",
84 | "type": "DataType::REALTIME",
85 | "engine": "Engine::PHPFINA",
86 | "interval": "10",
87 | "unit": "W"
88 | },
89 | {
90 | "name": "import_kwh",
91 | "type": "DataType::REALTIME",
92 | "engine": "Engine::PHPFINA",
93 | "interval": "10",
94 | "unit": "kWh"
95 | }
96 | ]
97 | }
98 |
--------------------------------------------------------------------------------
/data/OpenEnergyMonitor/emonpi-SPV1.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Solar PV Type 1",
3 | "category": "OpenEnergyMonitor",
4 | "group": "EmonPi",
5 | "description": "EmonPi Solar PV Type 1 template",
6 | "inputs": [
7 | {
8 | "name": "power1",
9 | "description": "House consumption",
10 | "processList": [
11 | {
12 | "process": "log_to_feed",
13 | "arguments": {"type": "ProcessArg::FEEDID", "value": "use" }
14 | },
15 | {
16 | "process": "power_to_kwh",
17 | "arguments": {"type": "ProcessArg::FEEDID", "value": "use_kwh" }
18 | },
19 | {
20 | "process": "subtract_input",
21 | "arguments": {"type": "ProcessArg::INPUTID", "value": "power2" }
22 | },
23 | {
24 | "process": "allowpositive",
25 | "arguments": {"type": "ProcessArg::NONE"}
26 | },
27 | {
28 | "process": "log_to_feed",
29 | "arguments": {"type": "ProcessArg::FEEDID", "value": "import" }
30 | },
31 | {
32 | "process": "power_to_kwh",
33 | "arguments": {"type": "ProcessArg::FEEDID", "value": "import_kwh" }
34 | }
35 | ]
36 | },
37 | {
38 | "name": "power2",
39 | "description": "Solar generation",
40 | "processList": [
41 | {
42 | "process": "log_to_feed",
43 | "arguments": {"type": "ProcessArg::FEEDID", "value": "solar" }
44 | },
45 | {
46 | "process": "power_to_kwh",
47 | "arguments": {"type": "ProcessArg::FEEDID", "value": "solar_kwh" }
48 | }
49 | ]
50 | }
51 | ],
52 |
53 | "feeds": [
54 | {
55 | "name": "use",
56 | "type": "DataType::REALTIME",
57 | "engine": "Engine::PHPFINA",
58 | "interval": "10",
59 | "unit": "W"
60 | },
61 | {
62 | "name": "use_kwh",
63 | "type": "DataType::REALTIME",
64 | "engine": "Engine::PHPFINA",
65 | "interval": "10",
66 | "unit": "kWh"
67 | },
68 | {
69 | "name": "solar",
70 | "type": "DataType::REALTIME",
71 | "engine": "Engine::PHPFINA",
72 | "interval": "10",
73 | "unit": "W"
74 | },
75 | {
76 | "name": "solar_kwh",
77 | "type": "DataType::REALTIME",
78 | "engine": "Engine::PHPFINA",
79 | "interval": "10",
80 | "unit": "kWh"
81 | },
82 | {
83 | "name": "import",
84 | "type": "DataType::REALTIME",
85 | "engine": "Engine::PHPFINA",
86 | "interval": "10",
87 | "unit": "W"
88 | },
89 | {
90 | "name": "import_kwh",
91 | "type": "DataType::REALTIME",
92 | "engine": "Engine::PHPFINA",
93 | "interval": "10",
94 | "unit": "kWh"
95 | }
96 | ]
97 | }
98 |
--------------------------------------------------------------------------------
/data/OpenEnergyMonitor/emontx-SPV2.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Solar PV Type 2",
3 | "category": "OpenEnergyMonitor",
4 | "group": "EmonTx",
5 | "description": "EmonTx Solar PV Type 2 template using CT4 for solar",
6 | "inputs": [
7 | {
8 | "name": "power1",
9 | "description": "House consumption",
10 | "processList": [
11 | {
12 | "process": "allowpositive",
13 | "arguments": {"type": "ProcessArg::NONE"}
14 | },
15 | {
16 | "process": "log_to_feed",
17 | "arguments": {"type": "ProcessArg::FEEDID", "value": "import" }
18 | },
19 | {
20 | "process": "power_to_kwh",
21 | "arguments": {"type": "ProcessArg::FEEDID", "value": "import_kwh" }
22 | }
23 | ]
24 | },
25 | {
26 | "name": "power4",
27 | "description": "Solar generation",
28 | "processList": [
29 | {
30 | "process": "log_to_feed",
31 | "arguments": {"type": "ProcessArg::FEEDID", "value": "solar" }
32 | },
33 | {
34 | "process": "power_to_kwh",
35 | "arguments": {"type": "ProcessArg::FEEDID", "value": "solar_kwh" }
36 | },
37 | {
38 | "process": "add_input",
39 | "arguments": {"type": "ProcessArg::INPUTID", "value": "power1" }
40 | },
41 | {
42 | "process": "log_to_feed",
43 | "arguments": {"type": "ProcessArg::FEEDID", "value": "use" }
44 | },
45 | {
46 | "process": "power_to_kwh",
47 | "arguments": {"type": "ProcessArg::FEEDID", "value": "use_kwh" }
48 | }
49 | ]
50 | }
51 | ],
52 |
53 | "feeds": [
54 | {
55 | "name": "use",
56 | "type": "DataType::REALTIME",
57 | "engine": "Engine::PHPFINA",
58 | "interval": "10",
59 | "unit": "W"
60 | },
61 | {
62 | "name": "use_kwh",
63 | "type": "DataType::REALTIME",
64 | "engine": "Engine::PHPFINA",
65 | "interval": "10",
66 | "unit": "kWh"
67 | },
68 | {
69 | "name": "solar",
70 | "type": "DataType::REALTIME",
71 | "engine": "Engine::PHPFINA",
72 | "interval": "10",
73 | "unit": "W"
74 | },
75 | {
76 | "name": "solar_kwh",
77 | "type": "DataType::REALTIME",
78 | "engine": "Engine::PHPFINA",
79 | "interval": "10",
80 | "unit": "kWh"
81 | },
82 | {
83 | "name": "import",
84 | "type": "DataType::REALTIME",
85 | "engine": "Engine::PHPFINA",
86 | "interval": "10",
87 | "unit": "W"
88 | },
89 | {
90 | "name": "import_kwh",
91 | "type": "DataType::REALTIME",
92 | "engine": "Engine::PHPFINA",
93 | "interval": "10",
94 | "unit": "kWh"
95 | }
96 | ]
97 | }
98 |
--------------------------------------------------------------------------------
/data/OpenEnergyMonitor/emontx-SPV1.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Solar PV Type 1",
3 | "category": "OpenEnergyMonitor",
4 | "group": "EmonTx",
5 | "description": "EmonTx Solar PV Type 1 template using CT4 for solar",
6 | "inputs": [
7 | {
8 | "name": "power1",
9 | "description": "House consumption",
10 | "processList": [
11 | {
12 | "process": "log_to_feed",
13 | "arguments": {"type": "ProcessArg::FEEDID", "value": "use" }
14 | },
15 | {
16 | "process": "power_to_kwh",
17 | "arguments": {"type": "ProcessArg::FEEDID", "value": "use_kwh" }
18 | },
19 | {
20 | "process": "subtract_input",
21 | "arguments": {"type": "ProcessArg::INPUTID", "value": "power4" }
22 | },
23 | {
24 | "process": "allowpositive",
25 | "arguments": {"type": "ProcessArg::NONE"}
26 | },
27 | {
28 | "process": "log_to_feed",
29 | "arguments": {"type": "ProcessArg::FEEDID", "value": "import" }
30 | },
31 | {
32 | "process": "power_to_kwh",
33 | "arguments": {"type": "ProcessArg::FEEDID", "value": "import_kwh" }
34 | }
35 | ]
36 | },
37 | {
38 | "name": "power4",
39 | "description": "Solar generation",
40 | "processList": [
41 | {
42 | "process": "log_to_feed",
43 | "arguments": {"type": "ProcessArg::FEEDID", "value": "solar" }
44 | },
45 | {
46 | "process": "power_to_kwh",
47 | "arguments": {"type": "ProcessArg::FEEDID", "value": "solar_kwh" }
48 | }
49 | ]
50 | }
51 | ],
52 |
53 | "feeds": [
54 | {
55 | "name": "use",
56 | "type": "DataType::REALTIME",
57 | "engine": "Engine::PHPFINA",
58 | "interval": "10",
59 | "unit": "W"
60 | },
61 | {
62 | "name": "use_kwh",
63 | "type": "DataType::REALTIME",
64 | "engine": "Engine::PHPFINA",
65 | "interval": "10",
66 | "unit": "kWh"
67 | },
68 | {
69 | "name": "solar",
70 | "type": "DataType::REALTIME",
71 | "engine": "Engine::PHPFINA",
72 | "interval": "10",
73 | "unit": "W"
74 | },
75 | {
76 | "name": "solar_kwh",
77 | "type": "DataType::REALTIME",
78 | "engine": "Engine::PHPFINA",
79 | "interval": "10",
80 | "unit": "kWh"
81 | },
82 | {
83 | "name": "import",
84 | "type": "DataType::REALTIME",
85 | "engine": "Engine::PHPFINA",
86 | "interval": "10",
87 | "unit": "W"
88 | },
89 | {
90 | "name": "import_kwh",
91 | "type": "DataType::REALTIME",
92 | "engine": "Engine::PHPFINA",
93 | "interval": "10",
94 | "unit": "kWh"
95 | }
96 | ]
97 | }
98 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Emoncms Device module
2 |
3 | 
4 |
5 | - Auto-configuration of inputs and feeds from pre-defined device templates
6 | - Device templates available for all standard OpenEnergyMonitor hardware units
7 | - Device level access keys
8 |
9 | Original author: [Chaveiro](https://github.com/chaveiro/)
10 | Contributions from: [Trystan Lea](http://github.com/trystanlea), [Adminde](https://github.com/adminde)
11 |
12 | ## Installation
13 |
14 | The following steps document the installation of the device module on a stock emonpi/emonbase running the latest emonSD image.
15 |
16 | After logging in via SSH, place the pi in write mode:
17 |
18 | rpi-rw
19 |
20 | Navigate to the emoncms Modules folder:
21 |
22 | cd /var/www/emoncms/Modules
23 |
24 | Clone the device module into the modules folder using git:
25 |
26 | git clone https://github.com/emoncms/device.git
27 |
28 | Login to emoncms on your emonpi/emonbase, navigate to Setup > Administration, Update the emoncms database by running 'Update & Check' under the Update database section.
29 |
30 | ## Using the device module
31 |
32 | **Auto-configration**
33 |
34 | One of the useful features of the device module is automatic configuration of inputs and feeds according to pre-defined device templates.
35 |
36 | The following is an example of automatic configuration of an emonpi for use as a Type 1 Solar PV monitor.
37 |
38 | With the EmonPi and CT sensors connected up the emoncms inputs list running on the EmonPi should show a list of inputs like so:
39 |
40 | 
41 |
42 | These are unconfigured at this point and no data is being recorded. The manual setup for a Type 1 Solar PV setup is documented here [OpenEnergyMonitor Guide: SolarPV](https://guide.openenergymonitor.org/applications/solar-pv), but we are going to use the device module here to automatically setup the inputs and feeds.
43 |
44 | Navigate to the device module, click on Setup > Device Setup, which should bring up the following page:
45 |
46 | 
47 |
48 | Click on 'New device', this will bring up the 'Configure Device' window. The left-hand pane lists the device templates available.
49 |
50 | Click on OpenEnergyMonitor > EmonPi > Solar PV Type 1 to select this template.
51 |
52 | Enter the nodename emonpi in both the 'Node' and 'Name' fields, set a location as you wish or leave blank.
53 |
54 | Click save to continue.
55 |
56 | 
57 |
58 | When a new device is created, it will automatically be initialized and all input processes and feeds will be created, according to the pre-defined template. If the template needs to be applied again, as e.g. a process list was experimented on and altered, or single feeds deleted, it may be re-initialized. Clicking the cycling arrows 'refresh icon' will bring up the initialization window.
59 |
60 | 
61 |
62 | Click 'Initialize' to re-initialize the device and to confirm the creation of missing feeds and inputs, as well as the reset of configured processes to their original state.
63 |
64 | 
65 |
66 | The input list will now show the input processes created:
67 |
68 | 
69 |
70 | and the feeds page the feeds created:
71 |
72 | 
73 |
74 | ## Development
75 |
76 | See: [Development: Devices, Inputs and Feeds in emoncms](https://community.openenergymonitor.org/t/development-devices-inputs-and-feeds-in-emoncms/4281/17)
77 |
--------------------------------------------------------------------------------
/data/CircuitSetup/circuitsetup_split-phase.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Split Single Phase Meter",
3 | "category": "CircuitSetup",
4 | "group": "Energy Meters",
5 | "description": "CircuitSetup Split Single Phase Energy Meter",
6 | "inputs": [
7 | {
8 | "name": "V1",
9 | "description": "Voltage 1",
10 | "processList": [
11 | {
12 | "process": "log_to_feed",
13 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Voltage 1" }
14 | }
15 | ]
16 | },
17 | {
18 | "name": "V2",
19 | "description": "Voltage 2",
20 | "processList": [
21 | {
22 | "process": "log_to_feed",
23 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Voltage 2" }
24 | }
25 | ]
26 | },
27 | {
28 | "name": "totV",
29 | "description": "Total Voltage",
30 | "processList": [
31 | {
32 | "process": "log_to_feed",
33 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Total Voltage" }
34 | }
35 | ]
36 | },
37 | {
38 | "name": "CT1",
39 | "description": "Current 1",
40 | "processList": [
41 | {
42 | "process": "log_to_feed",
43 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Current 1" }
44 | }
45 | ]
46 | },
47 | {
48 | "name": "CT2",
49 | "description": "Current 2",
50 | "processList": [
51 | {
52 | "process": "log_to_feed",
53 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Current 2" }
54 | }
55 | ]
56 | },
57 | {
58 | "name": "totI",
59 | "description": "Total Current",
60 | "processList": [
61 | {
62 | "process": "log_to_feed",
63 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Total Current" }
64 | }
65 | ]
66 | },
67 | {
68 | "name": "PF",
69 | "description": "Power Factor",
70 | "processList": [
71 | {
72 | "process": "log_to_feed",
73 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Power Factor" }
74 | }
75 | ]
76 | },
77 | {
78 | "name": "temp",
79 | "description": "Temp",
80 | "processList": [
81 | {
82 | "process": "log_to_feed",
83 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Temp" }
84 | }
85 | ]
86 | },
87 | {
88 | "name": "freq",
89 | "description": "Frequency",
90 | "processList": [
91 | {
92 | "process": "log_to_feed",
93 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Frequency" }
94 | }
95 | ]
96 | },
97 | {
98 | "name": "W",
99 | "description": "Total Watts",
100 | "processList": [
101 | {
102 | "process": "log_to_feed",
103 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Total Watts" }
104 | },
105 | {
106 | "process": "power_to_kwh",
107 | "arguments": {"type": "ProcessArg::FEEDID", "value": "kWh" }
108 | }
109 | ]
110 | }
111 | ],
112 |
113 | "feeds": [
114 | {
115 | "name": "Voltage 1",
116 | "type": "DataType::REALTIME",
117 | "engine": "Engine::PHPFINA",
118 | "interval": "10",
119 | "unit": "V"
120 | },
121 | {
122 | "name": "Voltage 2",
123 | "type": "DataType::REALTIME",
124 | "engine": "Engine::PHPFINA",
125 | "interval": "10",
126 | "unit": "V"
127 | },
128 | {
129 | "name": "Total Voltage",
130 | "type": "DataType::REALTIME",
131 | "engine": "Engine::PHPFINA",
132 | "interval": "10",
133 | "unit": "V"
134 | },
135 | {
136 | "name": "Current 1",
137 | "type": "DataType::REALTIME",
138 | "engine": "Engine::PHPFINA",
139 | "interval": "10",
140 | "unit": "A"
141 | },
142 | {
143 | "name": "Current 2",
144 | "type": "DataType::REALTIME",
145 | "engine": "Engine::PHPFINA",
146 | "interval": "10",
147 | "unit": "A"
148 | },
149 | {
150 | "name": "Total Current",
151 | "type": "DataType::REALTIME",
152 | "engine": "Engine::PHPFINA",
153 | "interval": "10",
154 | "unit": "A"
155 | },
156 | {
157 | "name": "Total Watts",
158 | "type": "DataType::REALTIME",
159 | "engine": "Engine::PHPFINA",
160 | "interval": "10",
161 | "unit": "W"
162 | },
163 | {
164 | "name": "kWh",
165 | "type": "DataType::REALTIME",
166 | "engine": "Engine::PHPFINA",
167 | "interval": "10",
168 | "unit": "kWh"
169 | },
170 | {
171 | "name": "Power Factor",
172 | "type": "DataType::REALTIME",
173 | "engine": "Engine::PHPFINA",
174 | "interval": "10",
175 | "unit": ""
176 | },
177 | {
178 | "name": "Temp",
179 | "type": "DataType::REALTIME",
180 | "engine": "Engine::PHPFINA",
181 | "interval": "10",
182 | "unit": "°C"
183 | },
184 | {
185 | "name": "Frequency",
186 | "type": "DataType::REALTIME",
187 | "engine": "Engine::PHPFINA",
188 | "interval": "10",
189 | "unit": ""
190 | }
191 | ]
192 | }
193 |
--------------------------------------------------------------------------------
/data/OpenEVSE/openevse.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Default",
3 | "category": "EVSE",
4 | "group": "OpenEVSE",
5 | "description": "OpenEVSE / EmonEVSE auto configuration",
6 | "inputs": [
7 | {
8 | "name": "amp",
9 | "description": "Real-time charging current",
10 | "processList": [
11 | {
12 | "process": "scale",
13 | "arguments": { "type": "ProcessArg::VALUE", "value": 0.001 }
14 | },
15 | {
16 | "process": "log_to_feed",
17 | "arguments": { "type": "ProcessArg::FEEDID", "value": "ev_current" }
18 | },
19 | {
20 | "process": "scale",
21 | "arguments": { "type": "ProcessArg::VALUE", "value": 230 }
22 | },
23 | {
24 | "process": "log_to_feed",
25 | "arguments": { "type": "ProcessArg::FEEDID", "value": "ev_power" }
26 | }
27 |
28 |
29 | ]
30 | },
31 | {
32 | "name": "wh",
33 | "description": "Cumulative Energy",
34 | "processList": [
35 | {
36 | "process": "scale",
37 | "arguments": { "type": "ProcessArg::VALUE", "value": 0.001 }
38 | },
39 | {
40 | "process": "wh_accumulator",
41 | "arguments": { "type": "ProcessArg::FEEDID", "value": "ev_energy" }
42 | }
43 | ]
44 | },
45 | {
46 | "name": "temp1",
47 | "description": "EVSE internal temperature",
48 | "processList": [
49 | {
50 | "process": "scale",
51 | "arguments": { "type": "ProcessArg::VALUE", "value": 0.1 }
52 | },
53 | {
54 | "process": "log_to_feed",
55 | "arguments": { "type": "ProcessArg::FEEDID", "value": "ev_temperature" }
56 | }
57 | ]
58 | },
59 | {
60 | "name": "state",
61 | "description": "State",
62 | "processList": [
63 | {
64 | "process": "log_to_feed",
65 | "arguments": { "type": "ProcessArg::FEEDID", "value": "ev_state" }
66 | }
67 | ]
68 | },
69 | {
70 | "name": "pilot",
71 | "description": "Pilot",
72 | "processList": [
73 | {
74 | "process": "log_to_feed",
75 | "arguments": { "type": "ProcessArg::FEEDID", "value": "ev_pilot" }
76 | }
77 | ]
78 | },
79 | {
80 | "name": "divertmode",
81 | "description": "Divert Mode state",
82 | "processList": [
83 | {
84 | "process": "log_to_feed",
85 | "arguments": { "type": "ProcessArg::FEEDID", "value": "ev_divertmode_state" }
86 | }
87 | ]
88 | }
89 | ],
90 |
91 | "feeds": [
92 | {
93 | "name": "ev_current",
94 | "type": "DataType::REALTIME",
95 | "engine": "Engine::PHPFINA",
96 | "interval": "30",
97 | "unit": "A"
98 | },
99 | {
100 | "name": "ev_power",
101 | "type": "DataType::REALTIME",
102 | "engine": "Engine::PHPFINA",
103 | "interval": "30",
104 | "unit": "W"
105 | },
106 | {
107 | "name": "ev_energy",
108 | "type": "DataType::REALTIME",
109 | "engine": "Engine::PHPFINA",
110 | "interval": "30",
111 | "unit": "kWh"
112 | },
113 | {
114 | "name": "ev_temperature",
115 | "type": "DataType::REALTIME",
116 | "engine": "Engine::PHPFINA",
117 | "interval": "30",
118 | "unit": "°C"
119 | },
120 | {
121 | "name": "ev_state",
122 | "type": "DataType::REALTIME",
123 | "engine": "Engine::PHPFINA",
124 | "interval": "30",
125 | "unit": ""
126 | },
127 | {
128 | "name": "ev_pilot",
129 | "type": "DataType::REALTIME",
130 | "engine": "Engine::PHPFINA",
131 | "interval": "30",
132 | "unit": "A"
133 | },
134 | {
135 | "name": "ev_divertmode_state",
136 | "type": "DataType::REALTIME",
137 | "engine": "Engine::PHPFINA",
138 | "interval": "30",
139 | "unit": ""
140 | }
141 | ],
142 |
143 | "control":
144 | {
145 | "active": {"name":"Active","type":"checkbox","default":1},
146 | "period": {"name":"Run period", "type":"time","default":0,"resolution":0.5},
147 | "end": {"name":"Complete by", "type":"time","default":0,"resolution":0.5},
148 | "repeat": {"type":"weekly-scheduler","default":[1,1,1,1,1,0,0]},
149 | "interruptible": {"name":"Ok to interrupt schedule","type":"checkbox","default":0},
150 | "runonce": {"type":"","default":true},
151 | "basic": {"type":"","default":0},
152 | "signal": {"name":"Signal","type":"select","default":"carbonintensity"}
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/Views/device_api.php:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
31 |
32 |
33 |
37 |
38 |
39 | .json'); ?>
40 |
41 |
42 |
50 |
51 |
52 |
57 |
58 |
59 |
66 |
67 |
68 |
69 | \'\\Modules\\device\\data\\*.json\''); ?>
70 |
71 |
72 |
--------------------------------------------------------------------------------
/data/amrm.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "AMRM",
3 | "category": "Datalogger",
4 | "group": "ArchiMetrics",
5 | "description": "INPUTS & FEEDS for AMRM Logger from archimetrics.co.uk",
6 | "inputs": [{
7 | "name": "n1T",
8 | "description": "N1 Temperature",
9 | "processList": [{
10 | "process": "log_to_feed",
11 | "arguments": {
12 | "type": "ProcessArg::FEEDID",
13 | "value": "n1T"
14 | }
15 | }]
16 | },
17 | {
18 | "name": "n2T",
19 | "description": "N2 Temperature",
20 | "processList": [{
21 | "process": "log_to_feed",
22 | "arguments": {
23 | "type": "ProcessArg::FEEDID",
24 | "value": "n2T"
25 | }
26 | }]
27 | },
28 | {
29 | "name": "n3T",
30 | "description": "N3 Temperature",
31 | "processList": [{
32 | "process": "log_to_feed",
33 | "arguments": {
34 | "type": "ProcessArg::FEEDID",
35 | "value": "n3T"
36 | }
37 | }]
38 | },
39 | {
40 | "name": "n4T",
41 | "description": "N4 Temperature",
42 | "processList": [{
43 | "process": "log_to_feed",
44 | "arguments": {
45 | "type": "ProcessArg::FEEDID",
46 | "value": "n4T"
47 | }
48 | }]
49 | },
50 | {
51 | "name": "n1IM",
52 | "description": "N1 IM",
53 | "processList": [{
54 | "process": "log_to_feed",
55 | "arguments": {
56 | "type": "ProcessArg::FEEDID",
57 | "value": "n1IM"
58 | }
59 | }]
60 | },
61 | {
62 | "name": "n2IM",
63 | "description": "N2 IM",
64 | "processList": [{
65 | "process": "log_to_feed",
66 | "arguments": {
67 | "type": "ProcessArg::FEEDID",
68 | "value": "n2IM"
69 | }
70 | }]
71 | },
72 | {
73 | "name": "n3IM",
74 | "description": "N3 IM",
75 | "processList": [{
76 | "process": "log_to_feed",
77 | "arguments": {
78 | "type": "ProcessArg::FEEDID",
79 | "value": "n3IM"
80 | }
81 | }]
82 | },
83 | {
84 | "name": "n4IM",
85 | "description": "N4 IM",
86 | "processList": [{
87 | "process": "log_to_feed",
88 | "arguments": {
89 | "type": "ProcessArg::FEEDID",
90 | "value": "n4IM"
91 | }
92 | }]
93 | },
94 | {
95 | "name": "n1DM",
96 | "description": "N1 DM",
97 | "processList": [{
98 | "process": "log_to_feed",
99 | "arguments": {
100 | "type": "ProcessArg::FEEDID",
101 | "value": "n1DM"
102 | }
103 | }]
104 | },
105 | {
106 | "name": "n2DM",
107 | "description": "N2 DM",
108 | "processList": [{
109 | "process": "log_to_feed",
110 | "arguments": {
111 | "type": "ProcessArg::FEEDID",
112 | "value": "n2DM"
113 | }
114 | }]
115 | },
116 | {
117 | "name": "n3DM",
118 | "description": "N3 DM",
119 | "processList": [{
120 | "process": "log_to_feed",
121 | "arguments": {
122 | "type": "ProcessArg::FEEDID",
123 | "value": "n3DM"
124 | }
125 | }]
126 | },
127 | {
128 | "name": "n4DM",
129 | "description": "N4 DM",
130 | "processList": [{
131 | "process": "log_to_feed",
132 | "arguments": {
133 | "type": "ProcessArg::FEEDID",
134 | "value": "n4DM"
135 | }
136 | }]
137 | },
138 | {
139 | "name": "iTA",
140 | "description": "Internal Temperature Air",
141 | "processList": [{
142 | "process": "log_to_feed",
143 | "arguments": {
144 | "type": "ProcessArg::FEEDID",
145 | "value": "iTA"
146 | }
147 | }]
148 | },
149 | {
150 | "name": "iRH",
151 | "description": "Internal RH",
152 | "processList": [{
153 | "process": "log_to_feed",
154 | "arguments": {
155 | "type": "ProcessArg::FEEDID",
156 | "value": "iRH"
157 | }
158 | }]
159 | },
160 | {
161 | "name": "iDP",
162 | "description": "Internal Dew Point",
163 | "processList": [{
164 | "process": "log_to_feed",
165 | "arguments": {
166 | "type": "ProcessArg::FEEDID",
167 | "value": "iDP"
168 | }
169 | }]
170 | },
171 | {
172 | "name": "SYS",
173 | "description": "System Temperature",
174 | "processList": [{
175 | "process": "log_to_feed",
176 | "arguments": {
177 | "type": "ProcessArg::FEEDID",
178 | "value": "SYS"
179 | }
180 | }]
181 | }],
182 | "feeds": [{
183 | "name": "n1T",
184 | "type": "DataType::REALTIME",
185 | "engine": "Engine::PHPFINA",
186 | "interval": "10"
187 | },
188 | {
189 | "name": "n2T",
190 | "type": "DataType::REALTIME",
191 | "engine": "Engine::PHPFINA",
192 | "interval": "10"
193 |
194 | },
195 | {
196 | "name": "n3T",
197 | "type": "DataType::REALTIME",
198 | "engine": "Engine::PHPFINA",
199 | "interval": "10"
200 | },
201 | {
202 | "name": "n4T",
203 | "type": "DataType::REALTIME",
204 | "engine": "Engine::PHPFINA",
205 | "interval": "10"
206 | },
207 | {
208 | "name": "n1IM",
209 | "type": "DataType::REALTIME",
210 | "engine": "Engine::PHPFINA",
211 | "interval": "10"
212 | },
213 | {
214 | "name": "n2IM",
215 | "type": "DataType::REALTIME",
216 | "engine": "Engine::PHPFINA",
217 | "interval": "10"
218 | },
219 | {
220 | "name": "n3IM",
221 | "type": "DataType::REALTIME",
222 | "engine": "Engine::PHPFINA",
223 | "interval": "10"
224 | },
225 | {
226 | "name": "n4IM",
227 | "type": "DataType::REALTIME",
228 | "engine": "Engine::PHPFINA",
229 | "interval": "10"
230 | },
231 | {
232 | "name": "n1DM",
233 | "type": "DataType::REALTIME",
234 | "engine": "Engine::PHPFINA",
235 | "interval": "10"
236 | },
237 | {
238 | "name": "n2DM",
239 | "type": "DataType::REALTIME",
240 | "engine": "Engine::PHPFINA",
241 | "interval": "10"
242 | },
243 | {
244 | "name": "n3DM",
245 | "type": "DataType::REALTIME",
246 | "engine": "Engine::PHPFINA",
247 | "interval": "10"
248 | },
249 | {
250 | "name": "n4DM",
251 | "type": "DataType::REALTIME",
252 | "engine": "Engine::PHPFINA",
253 | "interval": "10"
254 | },
255 | {
256 | "name": "iTA",
257 | "type": "DataType::REALTIME",
258 | "engine": "Engine::PHPFINA",
259 | "interval": "10"
260 | },
261 | {
262 | "name": "iRH",
263 | "type": "DataType::REALTIME",
264 | "engine": "Engine::PHPFINA",
265 | "interval": "10"
266 | },
267 | {
268 | "name": "iDP",
269 | "type": "DataType::REALTIME",
270 | "engine": "Engine::PHPFINA",
271 | "interval": "10"
272 | },
273 | {
274 | "name": "SYS",
275 | "type": "DataType::REALTIME",
276 | "engine": "Engine::PHPFINA",
277 | "interval": "10"
278 | }]
279 | }
280 |
--------------------------------------------------------------------------------
/data/CircuitSetup/circuitsetup-6_channel.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "6 Channel Meter",
3 | "category": "CircuitSetup",
4 | "group": "Energy Meters",
5 | "description": "CircuitSetup 6 Channel",
6 | "inputs": [
7 | {
8 | "name": "V1",
9 | "description": "Voltage 1",
10 | "processList": [
11 | {
12 | "process": "log_to_feed",
13 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Voltage 1" }
14 | }
15 | ]
16 | },
17 | {
18 | "name": "V2",
19 | "description": "Voltage 2",
20 | "processList": [
21 | {
22 | "process": "log_to_feed",
23 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Voltage 2" }
24 | }
25 | ]
26 | },
27 | {
28 | "name": "CT1",
29 | "description": "Current 1",
30 | "processList": [
31 | {
32 | "process": "log_to_feed",
33 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Current 1" }
34 | }
35 | ]
36 | },
37 | {
38 | "name": "CT2",
39 | "description": "Current 2",
40 | "processList": [
41 | {
42 | "process": "log_to_feed",
43 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Current 2" }
44 | }
45 | ]
46 | },
47 | {
48 | "name": "CT3",
49 | "description": "Current 3",
50 | "processList": [
51 | {
52 | "process": "log_to_feed",
53 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Current 3" }
54 | }
55 | ]
56 | },
57 | {
58 | "name": "CT4",
59 | "description": "Current 4",
60 | "processList": [
61 | {
62 | "process": "log_to_feed",
63 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Current 4" }
64 | }
65 | ]
66 | },
67 | {
68 | "name": "CT5",
69 | "description": "Current 5",
70 | "processList": [
71 | {
72 | "process": "log_to_feed",
73 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Current 5" }
74 | }
75 | ]
76 | },
77 | {
78 | "name": "CT6",
79 | "description": "Current 6",
80 | "processList": [
81 | {
82 | "process": "log_to_feed",
83 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Current 6" }
84 | }
85 | ]
86 | },
87 | {
88 | "name": "PF1",
89 | "description": "Power Factor 1",
90 | "processList": [
91 | {
92 | "process": "log_to_feed",
93 | "arguments": {"type": "ProcessArg::FEEDID", "value": "PF 1" }
94 | }
95 | ]
96 | },
97 | {
98 | "name": "PF2",
99 | "description": "Power Factor 2",
100 | "processList": [
101 | {
102 | "process": "log_to_feed",
103 | "arguments": {"type": "ProcessArg::FEEDID", "value": "PF 2" }
104 | }
105 | ]
106 | },
107 | {
108 | "name": "temp",
109 | "description": "Temp",
110 | "processList": [
111 | {
112 | "process": "log_to_feed",
113 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Temp" }
114 | }
115 | ]
116 | },
117 | {
118 | "name": "freq",
119 | "description": "Frequency",
120 | "processList": [
121 | {
122 | "process": "log_to_feed",
123 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Frequency" }
124 | }
125 | ]
126 | },
127 | {
128 | "name": "W1",
129 | "description": "Total Power",
130 | "processList": [
131 | {
132 | "process": "add_input",
133 | "arguments": {"type": "ProcessArg::INPUTID", "value": "W2" }
134 | },
135 | {
136 | "process": "add_input",
137 | "arguments": {"type": "ProcessArg::INPUTID", "value": "W3" }
138 | },
139 | {
140 | "process": "add_input",
141 | "arguments": {"type": "ProcessArg::INPUTID", "value": "W4" }
142 | },
143 | {
144 | "process": "add_input",
145 | "arguments": {"type": "ProcessArg::INPUTID", "value": "W5" }
146 | },
147 | {
148 | "process": "add_input",
149 | "arguments": {"type": "ProcessArg::INPUTID", "value": "W6" }
150 | },
151 | {
152 | "process": "log_to_feed",
153 | "arguments": {"type": "ProcessArg::FEEDID", "value": "total_power" }
154 | },
155 | {
156 | "process": "power_to_kwh",
157 | "arguments": {"type": "ProcessArg::FEEDID", "value": "total_power_kwh" }
158 | }
159 | ]
160 | }
161 | ],
162 |
163 | "feeds": [
164 | {
165 | "name": "Voltage 1",
166 | "type": "DataType::REALTIME",
167 | "engine": "Engine::PHPFINA",
168 | "interval": "10",
169 | "unit": "V"
170 | },
171 | {
172 | "name": "Voltage 2",
173 | "type": "DataType::REALTIME",
174 | "engine": "Engine::PHPFINA",
175 | "interval": "10",
176 | "unit": "V"
177 | },
178 | {
179 | "name": "Current 1",
180 | "type": "DataType::REALTIME",
181 | "engine": "Engine::PHPFINA",
182 | "interval": "10",
183 | "unit": "A"
184 | },
185 | {
186 | "name": "Current 2",
187 | "type": "DataType::REALTIME",
188 | "engine": "Engine::PHPFINA",
189 | "interval": "10",
190 | "unit": "A"
191 | },
192 | {
193 | "name": "Current 3",
194 | "type": "DataType::REALTIME",
195 | "engine": "Engine::PHPFINA",
196 | "interval": "10",
197 | "unit": "A"
198 | },
199 | {
200 | "name": "Current 4",
201 | "type": "DataType::REALTIME",
202 | "engine": "Engine::PHPFINA",
203 | "interval": "10",
204 | "unit": "A"
205 | },
206 | {
207 | "name": "Current 5",
208 | "type": "DataType::REALTIME",
209 | "engine": "Engine::PHPFINA",
210 | "interval": "10",
211 | "unit": "A"
212 | },
213 | {
214 | "name": "Current 6",
215 | "type": "DataType::REALTIME",
216 | "engine": "Engine::PHPFINA",
217 | "interval": "10",
218 | "unit": "A"
219 | },
220 | {
221 | "name": "total_power",
222 | "type": "DataType::REALTIME",
223 | "engine": "Engine::PHPFINA",
224 | "interval": "10",
225 | "unit": "W"
226 | },
227 | {
228 | "name": "total_power_kwh",
229 | "type": "DataType::REALTIME",
230 | "engine": "Engine::PHPFINA",
231 | "interval": "10",
232 | "unit": "kWh"
233 | },
234 | {
235 | "name": "PF 1",
236 | "type": "DataType::REALTIME",
237 | "engine": "Engine::PHPFINA",
238 | "interval": "10",
239 | "unit": ""
240 | },
241 | {
242 | "name": "PF 2",
243 | "type": "DataType::REALTIME",
244 | "engine": "Engine::PHPFINA",
245 | "interval": "10",
246 | "unit": ""
247 | },
248 | {
249 | "name": "Temp",
250 | "type": "DataType::REALTIME",
251 | "engine": "Engine::PHPFINA",
252 | "interval": "10",
253 | "unit": "°C"
254 | },
255 | {
256 | "name": "Frequency",
257 | "type": "DataType::REALTIME",
258 | "engine": "Engine::PHPFINA",
259 | "interval": "10",
260 | "unit": "Hz"
261 | }
262 | ]
263 | }
264 |
--------------------------------------------------------------------------------
/device_controller.php:
--------------------------------------------------------------------------------
1 | format == 'html')
16 | {
17 | if ($route->action == "view" && $session['write']) {
18 | $templates = $device->get_template_list_meta($session['userid']);
19 | $result = view("Modules/device/Views/device_view.php", array('templates'=>$templates));
20 | }
21 | else if ($route->action == 'api') $result = view("Modules/device/Views/device_api.php", array());
22 | }
23 |
24 | if ($route->format == 'json')
25 | {
26 | // ---------------------------------------------------------------
27 | // Method for sharing authentication details with a node
28 | // that does not require copying and pasting passwords and apikeys
29 | // 1. device requests authentication - reply "request registered"
30 | // 2. notification asks user whether to allow or deny device
31 | // 3. user clicks on allow
32 | // 4. device makes follow up request for authentication
33 | // - reply authentication details
34 | // ---------------------------------------------------------------
35 | if ($route->action == "authcheck") { $route->action = "auth"; $route->subaction = "check"; }
36 | if ($route->action == "authallow") { $route->action = "auth"; $route->subaction = "allow"; }
37 |
38 | if ($route->action == "auth") {
39 | if ($route->subaction=="request") {
40 | // 1. Register request for authentication details, or provide if allowed
41 | $result = $device->request_auth($_SERVER['REMOTE_ADDR']);
42 | if (isset($result['success'])) {
43 | $result = $result['message'];
44 | }
45 | $route->format = "text";
46 | }
47 | else if ($route->subaction=="check" && $session['read']) {
48 | // 2. User checks for device waiting for authentication
49 | $result = $device->get_auth_request();
50 |
51 | if (isset($settings["device"]["enable_UDP_broadcast"]) && $settings["device"]["enable_UDP_broadcast"]) {
52 | $port = 5005;
53 | $broadcast_string = "emonpi.local";
54 | $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
55 | socket_set_option($sock, SOL_SOCKET, SO_BROADCAST, 1);
56 | socket_sendto($sock, $broadcast_string, strlen($broadcast_string), 0, '255.255.255.255', $port);
57 | }
58 | }
59 | else if ($route->subaction=="allow" && $session['write']) {
60 | // 3. User allows device to receive authentication details
61 | $result = $device->allow_auth_request(get("ip"));
62 | }
63 | }
64 | else if ($route->action == 'list') {
65 | if ($session['userid']>0 && $session['read']) $result = $device->get_list($session['userid']);
66 | }
67 | else if ($route->action == "create") {
68 | if ($session['userid']>0 && $session['write']) $result = $device->create($session['userid'],get("nodeid"),get("name"),get("description"),get("type"),get("options"));
69 | }
70 | // Used in conjunction with input name describe to auto create device
71 | else if ($route->action == "autocreate") {
72 | if ($session['userid']>0 && $session['write']) $result = $device->autocreate($session['userid'],get('nodeid'),get('type'));
73 | }
74 | else if ($route->action == "template" && $route->subaction != "prepare" && $route->subaction != "init") {
75 | if ($route->subaction == "listshort") {
76 | if ($session['userid']>0 && $session['write']) $result = $device->get_template_list_meta();
77 | }
78 | else if ($route->subaction == "list") {
79 | if ($session['userid']>0 && $session['write']) $result = $device->get_template_list();
80 | }
81 | else if ($route->subaction == "reload") {
82 | if ($session['userid']>0 && $session['write']) $result = $device->reload_template_list();
83 | }
84 | else if ($route->subaction == "get") {
85 | if ($session['userid']>0 && $session['write']) $result = $device->get_template(get('type'));
86 | }
87 | }
88 | else {
89 | $deviceid = (int) get('id');
90 | if ($device->exist($deviceid)) // if the feed exists
91 | {
92 | $deviceget = $device->get($deviceid);
93 | if (isset($session['write']) && $session['write'] && $session['userid']>0 && $deviceget['userid']==$session['userid']) {
94 | if ($route->action == "get") $result = $deviceget;
95 | else if ($route->action == 'set') $result = $device->set_fields($deviceid, get('fields'));
96 | else if ($route->action == 'init') $result = $device->init($deviceid, prop('template'));
97 | else if ($route->action == "delete") $result = $device->delete($deviceid);
98 | else if ($route->action == "setnewdevicekey") $result = $device->set_new_devicekey($deviceid);
99 | else if ($route->action == 'template') {
100 | if (isset($_GET['type'])) {
101 | $device->set_fields($deviceid, json_encode(array("type"=>$_GET['type'])));
102 | }
103 | if ($route->subaction == 'prepare') $result = $device->prepare_template($deviceid);
104 | else if ($route->subaction == 'init') $result = $device->init_template($deviceget, $_POST['template']);
105 | }
106 | }
107 | }
108 | else {
109 | $result = array('success'=>false, 'message'=>'Device does not exist');
110 | }
111 | }
112 | }
113 |
114 |
115 | if ($route->action == "clean" && $session['write']) {
116 | $route->format = 'text';
117 | $active = 0; if (isset($_GET['active'])) $active = (int) $_GET['active'];
118 | $dryrun = 0; if (isset($_GET['dryrun']) && $_GET['dryrun']==1) $dryrun = 1;
119 | return $device->clean($session['userid'],$active,$dryrun);
120 | }
121 |
122 | return array('content'=>$result);
123 | }
124 |
--------------------------------------------------------------------------------
/Views/device_view.php:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
168 |
--------------------------------------------------------------------------------
/Views/device_dialog.css:
--------------------------------------------------------------------------------
1 | .modal-adjust {
2 | width: 88%;
3 | left: 6%; /* (100%-width)/2 */
4 | margin-left: auto;
5 | margin-right: auto;
6 | overflow-y: hidden;
7 | }
8 | @media (min-width: 1280px) {
9 | .modal-adjust {
10 | width: 60%;
11 | left: 20%; /* (100%-width)/2 */
12 | margin-left: auto;
13 | margin-right: auto;
14 | overflow-y: hidden;
15 | }
16 | }
17 |
18 | .modal-adjust .modal-body {
19 | max-height: none;
20 | overflow-y: hidden;
21 | }
22 |
23 | .modal-adjust .tooltip-inner {
24 | max-width: 250px;
25 | }
26 |
27 | .modal-adjust .divider {
28 | *width: 100%;
29 | height: 1px;
30 | margin: 9px 1px;
31 | *margin: -5px 0 5px;
32 | overflow: hidden;
33 | background-color: #e5e5e5;
34 | border-bottom: 1px solid #ffffff;
35 | }
36 |
37 | .modal-adjust .alert-comment {
38 | margin: 0px;
39 | color: #737373;
40 | background-color: #f9f9f9;
41 | border-color: #e6e6e6;
42 | }
43 |
44 | .modal-adjust .alert-comment h4 {
45 | color: #737373;
46 | }
47 |
48 | .modal-adjust .content {
49 | position: absolute;
50 | left: 15px;
51 | right: 0px;
52 | bottom: 0px;
53 | height: 100%;
54 | overflow-y: auto;
55 | }
56 |
57 | .modal-overlay {
58 | position: absolute;
59 | top: 0; bottom:0; left: 0; right:0;
60 | margin: auto;
61 | background: rgba(255, 255, 255, .8) center;
62 | }
63 | .modal-content {
64 | position: absolute;
65 | left: 15px;
66 | right: 0px;
67 | padding-right: 15px;
68 | margin-left: 15px;
69 | margin-top: -15px;
70 | height: 100%;
71 | max-height: none;
72 | overflow-y: auto;
73 | }
74 |
75 | .modal-content .btn-sidebar {
76 | margin-bottom: 5px;
77 | }
78 | .modal-sidebar {
79 | position: absolute;
80 | margin-top: -15px;
81 | margin-left: -15px;
82 | max-height: none;
83 | height: 100%;
84 | width: 250px;
85 | overflow-x: hidden;
86 | overflow-y: auto;
87 | background-color: #f2f2f2;
88 | z-index: 1000;
89 | }
90 | .modal-sidebar .accordion {
91 | margin-bottom: 0px;
92 | }
93 | .modal-sidebar .accordion-group {
94 | margin-bottom: 0px;
95 | border: 0px;
96 | -webkit-border-radius: 0px;
97 | -moz-border-radius: 0px;
98 | border-radius: 0px;
99 | }
100 | .modal-sidebar .accordion-inner {
101 | padding: 0px;
102 | border-top: 0px;
103 | }
104 | .modal-sidebar .category-heading {
105 | background-color: #cccccc;
106 | border-bottom: 1px solid #d9d9d9;
107 | cursor: pointer;
108 | }
109 | .modal-sidebar .category-heading:hover {
110 | background-color: #d9d9d9;
111 | }
112 | .modal-sidebar .category-heading span {
113 | font-weight: bold;
114 | padding: 6px 6px 6px 10px;
115 | }
116 | .modal-sidebar .group-heading {
117 | background-color: #e6e6e6;
118 | border-bottom: 1px solid #ededed;
119 | cursor: pointer;
120 | }
121 | .modal-sidebar .group-heading:hover {
122 | background-color: #ededed;
123 | }
124 | .modal-sidebar .group-heading span {
125 | font-weight: bold;
126 | padding: 6px 6px 6px 16px;
127 | }
128 | .modal-sidebar .group-device {
129 | padding: 8px 8px 8px 22px;
130 | background-color: #f5f5f5;
131 | border-bottom: 1px solid #fcfcfc;
132 | cursor: pointer;
133 | }
134 | .modal-sidebar .group-device:hover {
135 | background-color: #44b3e2;
136 | color: #ffffff;
137 | }
138 |
139 | .device-selected {
140 | background-color: #209ed3 !important;
141 | color: #ffffff;
142 | }
143 |
144 | .device-input {
145 | width: 100%;
146 | }
147 | .device-input th,
148 | .device-input td {
149 | white-space: nowrap;
150 | text-align: left;
151 | }
152 | .device-input th:nth-of-type(3) {
153 | color: #888;
154 | font-weight: normal;
155 | }
156 | .device-input td:nth-of-type(1) { width:108px; }
157 | .device-input td:nth-of-type(2) { width:168px; }
158 |
159 | .table-feeds td:nth-of-type(1) { width:14px; text-align: center; }
160 | .table-feeds td:nth-of-type(2) { width:5%; }
161 | .table-feeds td:nth-of-type(3) { width:15%; }
162 | .table-feeds td:nth-of-type(4) { width:25%; }
163 |
164 | .table-inputs td:nth-of-type(1) { width:14px; text-align: center; }
165 | .table-inputs td:nth-of-type(2) { width:5%; }
166 | .table-inputs td:nth-of-type(3) { width:5%; }
167 | .table-inputs td:nth-of-type(4) { width:10%; }
168 | .table-inputs td:nth-of-type(5) { width:25%; }
169 |
170 |
171 | #device-config-modal.modal {
172 | margin: 0;
173 | border-radius: 0;
174 | border: none;
175 | width: 100%;
176 | left: 0;
177 | top: 3%;
178 | }
179 | #device-config-body {
180 | padding: 0
181 | }
182 | #device-config-modal.modal .accordion-toggle {
183 | white-space: nowrap;
184 | overflow: hidden;
185 | }
186 | #device-content {
187 | left: 0.2rem;
188 | margin-left: 0;
189 | transition: width 0.5s ease-in-out;
190 | }
191 | #device-sidebar-close {
192 | cursor: pointer;
193 | position: absolute;
194 | right: 0;
195 | top: .5em;
196 | line-height: 1.3;
197 | background: #f2f2f2;
198 | padding: .3em .6em;
199 | }
200 | #device-sidebar {
201 | transition: width 0.3s ease-in-out;
202 | width: 0;
203 | }
204 | #device-sidebar.show {
205 | width: 100%;
206 | }
207 | .key {
208 | font-size: 12px !important;
209 | font-family: Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace;
210 | }
211 | #device-config-modal.modal .modal-content {
212 | margin-top: 0
213 | }
214 | #device-config-modal.modal .modal-sidebar {
215 | margin: 0
216 | }
217 |
218 | /* add horizontal padding to devices larger than iphone5 (320px)*/
219 | @media (min-width: 340px) {
220 | #device-config-modal.modal {
221 | margin: 0 auto
222 | }
223 | #device-config-modal.modal .modal-sidebar {
224 | margin: 0 -1rem
225 | }
226 | #device-content {
227 | left: 15px;
228 | }
229 | #device-config-body {
230 | padding: 0 1rem
231 | }
232 | }
233 |
234 | @media (min-width: 680px) {
235 | #device-sidebar.show {
236 | width: 250px;
237 | transition: none;
238 | }
239 | #device-content {
240 | left: 270px;
241 | }
242 | #device-config-modal.modal {
243 | margin: 0;
244 | border-radius: 6px;
245 | border: 1px solid rgba(0,0,0,0.3);
246 | width: 94%;
247 | left: 3%;
248 | }
249 | #device-config-modal.modal .modal-sidebar {
250 | margin: 0 -1rem
251 | }
252 |
253 | }
254 |
255 | /* hide/show items on very small devices */
256 | .visible-xs {
257 | display: none
258 | }
259 | @media (max-width: 375px) {
260 | .hidden-xs {
261 | display: none
262 | }
263 | .visible-xs {
264 | display: inline-block
265 | }
266 | }
267 |
--------------------------------------------------------------------------------
/Views/device_dialog.php:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
67 |
68 |
69 |
70 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | |
85 | |
86 | |
87 | |
88 | |
89 |
90 |
91 |
92 |
93 |
94 |
108 |
109 |
110 |
114 |
115 |
116 |
117 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
135 |
136 |
137 |
143 |
--------------------------------------------------------------------------------
/data/CircuitSetup/circuitsetup-split-phase_solar.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Solar Kit",
3 | "category": "CircuitSetup",
4 | "group": "Energy Meters",
5 | "description": "CircuitSetup Split Single Phase Energy Meter with Solar",
6 | "inputs": [
7 | {
8 | "name": "V1",
9 | "description": "Voltage 1",
10 | "processList": [
11 | {
12 | "process": "log_to_feed",
13 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Voltage 1" }
14 | }
15 | ]
16 | },
17 | {
18 | "name": "V2",
19 | "description": "Voltage 2",
20 | "processList": [
21 | {
22 | "process": "log_to_feed",
23 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Voltage 2" }
24 | }
25 | ]
26 | },
27 | {
28 | "name": "totV",
29 | "description": "Total Voltage",
30 | "processList": [
31 | {
32 | "process": "log_to_feed",
33 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Total Voltage" }
34 | }
35 | ]
36 | },
37 | {
38 | "name": "SolarV",
39 | "description": "Solar Voltage",
40 | "processList": [
41 | {
42 | "process": "log_to_feed",
43 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Solar Voltage" }
44 | }
45 | ]
46 | },
47 | {
48 | "name": "CT1",
49 | "description": "Current 1",
50 | "processList": [
51 | {
52 | "process": "log_to_feed",
53 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Current 1" }
54 | }
55 | ]
56 | },
57 | {
58 | "name": "CT2",
59 | "description": "Current 2",
60 | "processList": [
61 | {
62 | "process": "log_to_feed",
63 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Current 2" }
64 | }
65 | ]
66 | },
67 | {
68 | "name": "totI",
69 | "description": "Total Current",
70 | "processList": [
71 | {
72 | "process": "log_to_feed",
73 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Total Current" }
74 | }
75 | ]
76 | },
77 | {
78 | "name": "SCT1",
79 | "description": "Solar Current 1",
80 | "processList": [
81 | {
82 | "process": "log_to_feed",
83 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Solar Current 1" }
84 | }
85 | ]
86 | },
87 | {
88 | "name": "SCT2",
89 | "description": "Solar Current 2",
90 | "processList": [
91 | {
92 | "process": "log_to_feed",
93 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Solar Current 2" }
94 | }
95 | ]
96 | },
97 | {
98 | "name": "totSolarI",
99 | "description": "Total Solar Current",
100 | "processList": [
101 | {
102 | "process": "log_to_feed",
103 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Total Solar Current" }
104 | }
105 | ]
106 | },
107 | {
108 | "name": "PF",
109 | "description": "Power Factor",
110 | "processList": [
111 | {
112 | "process": "log_to_feed",
113 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Power Factor" }
114 | }
115 | ]
116 | },
117 | {
118 | "name": "temp",
119 | "description": "Temp",
120 | "processList": [
121 | {
122 | "process": "log_to_feed",
123 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Temp" }
124 | }
125 | ]
126 | },
127 | {
128 | "name": "freq",
129 | "description": "Frequency",
130 | "processList": [
131 | {
132 | "process": "log_to_feed",
133 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Frequency" }
134 | }
135 | ]
136 | },
137 | {
138 | "name": "W",
139 | "description": "Power Usage",
140 | "processList": [
141 | {
142 | "process": "allowpositive",
143 | "arguments": {"type": "ProcessArg::NONE"}
144 | },
145 | {
146 | "process": "log_to_feed",
147 | "arguments": {"type": "ProcessArg::FEEDID", "value": "import" }
148 | },
149 | {
150 | "process": "power_to_kwh",
151 | "arguments": {"type": "ProcessArg::FEEDID", "value": "import_kwh" }
152 | }
153 | ]
154 | },
155 | {
156 | "name": "SolarW",
157 | "description": "Solar Power",
158 | "processList": [
159 | {
160 | "process": "log_to_feed",
161 | "arguments": {"type": "ProcessArg::FEEDID", "value": "solar" }
162 | },
163 | {
164 | "process": "power_to_kwh",
165 | "arguments": {"type": "ProcessArg::FEEDID", "value": "solar_kwh" }
166 | },
167 | {
168 | "process": "add_input",
169 | "arguments": {"type": "ProcessArg::INPUTID", "value": "W" }
170 | },
171 | {
172 | "process": "log_to_feed",
173 | "arguments": {"type": "ProcessArg::FEEDID", "value": "use" }
174 | },
175 | {
176 | "process": "power_to_kwh",
177 | "arguments": {"type": "ProcessArg::FEEDID", "value": "use_kwh" }
178 | }
179 | ]
180 | }
181 | ],
182 |
183 | "feeds": [
184 | {
185 | "name": "Voltage 1",
186 | "type": "DataType::REALTIME",
187 | "engine": "Engine::PHPFINA",
188 | "interval": "10",
189 | "unit": "V"
190 | },
191 | {
192 | "name": "Voltage 2",
193 | "type": "DataType::REALTIME",
194 | "engine": "Engine::PHPFINA",
195 | "interval": "10",
196 | "unit": "V"
197 | },
198 | {
199 | "name": "Total Voltage",
200 | "type": "DataType::REALTIME",
201 | "engine": "Engine::PHPFINA",
202 | "interval": "10",
203 | "unit": "V"
204 | },
205 | {
206 | "name": "Solar Voltage",
207 | "type": "DataType::REALTIME",
208 | "engine": "Engine::PHPFINA",
209 | "interval": "10",
210 | "unit": "V"
211 | },
212 | {
213 | "name": "Current 1",
214 | "type": "DataType::REALTIME",
215 | "engine": "Engine::PHPFINA",
216 | "interval": "10",
217 | "unit": "A"
218 | },
219 | {
220 | "name": "Current 2",
221 | "type": "DataType::REALTIME",
222 | "engine": "Engine::PHPFINA",
223 | "interval": "10",
224 | "unit": "A"
225 | },
226 | {
227 | "name": "Total Current",
228 | "type": "DataType::REALTIME",
229 | "engine": "Engine::PHPFINA",
230 | "interval": "10",
231 | "unit": "A"
232 | },
233 | {
234 | "name": "Solar Current 1",
235 | "type": "DataType::REALTIME",
236 | "engine": "Engine::PHPFINA",
237 | "interval": "10",
238 | "unit": "A"
239 | },
240 | {
241 | "name": "Solar Current 2",
242 | "type": "DataType::REALTIME",
243 | "engine": "Engine::PHPFINA",
244 | "interval": "10",
245 | "unit": "A"
246 | },
247 | {
248 | "name": "Total Solar Current",
249 | "type": "DataType::REALTIME",
250 | "engine": "Engine::PHPFINA",
251 | "interval": "10",
252 | "unit": "A"
253 | },
254 | {
255 | "name": "use",
256 | "type": "DataType::REALTIME",
257 | "engine": "Engine::PHPFINA",
258 | "interval": "10",
259 | "unit": "W"
260 | },
261 | {
262 | "name": "use_kwh",
263 | "type": "DataType::REALTIME",
264 | "engine": "Engine::PHPFINA",
265 | "interval": "10",
266 | "unit": "kWh"
267 | },
268 | {
269 | "name": "solar",
270 | "type": "DataType::REALTIME",
271 | "engine": "Engine::PHPFINA",
272 | "interval": "10",
273 | "unit": "W"
274 | },
275 | {
276 | "name": "solar_kwh",
277 | "type": "DataType::REALTIME",
278 | "engine": "Engine::PHPFINA",
279 | "interval": "10",
280 | "unit": "kWh"
281 | },
282 | {
283 | "name": "import",
284 | "type": "DataType::REALTIME",
285 | "engine": "Engine::PHPFINA",
286 | "interval": "10",
287 | "unit": "W"
288 | },
289 | {
290 | "name": "import_kwh",
291 | "type": "DataType::REALTIME",
292 | "engine": "Engine::PHPFINA",
293 | "interval": "10",
294 | "unit": "kWh"
295 | },
296 | {
297 | "name": "Power Factor",
298 | "type": "DataType::REALTIME",
299 | "engine": "Engine::PHPFINA",
300 | "interval": "10",
301 | "unit": ""
302 | },
303 | {
304 | "name": "Temp",
305 | "type": "DataType::REALTIME",
306 | "engine": "Engine::PHPFINA",
307 | "interval": "10",
308 | "unit": "°C"
309 | },
310 | {
311 | "name": "Frequency",
312 | "type": "DataType::REALTIME",
313 | "engine": "Engine::PHPFINA",
314 | "interval": "10",
315 | "unit": ""
316 | }
317 | ]
318 | }
319 |
--------------------------------------------------------------------------------
/data/CircuitSetup/circuitsetup-6_channel_solar.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "6 Channel Meter with Solar",
3 | "category": "CircuitSetup",
4 | "group": "Energy Meters",
5 | "description": "CircuitSetup 6 Channel with Solar",
6 | "inputs": [
7 | {
8 | "name": "V1",
9 | "description": "Voltage 1",
10 | "processList": [
11 | {
12 | "process": "log_to_feed",
13 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Voltage 1" }
14 | }
15 | ]
16 | },
17 | {
18 | "name": "V2",
19 | "description": "Voltage 2",
20 | "processList": [
21 | {
22 | "process": "log_to_feed",
23 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Voltage 2" }
24 | }
25 | ]
26 | },
27 | {
28 | "name": "CT1",
29 | "description": "House Current 1",
30 | "processList": [
31 | {
32 | "process": "log_to_feed",
33 | "arguments": {"type": "ProcessArg::FEEDID", "value": "House Current 1" }
34 | }
35 | ]
36 | },
37 | {
38 | "name": "CT2",
39 | "description": "House Current 2",
40 | "processList": [
41 | {
42 | "process": "log_to_feed",
43 | "arguments": {"type": "ProcessArg::FEEDID", "value": "House Current 2" }
44 | }
45 | ]
46 | },
47 | {
48 | "name": "CT3",
49 | "description": "Charging Current 3",
50 | "processList": [
51 | {
52 | "process": "log_to_feed",
53 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Charging Current 3" }
54 | }
55 | ]
56 | },
57 | {
58 | "name": "CT4",
59 | "description": "Charging Current 4",
60 | "processList": [
61 | {
62 | "process": "log_to_feed",
63 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Charging Current 4" }
64 | }
65 | ]
66 | },
67 | {
68 | "name": "CT5",
69 | "description": "Solar Current 5",
70 | "processList": [
71 | {
72 | "process": "log_to_feed",
73 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Solar Current 5" }
74 | }
75 | ]
76 | },
77 | {
78 | "name": "CT6",
79 | "description": "Solar Current 6",
80 | "processList": [
81 | {
82 | "process": "log_to_feed",
83 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Solar Current 6" }
84 | }
85 | ]
86 | },
87 | {
88 | "name": "PF1",
89 | "description": "House Power Factor 1",
90 | "processList": [
91 | {
92 | "process": "log_to_feed",
93 | "arguments": {"type": "ProcessArg::FEEDID", "value": "House PF 1" }
94 | }
95 | ]
96 | },
97 | {
98 | "name": "PF2",
99 | "description": "House Power Factor 2",
100 | "processList": [
101 | {
102 | "process": "log_to_feed",
103 | "arguments": {"type": "ProcessArg::FEEDID", "value": "House PF 2" }
104 | }
105 | ]
106 | },
107 | {
108 | "name": "temp",
109 | "description": "Temp",
110 | "processList": [
111 | {
112 | "process": "log_to_feed",
113 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Temp" }
114 | }
115 | ]
116 | },
117 | {
118 | "name": "freq",
119 | "description": "Frequency",
120 | "processList": [
121 | {
122 | "process": "log_to_feed",
123 | "arguments": {"type": "ProcessArg::FEEDID", "value": "Frequency" }
124 | }
125 | ]
126 | },
127 | {
128 | "name": "W1",
129 | "description": "Total House Power",
130 | "processList": [
131 | {
132 | "process": "add_input",
133 | "arguments": {"type": "ProcessArg::INPUTID", "value": "W2" }
134 | },
135 | {
136 | "process": "log_to_feed",
137 | "arguments": {"type": "ProcessArg::FEEDID", "value": "house_import" }
138 | },
139 | {
140 | "process": "power_to_kwh",
141 | "arguments": {"type": "ProcessArg::FEEDID", "value": "house_import_kwh" }
142 | }
143 | ]
144 | },
145 | {
146 | "name": "W3",
147 | "description": "Total Charging",
148 | "processList": [
149 | {
150 | "process": "allowpositive",
151 | "arguments": {"type": "ProcessArg::NONE"}
152 | },
153 | {
154 | "process": "add_input",
155 | "arguments": {"type": "ProcessArg::INPUTID", "value": "W4" }
156 | },
157 | {
158 | "process": "log_to_feed",
159 | "arguments": {"type": "ProcessArg::FEEDID", "value": "charging_import" }
160 | },
161 | {
162 | "process": "power_to_kwh",
163 | "arguments": {"type": "ProcessArg::FEEDID", "value": "charging_import_kwh" }
164 | }
165 | ]
166 | },
167 | {
168 | "name": "W5",
169 | "description": "Total Solar Power",
170 | "processList": [
171 | {
172 | "process": "add_input",
173 | "arguments": {"type": "ProcessArg::INPUTID", "value": "W6" }
174 | },
175 | {
176 | "process": "log_to_feed",
177 | "arguments": {"type": "ProcessArg::FEEDID", "value": "solar" }
178 | },
179 | {
180 | "process": "power_to_kwh",
181 | "arguments": {"type": "ProcessArg::FEEDID", "value": "solar_kwh" }
182 | },
183 | {
184 | "process": "add_input",
185 | "arguments": {"type": "ProcessArg::INPUTID", "value": "W1" }
186 | },
187 | {
188 | "process": "add_input",
189 | "arguments": {"type": "ProcessArg::INPUTID", "value": "W2" }
190 | },
191 | {
192 | "process": "log_to_feed",
193 | "arguments": {"type": "ProcessArg::FEEDID", "value": "use" }
194 | },
195 | {
196 | "process": "power_to_kwh",
197 | "arguments": {"type": "ProcessArg::FEEDID", "value": "use_kwh" }
198 | }
199 | ]
200 | }
201 | ],
202 |
203 | "feeds": [
204 | {
205 | "name": "Voltage 1",
206 | "type": "DataType::REALTIME",
207 | "engine": "Engine::PHPFINA",
208 | "interval": "10",
209 | "unit": "V"
210 | },
211 | {
212 | "name": "Voltage 2",
213 | "type": "DataType::REALTIME",
214 | "engine": "Engine::PHPFINA",
215 | "interval": "10",
216 | "unit": "V"
217 | },
218 | {
219 | "name": "House Current 1",
220 | "type": "DataType::REALTIME",
221 | "engine": "Engine::PHPFINA",
222 | "interval": "10",
223 | "unit": "A"
224 | },
225 | {
226 | "name": "House Current 2",
227 | "type": "DataType::REALTIME",
228 | "engine": "Engine::PHPFINA",
229 | "interval": "10",
230 | "unit": "A"
231 | },
232 | {
233 | "name": "Charging Current 3",
234 | "type": "DataType::REALTIME",
235 | "engine": "Engine::PHPFINA",
236 | "interval": "10",
237 | "unit": "A"
238 | },
239 | {
240 | "name": "Charging Current 4",
241 | "type": "DataType::REALTIME",
242 | "engine": "Engine::PHPFINA",
243 | "interval": "10",
244 | "unit": "A"
245 | },
246 | {
247 | "name": "Solar Current 5",
248 | "type": "DataType::REALTIME",
249 | "engine": "Engine::PHPFINA",
250 | "interval": "10",
251 | "unit": "A"
252 | },
253 | {
254 | "name": "Solar Current 6",
255 | "type": "DataType::REALTIME",
256 | "engine": "Engine::PHPFINA",
257 | "interval": "10",
258 | "unit": "A"
259 | },
260 | {
261 | "name": "charging_import",
262 | "type": "DataType::REALTIME",
263 | "engine": "Engine::PHPFINA",
264 | "interval": "10",
265 | "unit": "W"
266 | },
267 | {
268 | "name": "charging_import_kwh",
269 | "type": "DataType::REALTIME",
270 | "engine": "Engine::PHPFINA",
271 | "interval": "10",
272 | "unit": "kWh"
273 | },
274 | {
275 | "name": "use",
276 | "type": "DataType::REALTIME",
277 | "engine": "Engine::PHPFINA",
278 | "interval": "10",
279 | "unit": "W"
280 | },
281 | {
282 | "name": "use_kwh",
283 | "type": "DataType::REALTIME",
284 | "engine": "Engine::PHPFINA",
285 | "interval": "10",
286 | "unit": "kWh"
287 | },
288 | {
289 | "name": "solar",
290 | "type": "DataType::REALTIME",
291 | "engine": "Engine::PHPFINA",
292 | "interval": "10",
293 | "unit": "W"
294 | },
295 | {
296 | "name": "solar_kwh",
297 | "type": "DataType::REALTIME",
298 | "engine": "Engine::PHPFINA",
299 | "interval": "10",
300 | "unit": "kWh"
301 | },
302 | {
303 | "name": "house_import",
304 | "type": "DataType::REALTIME",
305 | "engine": "Engine::PHPFINA",
306 | "interval": "10",
307 | "unit": "W"
308 | },
309 | {
310 | "name": "house_import_kwh",
311 | "type": "DataType::REALTIME",
312 | "engine": "Engine::PHPFINA",
313 | "interval": "10",
314 | "unit": "kWh"
315 | },
316 | {
317 | "name": "House PF 1",
318 | "type": "DataType::REALTIME",
319 | "engine": "Engine::PHPFINA",
320 | "interval": "10",
321 | "unit": ""
322 | },
323 | {
324 | "name": "House PF 2",
325 | "type": "DataType::REALTIME",
326 | "engine": "Engine::PHPFINA",
327 | "interval": "10",
328 | "unit": ""
329 | },
330 | {
331 | "name": "Temp",
332 | "type": "DataType::REALTIME",
333 | "engine": "Engine::PHPFINA",
334 | "interval": "10",
335 | "unit": "°C"
336 | },
337 | {
338 | "name": "Frequency",
339 | "type": "DataType::REALTIME",
340 | "engine": "Engine::PHPFINA",
341 | "interval": "10",
342 | "unit": ""
343 | }
344 | ]
345 | }
346 |
--------------------------------------------------------------------------------
/data/amig.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "AMIG",
3 | "category": "Datalogger",
4 | "group": "ArchiMetrics",
5 | "description": "INPUTS & FEEDS for AMIG Logger from archimetrics.co.uk",
6 | "inputs": [{
7 | "name": "n1T",
8 | "description": "N1 Temperature",
9 | "processList": [{
10 | "process": "log_to_feed",
11 | "arguments": {
12 | "type": "ProcessArg::FEEDID",
13 | "value": "n1T"
14 | }
15 | }]
16 | },
17 | {
18 | "name": "n1R",
19 | "description": "N1 RH",
20 | "processList": [{
21 | "process": "log_to_feed",
22 | "arguments": {
23 | "type": "ProcessArg::FEEDID",
24 | "value": "n1R"
25 | }
26 | }]
27 | },
28 | {
29 | "name": "n1D",
30 | "description": "N1 DewPoint",
31 | "processList": [{
32 | "process": "log_to_feed",
33 | "arguments": {
34 | "type": "ProcessArg::FEEDID",
35 | "value": "n1D"
36 | }
37 | }]
38 | },
39 | {
40 | "name": "n2T",
41 | "description": "N2 Temperature",
42 | "processList": [{
43 | "process": "log_to_feed",
44 | "arguments": {
45 | "type": "ProcessArg::FEEDID",
46 | "value": "n2T"
47 | }
48 | }]
49 | },
50 | {
51 | "name": "n2R",
52 | "description": "N2 RH",
53 | "processList": [{
54 | "process": "log_to_feed",
55 | "arguments": {
56 | "type": "ProcessArg::FEEDID",
57 | "value": "n2R"
58 | }
59 | }]
60 | },
61 | {
62 | "name": "n2D",
63 | "description": "N2 DewPoint",
64 | "processList": [{
65 | "process": "log_to_feed",
66 | "arguments": {
67 | "type": "ProcessArg::FEEDID",
68 | "value": "n2D"
69 | }
70 | }]
71 | },
72 | {
73 | "name": "n3T",
74 | "description": "N3 Temperature",
75 | "processList": [{
76 | "process": "log_to_feed",
77 | "arguments": {
78 | "type": "ProcessArg::FEEDID",
79 | "value": "n3T"
80 | }
81 | }]
82 | },
83 | {
84 | "name": "n3R",
85 | "description": "N3 RH",
86 | "processList": [{
87 | "process": "log_to_feed",
88 | "arguments": {
89 | "type": "ProcessArg::FEEDID",
90 | "value": "n3R"
91 | }
92 | }]
93 | },
94 | {
95 | "name": "n3D",
96 | "description": "N3 DewPoint",
97 | "processList": [{
98 | "process": "log_to_feed",
99 | "arguments": {
100 | "type": "ProcessArg::FEEDID",
101 | "value": "n3D"
102 | }
103 | }]
104 | },
105 | {
106 | "name": "n4T",
107 | "description": "N4 Temperature",
108 | "processList": [{
109 | "process": "log_to_feed",
110 | "arguments": {
111 | "type": "ProcessArg::FEEDID",
112 | "value": "n4T"
113 | }
114 | }]
115 | },
116 | {
117 | "name": "n4R",
118 | "description": "N4 RH",
119 | "processList": [{
120 | "process": "log_to_feed",
121 | "arguments": {
122 | "type": "ProcessArg::FEEDID",
123 | "value": "n4R"
124 | }
125 | }]
126 | },
127 | {
128 | "name": "n4D",
129 | "description": "N4 DewPoint",
130 | "processList": [{
131 | "process": "log_to_feed",
132 | "arguments": {
133 | "type": "ProcessArg::FEEDID",
134 | "value": "n4D"
135 | }
136 | }]
137 | },
138 | {
139 | "name": "iTS",
140 | "description": "Internal Temperature Surface",
141 | "processList": [{
142 | "process": "log_to_feed",
143 | "arguments": {
144 | "type": "ProcessArg::FEEDID",
145 | "value": "iTS"
146 | }
147 | }]
148 | },
149 | {
150 | "name": "eTS",
151 | "description": "External Temperature Surface",
152 | "processList": [{
153 | "process": "log_to_feed",
154 | "arguments": {
155 | "type": "ProcessArg::FEEDID",
156 | "value": "eTS"
157 | }
158 | }]
159 | },
160 | {
161 | "name": "eSR",
162 | "description": "External Solar Radiation",
163 | "processList": [{
164 | "process": "log_to_feed",
165 | "arguments": {
166 | "type": "ProcessArg::FEEDID",
167 | "value": "eSR"
168 | }
169 | }]
170 | },
171 | {
172 | "name": "iTA",
173 | "description": "Internal Temperature Air",
174 | "processList": [{
175 | "process": "log_to_feed",
176 | "arguments": {
177 | "type": "ProcessArg::FEEDID",
178 | "value": "iTA"
179 | }
180 | }]
181 | },
182 | {
183 | "name": "iRH",
184 | "description": "Internal RH",
185 | "processList": [{
186 | "process": "log_to_feed",
187 | "arguments": {
188 | "type": "ProcessArg::FEEDID",
189 | "value": "iRH"
190 | }
191 | }]
192 | },
193 | {
194 | "name": "iDP",
195 | "description": "Internal DewPoint",
196 | "processList": [{
197 | "process": "log_to_feed",
198 | "arguments": {
199 | "type": "ProcessArg::FEEDID",
200 | "value": "iDP"
201 | }
202 | }]
203 | },
204 | {
205 | "name": "eTA",
206 | "description": "External Temperature Air",
207 | "processList": [{
208 | "process": "log_to_feed",
209 | "arguments": {
210 | "type": "ProcessArg::FEEDID",
211 | "value": "eTA"
212 | }
213 | }]
214 | },
215 | {
216 | "name": "eRH",
217 | "description": "External RH",
218 | "processList": [{
219 | "process": "log_to_feed",
220 | "arguments": {
221 | "type": "ProcessArg::FEEDID",
222 | "value": "eRH"
223 | }
224 | }]
225 | },
226 | {
227 | "name": "eDP",
228 | "description": "External DewPoint",
229 | "processList": [{
230 | "process": "log_to_feed",
231 | "arguments": {
232 | "type": "ProcessArg::FEEDID",
233 | "value": "eDP"
234 | }
235 | }]
236 | },
237 | {
238 | "name": "hfV",
239 | "description": "HeatFlux Voltage",
240 | "processList": [{
241 | "process": "log_to_feed",
242 | "arguments": {
243 | "type": "ProcessArg::FEEDID",
244 | "value": "hfV"
245 | }
246 | }]
247 | },
248 | {
249 | "name": "hfW",
250 | "description": "HeatFlux Watts",
251 | "processList": [{
252 | "process": "log_to_feed",
253 | "arguments": {
254 | "type": "ProcessArg::FEEDID",
255 | "value": "hfW"
256 | }
257 | }]
258 | },
259 | {
260 | "name": "CO2",
261 | "description": "Carbon Dioxide",
262 | "processList": [{
263 | "process": "log_to_feed",
264 | "arguments": {
265 | "type": "ProcessArg::FEEDID",
266 | "value": "CO2"
267 | }
268 | }]
269 | },
270 | {
271 | "name": "SYS",
272 | "description": "System Temperature",
273 | "processList": [{
274 | "process": "log_to_feed",
275 | "arguments": {
276 | "type": "ProcessArg::FEEDID",
277 | "value": "SYS"
278 | }
279 | }]
280 | }],
281 | "feeds": [{
282 | "name": "n1T",
283 | "type": "DataType::REALTIME",
284 | "engine": "Engine::PHPFINA",
285 | "interval": "10"
286 | },
287 | {
288 | "name": "n1R",
289 | "type": "DataType::REALTIME",
290 | "engine": "Engine::PHPFINA",
291 | "interval": "10"
292 | },
293 | {
294 | "name": "n1D",
295 | "type": "DataType::REALTIME",
296 | "engine": "Engine::PHPFINA",
297 | "interval": "10"
298 | },
299 | {
300 | "name": "n2T",
301 | "type": "DataType::REALTIME",
302 | "engine": "Engine::PHPFINA",
303 | "interval": "10"
304 | },
305 | {
306 | "name": "n2R",
307 | "type": "DataType::REALTIME",
308 | "engine": "Engine::PHPFINA",
309 | "interval": "10"
310 | },
311 | {
312 | "name": "n2D",
313 | "type": "DataType::REALTIME",
314 | "engine": "Engine::PHPFINA",
315 | "interval": "10"
316 | },
317 | {
318 | "name": "n3T",
319 | "type": "DataType::REALTIME",
320 | "engine": "Engine::PHPFINA",
321 | "interval": "10"
322 | },
323 | {
324 | "name": "n3R",
325 | "type": "DataType::REALTIME",
326 | "engine": "Engine::PHPFINA",
327 | "interval": "10"
328 | },
329 | {
330 | "name": "n3D",
331 | "type": "DataType::REALTIME",
332 | "engine": "Engine::PHPFINA",
333 | "interval": "10"
334 | },
335 | {
336 | "name": "n4T",
337 | "type": "DataType::REALTIME",
338 | "engine": "Engine::PHPFINA",
339 | "interval": "10"
340 | },
341 | {
342 | "name": "n4R",
343 | "type": "DataType::REALTIME",
344 | "engine": "Engine::PHPFINA",
345 | "interval": "10"
346 | },
347 | {
348 | "name": "n4D",
349 | "type": "DataType::REALTIME",
350 | "engine": "Engine::PHPFINA",
351 | "interval": "10"
352 | },
353 | {
354 | "name": "iTS",
355 | "type": "DataType::REALTIME",
356 | "engine": "Engine::PHPFINA",
357 | "interval": "10"
358 | },
359 | {
360 | "name": "eTS",
361 | "type": "DataType::REALTIME",
362 | "engine": "Engine::PHPFINA",
363 | "interval": "10"
364 | },
365 | {
366 | "name": "eSR",
367 | "type": "DataType::REALTIME",
368 | "engine": "Engine::PHPFINA",
369 | "interval": "10"
370 | },
371 | {
372 | "name": "iTA",
373 | "type": "DataType::REALTIME",
374 | "engine": "Engine::PHPFINA",
375 | "interval": "10"
376 | },
377 | {
378 | "name": "iRH",
379 | "type": "DataType::REALTIME",
380 | "engine": "Engine::PHPFINA",
381 | "interval": "10"
382 | },
383 | {
384 | "name": "iDP",
385 | "type": "DataType::REALTIME",
386 | "engine": "Engine::PHPFINA",
387 | "interval": "10"
388 | },
389 | {
390 | "name": "eTA",
391 | "type": "DataType::REALTIME",
392 | "engine": "Engine::PHPFINA",
393 | "interval": "10"
394 | },
395 | {
396 | "name": "eRH",
397 | "type": "DataType::REALTIME",
398 | "engine": "Engine::PHPFINA",
399 | "interval": "10"
400 | },
401 | {
402 | "name": "eDP",
403 | "type": "DataType::REALTIME",
404 | "engine": "Engine::PHPFINA",
405 | "interval": "10"
406 | },
407 | {
408 | "name": "hfV",
409 | "type": "DataType::REALTIME",
410 | "engine": "Engine::PHPFINA",
411 | "interval": "10"
412 | },
413 | {
414 | "name": "hfW",
415 | "type": "DataType::REALTIME",
416 | "engine": "Engine::PHPFINA",
417 | "interval": "10"
418 | },
419 | {
420 | "name": "CO2",
421 | "type": "DataType::REALTIME",
422 | "engine": "Engine::PHPFINA",
423 | "interval": "10"
424 | },
425 | {
426 | "name": "SYS",
427 | "type": "DataType::REALTIME",
428 | "engine": "Engine::PHPFINA",
429 | "interval": "10"
430 | }]
431 | }
432 |
--------------------------------------------------------------------------------
/device_template.php:
--------------------------------------------------------------------------------
1 | mysqli = &$parent->mysqli;
27 | $this->redis = &$parent->redis;
28 | $this->log = new EmonLogger(__FILE__);
29 |
30 | global $user,$settings;
31 |
32 | require_once "Modules/feed/feed_model.php";
33 | $this->feed = new Feed($this->mysqli, $this->redis, $settings['feed']);
34 |
35 | require_once "Modules/input/input_model.php";
36 | $this->input = new Input($this->mysqli, $this->redis, $this->feed);
37 |
38 | require_once "Modules/process/process_model.php";
39 | $this->process = new Process($this->mysqli, $this->input, $this->feed,"UTC");
40 | }
41 |
42 | public function get_template_list() {
43 | return $this->load_template_list();
44 | }
45 |
46 | protected function load_template_list() {
47 | $list = array();
48 |
49 | $iti = new RecursiveDirectoryIterator("Modules/device/data");
50 | foreach(new RecursiveIteratorIterator($iti) as $file){
51 | if(strpos($file ,".json") !== false){
52 | $content = json_decode(file_get_contents($file));
53 | if (json_last_error() != 0) {
54 | return array('success'=>false, 'message'=>"Error reading file $file: ".json_last_error_msg());
55 | }
56 | $list[basename($file, ".json")] = $content;
57 | }
58 | }
59 | return $list;
60 | }
61 |
62 | public function get_template($type) {
63 | $type = preg_replace('/[^\p{L}_\p{N}\s\-:]/u','', $type);
64 | $result = $this->load_template_list();
65 | if (isset($result['success']) && $result['success'] == false) {
66 | return $result;
67 | }
68 | if (!isset($result[$type])) {
69 | return array('success'=>false, 'message'=>'Device template "'.$type.'" not found');
70 | }
71 | return $result[$type];
72 | }
73 |
74 | public function prepare_template($device) {
75 | $userid = intval($device['userid']);
76 |
77 | $result = $this->get_template($device['type']);
78 | if (!is_object($result)) {
79 | return $result;
80 | }
81 | $prefix = $this->parse_prefix($device['nodeid'], $device['name'], $result);
82 |
83 | if (isset($result->feeds)) {
84 | $feeds = $result->feeds;
85 | $this->prepare_feeds($userid, $device['nodeid'], $prefix, $feeds);
86 | }
87 | else {
88 | $feeds = array();
89 | }
90 |
91 | if (isset($result->inputs)) {
92 | $inputs = $result->inputs;
93 | $this->prepare_inputs($userid, $device['nodeid'], $prefix, $inputs);
94 | }
95 | else {
96 | $inputs = array();
97 | }
98 |
99 | if (!empty($feeds)) {
100 | $this->prepare_feed_processes($userid, $prefix, $feeds, $inputs);
101 | }
102 | if (!empty($inputs)) {
103 | $this->prepare_input_processes($userid, $prefix, $feeds, $inputs);
104 | }
105 |
106 | return array('success'=>true, 'feeds'=>$feeds, 'inputs'=>$inputs);
107 | }
108 |
109 | public function init_template($device, $template) {
110 | $userid = intval($device['userid']);
111 |
112 | if (empty($template)) {
113 | $result = $this->prepare_template($device);
114 | if (isset($result['success']) && $result['success'] == false) {
115 | return $result;
116 | }
117 | $template = $result;
118 | }
119 | if (!is_object($template)) $template = (object) $template;
120 |
121 | if (isset($template->feeds)) {
122 | $feeds = $template->feeds;
123 | $this->create_feeds($userid, $feeds);
124 | }
125 | else {
126 | $feeds = array();
127 | }
128 |
129 | if (isset($template->inputs)) {
130 | $inputs = $template->inputs;
131 | $this->create_inputs($userid, $inputs);
132 | }
133 | else {
134 | $inputs = array();
135 | }
136 |
137 | if (!empty($feeds)) {
138 | $this->create_feed_processes($userid, $feeds, $inputs);
139 | }
140 | if (!empty($inputs)) {
141 | $this->create_input_processes($userid, $feeds, $inputs);
142 | }
143 |
144 | return array('success'=>true, 'message'=>'Device initialized');
145 | }
146 |
147 | protected function prepare_feeds($userid, $nodeid, $prefix, &$feeds) {
148 |
149 | foreach($feeds as $f) {
150 | $f->name = $prefix.$f->name;
151 | if (!isset($f->tag)) {
152 | $f->tag = $nodeid;
153 | }
154 |
155 | $feedid = $this->feed->exists_tag_name($userid, $f->tag, $f->name);
156 | if ($feedid == false) {
157 | $f->action = 'create';
158 | $f->id = -1;
159 | }
160 | else {
161 | $f->action = 'none';
162 | $f->id = $feedid;
163 | }
164 | }
165 | }
166 |
167 | protected function prepare_inputs($userid, $nodeid, $prefix, &$inputs) {
168 |
169 | foreach($inputs as $i) {
170 | $i->name = $prefix.$i->name;
171 | if(!isset($i->node)) {
172 | $i->node = $nodeid;
173 | }
174 |
175 | $inputid = $this->input->exists_nodeid_name($userid, $i->node, $i->name);
176 | if ($inputid == false) {
177 | $i->action = 'create';
178 | $i->id = -1;
179 | }
180 | else {
181 | $i->action = 'none';
182 | $i->id = $inputid;
183 | }
184 | }
185 | }
186 |
187 | // Prepare the input process lists
188 | protected function prepare_input_processes($userid, $prefix, $feeds, &$inputs) {
189 |
190 | $process_list = $this->process->get_process_list(); // emoncms supported processes
191 |
192 | foreach($inputs as $i) {
193 | // for each input
194 | if (isset($i->id) && (isset($i->processList) || isset($i->processlist))) {
195 | $processes = isset($i->processList) ? $i->processList : $i->processlist;
196 | if (!empty($processes)) {
197 | $processes = $this->prepare_processes($prefix, $feeds, $inputs, $processes, $process_list);
198 | if (isset($i->action) && $i->action != 'create') {
199 | $processes_input = $this->input->get_processlist($i->id);
200 | if (!isset($processes['success'])) {
201 | if ($processes_input == '' && $processes != '') {
202 | $i->action = 'set';
203 | }
204 | else if ($processes_input != $processes) {
205 | $i->action = 'override';
206 | }
207 | }
208 | else {
209 | if ($processes_input == '') {
210 | $i->action = 'set';
211 | }
212 | else {
213 | $i->action = 'override';
214 | }
215 | }
216 | }
217 | }
218 | }
219 | }
220 | }
221 |
222 | // Prepare the feed process lists
223 | protected function prepare_feed_processes($userid, $prefix, &$feeds, $inputs) {
224 |
225 | $process_list = $this->process->get_process_list(); // emoncms supported processes
226 |
227 | foreach($feeds as $f) {
228 | // for each feed
229 | if ($f->engine == Engine::VIRTUALFEED && isset($f->id) && (isset($f->processList) || isset($f->processlist))) {
230 | $processes = isset($f->processList) ? $f->processList : $f->processlist;
231 | if (!empty($processes)) {
232 | $processes = $this->prepare_processes($prefix, $feeds, $inputs, $processes, $process_list);
233 | if (isset($f->action) && $f->action != 'create') {
234 | $processes_input = $this->feed->get_processlist($f->id);
235 | if (!isset($processes['success'])) {
236 | if ($processes_input == '' && $processes != '') {
237 | $f->action = 'set';
238 | }
239 | else if ($processes_input != $processes) {
240 | $f->action = 'override';
241 | }
242 | }
243 | else {
244 | if ($processes_input == '') {
245 | $f->action = 'set';
246 | }
247 | else {
248 | $f->action = 'override';
249 | }
250 | }
251 | }
252 | }
253 | }
254 | }
255 | }
256 |
257 | // Prepare template processes
258 | protected function prepare_processes($prefix, $feeds, $inputs, &$processes, $process_list) {
259 | $process_list_by_func = array();
260 | foreach ($process_list as $process_id => $process_item) {
261 | $func = $process_item['function'];
262 | $process_list_by_func[$func] = $process_id;
263 | }
264 | $processes_converted = array();
265 |
266 | $failed = false;
267 | foreach($processes as &$process) {
268 | // If process names are used map to process id
269 | if (isset($process_list_by_func[$process->process])) $process->process = $process_list_by_func[$process->process];
270 |
271 | if (!isset($process_list[$process->process])) {
272 | $this->log->error("prepare_processes() Process '$process->process' not supported. Module missing?");
273 | return array('success'=>false, 'message'=>"Process '$process->process' not supported. Module missing?");
274 | }
275 | $process->name = $process_list[$process->process]['name'];
276 | $process->short = $process_list[$process->process]['short'];
277 |
278 | // Arguments
279 | if(isset($process->arguments)) {
280 | if(isset($process->arguments->type)) {
281 | $process->arguments->type = @constant($process->arguments->type); // ProcessArg::
282 | $process_type = $process_list[$process->process]['argtype']; // get emoncms process ProcessArg
283 |
284 | if ($process_type != $process->arguments->type) {
285 | $this->log->error("prepare_processes() Bad device template. Missmatch ProcessArg type. Got '$process->arguments->type' expected '$process_type'. process='$process->process'");
286 | return array('success'=>false, 'message'=>"Bad device template. Missmatch ProcessArg type. Got '$process->arguments->type' expected '$process_type'. process='$process->process'");
287 | }
288 | else if ($process->arguments->type === ProcessArg::INPUTID || $process->arguments->type === ProcessArg::FEEDID) {
289 | $process->arguments->value = $prefix.$process->arguments->value;
290 | }
291 |
292 | $result = $this->convert_process($feeds, $inputs, $process, $process_list);
293 | if (isset($result['success'])) {
294 | $failed = true;
295 | }
296 | else {
297 | $processes_converted[] = $result;
298 | }
299 | }
300 | else {
301 | $this->log->error("prepare_processes() Bad device template. Argument type is missing, set to NONE if not required. process='$process->process' type='".$process->arguments->type."'");
302 | return array('success'=>false, 'message'=>"Bad device template. Argument type is missing, set to NONE if not required. process='$process->process' type='".$process->arguments->type."'");
303 | }
304 | }
305 | else {
306 | $this->log->error("prepare_processes() Bad device template. Missing processList arguments. process='$process->process'");
307 | return array('success'=>false, 'message'=>"Bad device template. Missing processList arguments. process='$process->process'");
308 | }
309 | }
310 | if (!$failed) {
311 | return implode(",", $processes_converted);
312 | }
313 | return array('success'=>false, 'message'=>"Unable to convert all prepared processes");
314 | }
315 |
316 | // Create the feeds
317 | protected function create_feeds($userid, &$feeds) {
318 |
319 | foreach($feeds as $f) {
320 | $datatype = constant($f->type); // DataType::
321 | $engine = constant($f->engine); // Engine::
322 | if (isset($f->unit)) $unit = $f->unit; else $unit = "";
323 |
324 | $options = new stdClass();
325 | if (property_exists($f, "interval")) {
326 | $options->interval = $f->interval;
327 | }
328 |
329 | if ($f->action === 'create') {
330 | $this->log->info("create_feeds() userid=$userid tag=$f->tag name=$f->name datatype=$datatype engine=$engine unit=$unit");
331 |
332 | $result = $this->feed->create($userid,$f->tag,$f->name,$datatype,$engine,$options,$unit);
333 | if($result['success'] !== true) {
334 | $this->log->error("create_feeds() failed for userid=$userid tag=$f->tag name=$f->name datatype=$datatype engine=$engine unit=$unit");
335 | }
336 | else {
337 | $f->id = $result["feedid"]; // Assign the created feed id to the feeds array
338 | }
339 | }
340 | }
341 | }
342 |
343 | // Create the inputs
344 | protected function create_inputs($userid, &$inputs) {
345 |
346 | foreach($inputs as $i) {
347 | if ($i->action === 'create') {
348 | $this->log->info("create_inputs() userid=$userid nodeid=$i->node name=$i->name description=$i->description");
349 |
350 | $inputid = $this->input->create_input($userid, $i->node, $i->name);
351 | if(!$this->input->exists($inputid)) {
352 | $this->log->error("create_inputs() failed for userid=$userid nodeid=$i->node name=$i->name description=$i->description");
353 | }
354 | else {
355 | $this->input->set_fields($inputid, '{"description":"'.$i->description.'"}');
356 | $i->id = $inputid; // Assign the created input id to the inputs array
357 | }
358 | }
359 | }
360 | }
361 |
362 | // Create the input process lists
363 | protected function create_input_processes($userid, $feeds, $inputs) {
364 |
365 | $process_list = $this->process->get_process_list(); // emoncms supported processes
366 |
367 | foreach($inputs as $i) {
368 | if ($i->action !== 'none') {
369 | if (isset($i->id) && (isset($i->processList) || isset($i->processlist))) {
370 | $processes = isset($i->processList) ? $i->processList : $i->processlist;
371 | $inputid = $i->id;
372 |
373 | if (is_array($processes)) {
374 | $processes_converted = array();
375 |
376 | $failed = false;
377 | foreach($processes as $process) {
378 | $result = $this->convert_process($feeds, $inputs, $process, $process_list);
379 | if (isset($result['success']) && !$result['success']) {
380 | $failed = true;
381 | break;
382 | }
383 | $processes_converted[] = $result;
384 | }
385 | $processes = implode(",", $processes_converted);
386 | if (!$failed && $processes != "") {
387 | $this->log->info("create_inputs_processes() calling input->set_processlist inputid=$inputid processes=$processes");
388 | $this->input->set_processlist($userid, $inputid, $processes, $process_list);
389 | }
390 | }
391 | }
392 | }
393 | }
394 | }
395 |
396 | // Create the feed process lists
397 | protected function create_feed_processes($userid, $feeds, $inputs) {
398 |
399 | $process_list = $this->process->get_process_list(); // emoncms supported processes
400 |
401 | foreach($feeds as $f) {
402 | if ($f->action !== 'none') {
403 | if ($f->engine == Engine::VIRTUALFEED && isset($f->id) && (isset($f->processList) || isset($f->processlist))) {
404 | $processes = isset($f->processList) ? $f->processList : $f->processlist;
405 | $feedid = $f->id;
406 |
407 | if (is_array($processes)) {
408 | $processes_converted = array();
409 |
410 | $failed = false;
411 | foreach($processes as $process) {
412 | $result = $this->convert_process($feeds, $inputs, $process, $process_list);
413 | if (isset($result['success']) && !$result['success']) {
414 | $failed = true;
415 | break;
416 | }
417 | $processes_converted[] = $result;
418 | }
419 | $processes = implode(",", $processes_converted);
420 | if (!$failed && $processes != "") {
421 | $this->log->info("create_feeds_processes() calling feed->set_processlist feedId=$feedid processes=$processes");
422 | $this->feed->set_processlist($userid, $feedid, $processes, $process_list);
423 | }
424 | }
425 | }
426 | }
427 | }
428 | }
429 |
430 | // Converts template process
431 | protected function convert_process($feeds, $inputs, $process, $process_list) {
432 | if (isset($process->arguments->value)) {
433 | $value = $process->arguments->value;
434 | }
435 | else if ($process->arguments->type === ProcessArg::NONE) {
436 | $value = 0;
437 | }
438 | else {
439 | $this->log->error("convertProcess() Bad device template. Undefined argument value. process='$process->process' type='".$process->arguments->type."'");
440 | return array('success'=>false, 'message'=>"Bad device template. Undefined argument value. process='$process->process' type='".$process->arguments->type."'");
441 | }
442 |
443 | if ($process->arguments->type === ProcessArg::VALUE) {
444 | }
445 | else if ($process->arguments->type === ProcessArg::INPUTID) {
446 | $temp = $this->search_array($inputs, 'name', $value); // return input array that matches $inputArray[]['name']=$value
447 | if (isset($temp->id) && $temp->id > 0) {
448 | $value = $temp->id;
449 | }
450 | else {
451 | $this->log->info("convertProcess() Input name '$value' was not found. process='$process->process' type='".$process->arguments->type."'");
452 | return array('success'=>false, 'message'=>"Input name '$value' was not found. process='$process->process' type='".$process->arguments->type."'");
453 | }
454 | }
455 | else if ($process->arguments->type === ProcessArg::FEEDID) {
456 | $temp = $this->search_array($feeds, 'name', $value); // return feed array that matches $feedArray[]['name']=$value
457 | if (isset($temp->id) && $temp->id > 0) {
458 | $value = $temp->id;
459 | }
460 | else {
461 | $this->log->info("convertProcess() Feed name '$value' was not found. process='$process->process' type='".$process->arguments->type."'");
462 | return array('success'=>false, 'message'=>"Feed name '$value' was not found. process='$process->process' type='".$process->arguments->type."'");
463 | }
464 | }
465 | else if ($process->arguments->type === ProcessArg::NONE) {
466 | $value = "";
467 | }
468 | else if ($process->arguments->type === ProcessArg::TEXT) {
469 | }
470 | else if ($process->arguments->type === ProcessArg::SCHEDULEID) {
471 | //not supporte for now
472 | }
473 | else {
474 | $this->log->error("convertProcess() Bad device template. Unsuported argument type. process='$process->process' type='".$process->arguments->type."'");
475 | return array('success'=>false, 'message'=>"Bad device template. Unsuported argument type. process='$process->process' type='".$process->arguments->type."'");
476 | }
477 |
478 | if (isset($process_list[$process->process]['id_num'])) {
479 | $id = $process_list[$process->process]['id_num'];
480 | }
481 | else {
482 | $id = $process->process;
483 | }
484 | $this->log->info("convertProcess() process process='$id' type='".$process->arguments->type."' value='" . $value . "'");
485 | return $id.":".$value;
486 | }
487 |
488 | protected function parse_prefix($nodeid, $name, $template) {
489 | if (isset($template->prefix)) {
490 | $prefix = $template->prefix;
491 | if ($prefix === "node") {
492 | return strtolower($nodeid)."_";
493 | }
494 | else if ($prefix === "name") {
495 | return strtolower($name)."_";
496 | }
497 | }
498 | return "";
499 | }
500 |
501 | protected function search_array($array, $key, $val) {
502 | foreach ($array as $item) {
503 | if (isset($item->$key) && $item->$key == $val) {
504 | return $item;
505 | }
506 | }
507 | return null;
508 | }
509 | }
510 |
--------------------------------------------------------------------------------
/Views/device_dialog.js:
--------------------------------------------------------------------------------
1 | var device_dialog =
2 | {
3 | templates: null,
4 | deviceTemplate: null,
5 | deviceType: null,
6 | device: null,
7 |
8 | 'loadConfig':function(templates, device) {
9 | this.templates = templates;
10 |
11 | if (device != null) {
12 | this.deviceTemplate = null;
13 | this.deviceType = device.type;
14 | this.device = device;
15 | }
16 | else {
17 | this.deviceTemplate = null;
18 | this.deviceType = null;
19 | this.device = null;
20 | }
21 |
22 | this.drawConfig();
23 | },
24 |
25 | 'drawConfig':function() {
26 | $("#device-config-modal").modal('show');
27 | this.adjustConfigModal();
28 | this.clearConfigModal();
29 |
30 | var categories = [];
31 | var devicesByCategory = {};
32 | for (var id in this.templates) {
33 | var device = this.templates[id];
34 | device['id'] = id;
35 |
36 | if (devicesByCategory[device.category] == undefined) {
37 | devicesByCategory[device.category] = [];
38 | categories.push(device.category);
39 | }
40 | devicesByCategory[device.category].push(device);
41 | }
42 | // Place OpenEnergyMonitor prominently at first place, while sorting other categories
43 | if (categories.indexOf('OpenEnergyMonitor') > -1) {
44 | categories.splice(categories.indexOf('OpenEnergyMonitor'), 1);
45 | categories.sort()
46 | categories = ['OpenEnergyMonitor'].concat(categories);
47 | }
48 |
49 | for (var i in categories) {
50 | var category = categories[i];
51 | var categoryid = category.replace(/\W/g, '').toLowerCase();
52 |
53 | var groups = [];
54 | var devicesByGroup = {};
55 | for (var i in devicesByCategory[category]) {
56 | var group = devicesByCategory[category][i].group;
57 | if (devicesByGroup[group] == undefined) {
58 | devicesByGroup[group] = [];
59 | groups.push(group);
60 | }
61 | devicesByGroup[group].push(devicesByCategory[category][i]);
62 | }
63 | groups.sort();
64 |
65 | $('#template-list').append(
66 | "" +
67 | "
" +
68 | "" +
70 | category +
71 | "" +
72 | "
" +
73 | "
" +
78 | "
"
79 | );
80 |
81 | for (var i in groups) {
82 | var group = groups[i];
83 | var groupid = group.replace(/\W/g, '').toLowerCase();
84 | $('#template-'+categoryid).append(
85 | "" +
86 | "
" +
87 | "" +
89 | group +
90 | "" +
91 | "
" +
92 | "
" +
95 | "
"
96 | );
97 | var body = $('#template-'+categoryid+'-'+groupid);
98 |
99 | for (var i in devicesByGroup[group]) {
100 | var id = devicesByGroup[group][i].id;
101 | var name = devicesByGroup[group][i].name;
102 | if (name.length > 25) {
103 | name = name.substr(0, 25) + "...";
104 | }
105 |
106 | body.append(
107 | "" +
109 | ""+name+"" +
110 | "
"
111 | );
112 | }
113 | }
114 | }
115 |
116 | if (this.deviceType != null && this.deviceType != '') {
117 | if (this.templates[this.deviceType]!=undefined) {
118 | var template = this.templates[this.deviceType];
119 | var category = template.category.replace(/\W/g, '').toLowerCase();
120 | var group = template.group.replace(/\W/g, '').toLowerCase();
121 | var id = this.deviceType.replace('/', '-');
122 |
123 | $("#template-"+category+"-collapse").collapse('show');
124 | $("#template-"+category+"-"+group+"-collapse").collapse('show');
125 | $("#template-"+category+"-"+group+"-"+id).addClass("device-selected");
126 |
127 | $('#template-description').html(''+template.description+'');
128 | $('#template-info').show();
129 | }
130 | }
131 |
132 | // Initialize callbacks
133 | this.registerConfigEvents();
134 | },
135 |
136 | 'clearConfigModal':function() {
137 | $("#template-list").text('');
138 |
139 | var tooltip = "Defaults, like inputs and associated feeds will be automaticaly configured together with the device.
" +
140 | "Initializing a device usualy should only be done once on installation. " +
141 | "If the configuration was already applied, only missing inputs and feeds will be created.";
142 |
143 | $('#template-tooltip').attr("title", tooltip).tooltip({html: true});
144 |
145 | if (this.device != null) {
146 | $('#device-config-node').val(this.device.nodeid);
147 | $('#device-config-name').val(this.device.name);
148 | $('#device-config-description').val(this.device.description);
149 | $('#device-config-devicekey').val(this.device.devicekey).prop("disabled", false);
150 | $("#device-config-devicekey-new").prop("disabled", false);
151 | $('#device-delete').show();
152 | $("#device-save").html("Save");
153 | if (this.device.type != null && this.device.type != '') {
154 | $("#device-init").show();
155 | $('#select-device-alert').addClass('hidden')
156 | } else {
157 | $("#device-init").hide();
158 | $('#device-sidebar').addClass('show')
159 | $('#select-device-alert').removeClass('hidden')
160 | }
161 | }
162 | else {
163 | $('#device-config-node').val('');
164 | $('#device-config-name').val('');
165 | $('#device-config-description').val('');
166 | $('#device-config-devicekey').val('').prop("disabled", true);
167 | $("#device-config-devicekey-new").prop("disabled", true);
168 | $('#device-delete').hide();
169 | $("#device-init").hide();
170 | $("#device-save").html("Save & Initialize");
171 | }
172 | device_dialog.drawTemplate();
173 | },
174 |
175 | 'adjustConfigModal':function() {
176 |
177 | var width = $(window).width();
178 | var height = $(window).height();
179 |
180 | if ($("#device-config-modal").length) {
181 | var h = height - $("#device-config-modal").position().top - 180;
182 | $("#device-config-body").height(h);
183 | }
184 |
185 | if (width < 680) {
186 | $("#device-sidebar-open").show();
187 | $("#device-sidebar-close").show();
188 | $("#device-sidebar").removeClass('show')
189 | } else {
190 | $("#device-sidebar-open").hide();
191 | $("#device-sidebar-close").hide();
192 | $("#device-sidebar").addClass('show')
193 | }
194 | },
195 |
196 | 'registerConfigEvents':function() {
197 |
198 | $("#template-list").off('click').on('click', '.group-device', function () {
199 | var type = $(this).data("type");
200 |
201 | $(".group-device[data-type='"+device_dialog.deviceType+"']").removeClass("device-selected");
202 | if (device_dialog.deviceType !== type) {
203 | $(this).addClass("device-selected");
204 | device_dialog.deviceType = type;
205 |
206 | var template = device_dialog.templates[type];
207 | $('#template-description').html(''+template.description+'');
208 | $('#template-info').show();
209 | $("#device-init").hide();
210 | }
211 | else {
212 | device_dialog.deviceType = null;
213 |
214 | $('#template-description').text('');
215 | $('#template-info').hide();
216 | $("#device-init").show()
217 | }
218 | if ($(window).width() < 680) {
219 | $("#device-sidebar").removeClass('show')
220 | }
221 |
222 | device_dialog.drawTemplate();
223 | });
224 |
225 | $("#device-sidebar-open").off('click').on('click', function () {
226 | $("#device-sidebar").addClass('show')
227 | });
228 |
229 | $("#device-sidebar-close").off('click').on('click', function () {
230 | $("#device-sidebar").removeClass('show')
231 | });
232 |
233 | $("#device-save").off('click').on('click', function () {
234 |
235 | var node = $('#device-config-node').val();
236 | var name = $('#device-config-name').val();
237 |
238 | if (name && node) {
239 | var desc = $('#device-config-description').val();
240 | var devicekey = $('#device-config-devicekey').val();
241 |
242 | var init = false;
243 | if (device_dialog.device != null) {
244 | var fields = {};
245 | if (device_dialog.device.nodeid != node) fields['nodeid'] = node;
246 | if (device_dialog.device.name != name) fields['name'] = name;
247 | if (device_dialog.device.description != desc) fields['description'] = desc;
248 | if (device_dialog.device.devicekey != devicekey) fields['devicekey'] = devicekey;
249 |
250 | if (device_dialog.device.type != device_dialog.deviceType) {
251 | if (device_dialog.deviceType != null) {
252 | fields['type'] = device_dialog.deviceType;
253 | init = true;
254 | }
255 | else fields['type'] = '';
256 | }
257 |
258 | var result = device.set(device_dialog.device.id, fields);
259 | if (typeof result.success !== 'undefined' && !result.success) {
260 | alert('Unable to update device fields:\n'+result.message);
261 | return false;
262 | }
263 | update();
264 | }
265 | else {
266 | var type = device_dialog.deviceType;
267 | var result = device.create(node, name, desc, type);
268 | if (typeof result.success !== 'undefined' && !result.success) {
269 | alert('Unable to create device:\n'+result.message);
270 | return false;
271 | }
272 | if (type != null) {
273 | device_dialog.device = {
274 | id: result,
275 | nodeid: node,
276 | name: name,
277 | type: type
278 | };
279 | init = true;
280 | }
281 | update();
282 | }
283 | $('#device-config-modal').modal('hide');
284 | if (init) device_dialog.loadInit();
285 | }
286 | else {
287 | alert('Device needs to be configured first.');
288 | return false;
289 | }
290 | });
291 |
292 | $("#device-delete").off('click').on('click', function () {
293 | $('#device-config-modal').modal('hide');
294 | device_dialog.loadDelete(device_dialog.device, null);
295 | });
296 |
297 | $("#device-init").off('click').on('click', function () {
298 | $('#device-config-modal').modal('hide');
299 | device_dialog.loadInit();
300 | });
301 |
302 | $("#device-config-devicekey-new").off('click').on('click', function () {
303 | device_dialog.device.devicekey = device.setNewDeviceKey(device_dialog.device.id);
304 | $('#device-config-devicekey').val(device_dialog.device.devicekey);
305 | });
306 | },
307 |
308 | 'drawTemplate':function() {
309 | if (device_dialog.deviceType == null || device_dialog.deviceType == "" || !device_dialog.deviceType in device_dialog.templates) {
310 | $('#template-description').text('');
311 | $('#template-info').hide();
312 | return;
313 | }
314 |
315 | // console.log("deviceType:"+device_dialog.deviceType)
316 | // console.log(device_dialog.templates)
317 | var template = device_dialog.templates[device_dialog.deviceType];
318 | if (template!=undefined) {
319 | $('#template-description').html(''+template.description+'');
320 | }
321 | $('#template-info').show();
322 | },
323 |
324 | 'loadInit': function() {
325 | var result = device.prepareTemplate(device_dialog.device.id);
326 | if (typeof result.success !== 'undefined' && !result.success) {
327 | alert('Unable to initialize device:\n'+result.message);
328 | return false;
329 | }
330 | device_dialog.deviceTemplate = result;
331 | device_dialog.drawInit(result);
332 |
333 | // Initialize callbacks
334 | $("#device-init-confirm").off('click').on('click', function() {
335 | $('#device-init-modal').modal('hide');
336 |
337 | var template = device_dialog.parseTemplate();
338 | var result = device.init(device_dialog.device.id, template);
339 | if (typeof result.success !== 'undefined' && !result.success) {
340 | alert('Unable to initialize device:\n'+result.message);
341 | return false;
342 | }
343 |
344 | $('#wrap').trigger("device-init");
345 | });
346 | },
347 |
348 | 'drawInit': function (result) {
349 | $('#device-init-modal').modal('show');
350 | device_dialog.adjustInitModal();
351 |
352 | $('#device-init-modal-label').html('Initialize Device: '+device_dialog.device.name+'');
353 |
354 | if (typeof result.feeds !== 'undefined' && result.feeds.length > 0) {
355 | $('#device-init-feeds').show();
356 | var table = "";
357 | for (var i = 0; i < result.feeds.length; i++) {
358 | var feed = result.feeds[i];
359 | var row = "";
360 | if (feed.action.toLowerCase() == "none") {
361 | row += " | ";
362 | }
363 | else {
364 | row += " | ";
365 | }
366 | row += ""+device_dialog.drawInitAction(feed.action)+" | "
367 | row += ""+feed.tag+" | "+feed.name+" | ";
368 | row += ""+device_dialog.drawInitProcessList(feed.processList)+" | ";
369 |
370 | table += ""+row+"
";
371 | }
372 | $('#device-init-feeds-table').html(table);
373 | }
374 | else {
375 | $('#device-init-feeds').hide();
376 | }
377 |
378 | if (typeof result.inputs !== 'undefined' && result.inputs.length > 0) {
379 | $('#device-init-inputs').show();
380 | var table = "";
381 | for (var i = 0; i < result.inputs.length; i++) {
382 | var input = result.inputs[i];
383 | var row = "";
384 | if (input.action.toLowerCase() == "none") {
385 | row += " | ";
386 | }
387 | else {
388 | row += " | ";
389 | }
390 | row += ""+device_dialog.drawInitAction(input.action)+" | "
391 | row += ""+input.node+" | "+input.name+" | "+input.description+" | ";
392 | row += ""+device_dialog.drawInitProcessList(input.processList)+" | ";
393 |
394 | table += ""+row+"
";
395 | }
396 | $('#device-init-inputs-table').html(table);
397 | }
398 | else {
399 | $('#device-init-inputs').hide();
400 | $('#device-init-inputs-table').html("");
401 | }
402 |
403 | return true;
404 | },
405 |
406 | 'drawInitAction': function (action) {
407 | action = action.toLowerCase();
408 |
409 | var color;
410 | if (action === 'create' || action === 'set') {
411 | color = "rgb(0,110,205)";
412 | }
413 | else if (action === 'override') {
414 | color = "rgb(255,125,20)";
415 | }
416 | else {
417 | color = "rgb(50,200,50)";
418 | action = "exists"
419 | }
420 | action = action.charAt(0).toUpperCase() + action.slice(1);
421 |
422 | return ""+action+"";
423 | },
424 |
425 | 'drawInitProcessList': function (processList) {
426 | if (!processList || processList.length < 1) return "";
427 |
428 | var out = "";
429 | for (var i = 0; i < processList.length; i++) {
430 | var process = processList[i];
431 | if (process['arguments'] != undefined && process['arguments']['type'] != undefined) {
432 | var title;
433 | var label;
434 | switch(process['arguments']['type']) {
435 | case 0: // VALUE
436 | label = "important";
437 | title = "Value - ";
438 | break;
439 |
440 | case 1: //INPUTID
441 | label = "warning";
442 | title = "Input - ";
443 | break;
444 |
445 | case 2: //FEEDID
446 | label = "info";
447 | title = "Feed - ";
448 | break;
449 |
450 | case 3: // NONE
451 | label = "important";
452 | title = "";
453 | break;
454 |
455 | case 4: // TEXT
456 | label = "important";
457 | title = "Text - ";
458 | break;
459 |
460 | case 5: // SCHEDULEID
461 | label = "warning";
462 | title = "Schedule - "
463 | break;
464 |
465 | default:
466 | label = "important";
467 | title = "ERROR - ";
468 | break;
469 | }
470 | title += process["name"];
471 |
472 | if (process['arguments']['value'] != undefined) {
473 | title += ": " + process['arguments']['value'];
474 | }
475 |
476 | out += ""+process["short"]+" ";
477 | }
478 | }
479 | return out;
480 | },
481 |
482 | 'adjustInitModal':function() {
483 |
484 | var width = $(window).width();
485 | var height = $(window).height();
486 |
487 | if ($("#device-init-modal").length) {
488 | var h = height - $("#device-init-modal").position().top - 180;
489 | $("#device-init-body").height(h);
490 | }
491 | },
492 |
493 | 'parseTemplate': function() {
494 | var template = {};
495 |
496 | template['feeds'] = [];
497 | if (typeof device_dialog.deviceTemplate.feeds !== 'undefined' &&
498 | device_dialog.deviceTemplate.feeds.length > 0) {
499 |
500 | var feeds = device_dialog.deviceTemplate.feeds;
501 | $("#device-init-feeds-table tr").find('input[type="checkbox"]:checked').each(function() {
502 | template['feeds'].push(feeds[$(this).attr("row")]);
503 | });
504 | }
505 |
506 | template['inputs'] = [];
507 | if (typeof device_dialog.deviceTemplate.inputs !== 'undefined' &&
508 | device_dialog.deviceTemplate.inputs.length > 0) {
509 |
510 | var inputs = device_dialog.deviceTemplate.inputs;
511 | $("#device-init-inputs-table tr").find('input[type="checkbox"]:checked').each(function() {
512 | template['inputs'].push(inputs[$(this).attr("row")]);
513 | });
514 | }
515 |
516 | return template;
517 | },
518 |
519 | 'loadDelete': function(device, tablerow) {
520 | this.device = device;
521 |
522 | $('#device-delete-modal').modal('show');
523 | $('#device-delete-modal-label').html('Delete Device: '+device.name+'');
524 |
525 | // Initialize callbacks
526 | this.registerDeleteEvents(tablerow);
527 | },
528 |
529 | 'registerDeleteEvents':function(row) {
530 |
531 | $("#device-delete-confirm").off('click').on('click', function() {
532 | device.remove(device_dialog.device.id);
533 | if (row != null) {
534 | table.remove(row);
535 | update();
536 | }
537 | else if (typeof device_dialog.device.inputs !== 'undefined') {
538 | // If the table row is undefined and an input list exists, the config dialog
539 | // was opened in the input view and all corresponding inputs will be deleted
540 | var inputIds = [];
541 | for (var i in device_dialog.device.inputs) {
542 | var inputId = device_dialog.device.inputs[i].id;
543 | inputIds.push(parseInt(inputId));
544 | }
545 | input.delete_multiple_async(inputIds)
546 | .done(function(){
547 | update();
548 | });
549 | }
550 | $('#device-delete-modal').modal('hide');
551 | $('#wrap').trigger("device-delete");
552 | });
553 | }
554 | }
555 |
--------------------------------------------------------------------------------
/device_model.php:
--------------------------------------------------------------------------------
1 | mysqli = $mysqli;
26 | $this->redis = $redis;
27 | $this->templates = array();
28 | $this->log = new EmonLogger(__FILE__);
29 | }
30 |
31 | public function devicekey_session($devicekey) {
32 | // 1. Only allow alphanumeric characters
33 | // if (!ctype_alnum($devicekey)) return array();
34 |
35 | // 2. Only allow 32 character length
36 | if (strlen($devicekey)!=32) return array();
37 |
38 | $session = array();
39 | $time = time();
40 |
41 | //----------------------------------------------------
42 | // Check for devicekey login
43 | //----------------------------------------------------
44 | if($this->redis && $this->redis->exists("device:key:$devicekey")) {
45 | $session['userid'] = $this->redis->get("device:key:$devicekey:user");
46 | $session['read'] = 1;
47 | $session['write'] = 1;
48 | $session['admin'] = 0;
49 | $session['lang'] = "en"; // API access is always in english
50 | $session['username'] = "API";
51 | $session['deviceid'] = $this->redis->get("device:key:$devicekey:device");
52 | $session['nodeid'] = $this->redis->get("device:key:$devicekey:node");
53 | $this->redis->hMset("device:lastvalue:".$session['device'], array('time' => $time));
54 | }
55 | else {
56 | $stmt = $this->mysqli->prepare("SELECT id, userid, nodeid FROM device WHERE devicekey=?");
57 | $stmt->bind_param("s",$devicekey);
58 | $stmt->execute();
59 | $stmt->bind_result($id,$userid,$nodeid);
60 | $result = $stmt->fetch();
61 | $stmt->close();
62 |
63 | if ($result && $id>0) {
64 | $session['userid'] = $userid;
65 | $session['read'] = 1;
66 | $session['write'] = 1;
67 | $session['admin'] = 0;
68 | $session['lang'] = "en"; // API access is always in english
69 | $session['username'] = "API";
70 | $session['deviceid'] = $id;
71 | $session['nodeid'] = $nodeid;
72 |
73 | if ($this->redis) {
74 | $this->redis->set("device:key:$devicekey:user",$userid);
75 | $this->redis->set("device:key:$devicekey:device",$id);
76 | $this->redis->set("device:key:$devicekey:node",$nodeid);
77 | $this->redis->hMset("device:lastvalue:$id", array('time' => $time));
78 | } else {
79 | //$time = date("Y-n-j H:i:s", $time);
80 | $this->mysqli->query("UPDATE device SET time='$time' WHERE id = '$id");
81 | }
82 | }
83 | }
84 |
85 | return $session;
86 | }
87 |
88 | public function exist($id) {
89 | static $device_exists_cache = array(); // Array to hold the cache
90 | if (isset($device_exists_cache[$id])) {
91 | $device_exist = $device_exists_cache[$id]; // Retrieve from static cache
92 | }
93 | else {
94 | $device_exist = false;
95 | if ($this->redis) {
96 | if (!$this->redis->exists("device:$id")) {
97 | if ($this->load_device_to_redis($id)) {
98 | $device_exist = true;
99 | }
100 | }
101 | else {
102 | $device_exist = true;
103 | }
104 | }
105 | else {
106 | $id = (int) $id;
107 | $result = $this->mysqli->query("SELECT id FROM device WHERE id = '$id'");
108 | if ($result->num_rows > 0) $device_exist = true;
109 | }
110 | $device_exists_cache[$id] = $device_exist; // Cache it
111 | }
112 | return $device_exist;
113 | }
114 |
115 | public function exists_name($userid, $name) {
116 | $userid = intval($userid);
117 | $name = preg_replace('/[^\p{L}_\p{N}\s\-:]/u','',$name);
118 |
119 | $stmt = $this->mysqli->prepare("SELECT id,name FROM device WHERE userid=? AND name=?");
120 | $stmt->bind_param("is", $userid, $name);
121 | $stmt->execute();
122 | $stmt->bind_result($id,$_name);
123 | $result = $stmt->fetch();
124 | $stmt->close();
125 |
126 | // SQL search may not be case sensitive
127 | if ($_name!=$name) return false;
128 |
129 | if ($result && $id > 0) return $id; else return false;
130 | }
131 |
132 | public function exists_nodeid($userid, $nodeid) {
133 | $userid = intval($userid);
134 | $nodeid = preg_replace('/[^\p{L}_\p{N}\s\-:]/u','',$nodeid);
135 |
136 | $stmt = $this->mysqli->prepare("SELECT id,nodeid FROM device WHERE userid=? AND nodeid=?");
137 | $stmt->bind_param("is", $userid, $nodeid);
138 | $stmt->execute();
139 | $stmt->bind_result($id,$_nodeid);
140 | $result = $stmt->fetch();
141 | $stmt->close();
142 |
143 | // SQL search may not be case sensitive
144 | if ($_nodeid!=$nodeid) return false;
145 |
146 | if ($result && $id > 0) return $id; else return false;
147 | }
148 |
149 | public function request_auth($ip) {
150 | if (!$this->redis) {
151 | return array("success"=>false, "message"=>"Unable to handle authentication requests without redis");
152 | }
153 | $ip_parts = explode(".", $ip);
154 | for ($i=0; $iredis->get("device:auth:allow");
158 | // Only show authentication details to allowed ip address
159 | if ($allow_ip == $ip) {
160 | $this->redis->del("device:auth:allow");
161 | global $settings;
162 | return $settings['mqtt']['user'].":".$settings['mqtt']['password'].":".$settings['mqtt']['basetopic'];
163 | } else {
164 | $this->redis->set("device:auth:request", json_encode(array("ip"=>$ip)));
165 | return array("success"=>true, "message"=>"Authentication request registered for IP $ip");
166 | }
167 | }
168 |
169 | public function get_auth_request() {
170 | if (!$this->redis) {
171 | return array("success"=>false, "message"=>"Unable to handle authentication requests without redis");
172 | }
173 | if ($device_auth = $this->redis->get("device:auth:request")) {
174 | $device_auth = json_decode($device_auth);
175 | return array_merge(array("success"=>true, "ip"=>$device_auth->ip));
176 | } else {
177 | return array("success"=>true, "message"=>"No authentication request registered");
178 | }
179 | }
180 |
181 | public function allow_auth_request($ip) {
182 | if (!$this->redis) {
183 | return array("success"=>false, "message"=>"Unable to handle authentication requests without redis");
184 | }
185 | $ip_parts = explode(".", $ip);
186 | for ($i=0; $iredis->set("device:auth:allow", $ip); // Temporary availability of auth for device ip address
190 | $this->redis->expire("device:auth:allow", 60); // Expire after 60 seconds
191 | $this->redis->del("device:auth:request");
192 |
193 | return array("success"=>true, "message"=>"Authentication request allowed for IP $ip");
194 | }
195 |
196 | public function get($id) {
197 | $id = intval($id);
198 | if (!$this->exist($id)) {
199 | if (!$this->redis || !$this->load_device_to_redis($id)) {
200 | return array('success'=>false, 'message'=>'Device does not exist');
201 | }
202 | }
203 |
204 | if ($this->redis) {
205 | // Get from redis cache
206 | $device = (array) $this->redis->hGetAll("device:$id");
207 | // Verify, if the cached device contains the userid, to avoid compatibility issues
208 | // with former versions where the userid was not cached.
209 | if (!isset($device['userid'])) {
210 | $this->load_device_to_redis($id);
211 | $device = $this->get($id);
212 | }
213 | $device['time'] = $this->redis->hget("device:lastvalue:".$id, 'time');
214 | }
215 | else {
216 | // Get from mysql db
217 | $result = $this->mysqli->query("SELECT `id`,`userid`,`nodeid`,`name`,`description`,`type`,`devicekey`,`time` FROM device WHERE id = '$id'");
218 | $device = (array) $result->fetch_object();
219 | }
220 | return $device;
221 | }
222 |
223 | public function get_list($userid) {
224 | if ($this->redis) {
225 | return $this->get_list_redis($userid);
226 | } else {
227 | return $this->get_list_mysql($userid);
228 | }
229 | }
230 |
231 | private function get_list_redis($userid) {
232 | $userid = intval($userid);
233 |
234 | if (!$this->redis->exists("user:device:$userid")) {
235 | $this->load_list_to_redis($userid);
236 | }
237 |
238 | $devices = array();
239 | $deviceids = $this->redis->sMembers("user:device:$userid");
240 | foreach ($deviceids as $id) {
241 | $device = $this->redis->hGetAll("device:$id");
242 | // Verify, if the cached device contains the userid, to avoid compatibility issues
243 | // with former versions where the userid was not cached.
244 | if (!isset($device['userid'])) {
245 | $this->load_device_to_redis($id);
246 | $device = $this->get($id);
247 | }
248 | $device['time'] = $this->redis->hget("device:lastvalue:".$id, 'time');
249 | $devices[] = $device;
250 | }
251 | usort($devices, function($d1, $d2) {
252 | if($d1['nodeid'] == $d2['nodeid'])
253 | return strcmp($d1['name'], $d2['name']);
254 | return strcmp($d1['nodeid'], $d2['nodeid']);
255 | });
256 | return $devices;
257 | }
258 |
259 | private function get_list_mysql($userid) {
260 | $userid = intval($userid);
261 |
262 | $devices = array();
263 | $result = $this->mysqli->query("SELECT `id`,`userid`,`nodeid`,`name`,`description`,`type`,`devicekey`,`time` FROM device WHERE userid = '$userid' ORDER BY nodeid, name asc");
264 | while ($device = (array) $result->fetch_object()) {
265 | $devices[] = $device;
266 | }
267 | return $devices;
268 | }
269 |
270 | private function load_list_to_redis($userid) {
271 | $userid = intval($userid);
272 |
273 | $result = $this->mysqli->query("SELECT `id`,`userid`,`nodeid`,`name`,`description`,`type`,`devicekey` FROM device WHERE userid = '$userid'");
274 | while ($row = $result->fetch_object()) {
275 | $this->redis->sAdd("user:device:$userid", $row->id);
276 | $this->redis->hMSet("device:".$row->id, array(
277 | 'id'=>$row->id,
278 | 'userid'=>$row->userid,
279 | 'nodeid'=>$row->nodeid,
280 | 'name'=>$row->name,
281 | 'description'=>$row->description,
282 | 'type'=>$row->type,
283 | 'devicekey'=>$row->devicekey
284 | ));
285 | }
286 | }
287 |
288 | private function load_device_to_redis($id) {
289 | $id = intval($id);
290 |
291 | $result = $this->mysqli->query("SELECT `id`,`userid`,`nodeid`,`name`,`description`,`type`,`devicekey` FROM device WHERE id = '$id'");
292 | $row = $result->fetch_object();
293 | if (!$row) {
294 | $this->log->warn("Device model: Requested device does not exist for id=$id");
295 | return false;
296 | }
297 | $this->redis->hMSet("device:".$row->id, array(
298 | 'id'=>$row->id,
299 | 'userid'=>$row->userid,
300 | 'nodeid'=>$row->nodeid,
301 | 'name'=>$row->name,
302 | 'description'=>$row->description,
303 | 'type'=>$row->type,
304 | 'devicekey'=>$row->devicekey
305 | ));
306 | return true;
307 | }
308 |
309 | public function autocreate($userid, $_nodeid, $_type) {
310 | $userid = intval($userid);
311 |
312 | $nodeid = preg_replace('/[^\p{L}_\p{N}\s\-:]/u','',$_nodeid);
313 | if ($_nodeid != $nodeid) return array("success"=>false, "message"=>"Invalid nodeid");
314 | $type = preg_replace('/[^\/\|\,\w\s\-:]/','',$_type);
315 | if ($_type != $type) return array("success"=>false, "message"=>"Invalid type");
316 |
317 | $name = "$nodeid:$type";
318 |
319 | $deviceid = $this->exists_nodeid($userid, $nodeid);
320 |
321 | if (!$deviceid) {
322 | $this->log->info("Automatically create device for user=$userid, nodeid=$nodeid");
323 | $deviceid = $this->create($userid, $nodeid, null, null, null);
324 | if (!$deviceid) return array("success"=>false, "message"=>"Device creation failed");
325 | }
326 |
327 | $result = $this->set_fields($deviceid,json_encode(array("name"=>$name,"nodeid"=>$nodeid,"type"=>$type)));
328 | if ($result['success']==true) {
329 | return $this->init($deviceid,false);
330 | } else {
331 | return $result;
332 | }
333 | }
334 |
335 | public function create($userid, $nodeid, $name, $description, $type) {
336 | $userid = intval($userid);
337 | $nodeid = preg_replace('/[^\p{L}_\p{N}\s\-:]/u', '', $nodeid);
338 |
339 | if (isset($name)) {
340 | $name = preg_replace('/[^\p{L}_\p{N}\s\-:]/u', '', $name);
341 | } else {
342 | $name = $nodeid;
343 | }
344 |
345 | if (isset($description)) {
346 | $description = preg_replace('/[^\p{L}_\p{N}\s\-:]/u', '', $description);
347 | } else {
348 | $description = '';
349 | }
350 |
351 | if (isset($type) && $type != 'null') {
352 | $type = preg_replace('/[^\/\|\,\w\s\-:]/','', $type);
353 | } else {
354 | $type = '';
355 | }
356 |
357 | if (!$this->exists_nodeid($userid, $nodeid)) {
358 | // device key disabled by default
359 | $devicekey = ""; // md5(uniqid(mt_rand(), true));
360 |
361 | $stmt = $this->mysqli->prepare("INSERT INTO device (userid,nodeid,name,description,type,devicekey) VALUES (?,?,?,?,?,?)");
362 | $stmt->bind_param("isssss",$userid,$nodeid,$name,$description,$type,$devicekey);
363 | $result = $stmt->execute();
364 | $stmt->close();
365 | if (!$result) return array('success'=>false, 'message'=>_("Error creating device"));
366 |
367 | $deviceid = $this->mysqli->insert_id;
368 |
369 | if ($deviceid > 0) {
370 | // Add the device to redis
371 | if ($this->redis) {
372 | // Reload all devices from mysql here to ensure cache is not out of sync
373 | $this->load_list_to_redis($userid);
374 | $this->redis->sAdd("user:device:$userid", $deviceid);
375 | $this->redis->hMSet("device:".$deviceid, array(
376 | 'id'=>$deviceid,
377 | 'userid'=>$userid,
378 | 'nodeid'=>$nodeid,
379 | 'name'=>$name,
380 | 'description'=>$description,
381 | 'type'=>$type,
382 | 'devicekey'=>$devicekey
383 | ));
384 | }
385 | return $deviceid;
386 | }
387 | return array('success'=>false, 'result'=>"SQL returned invalid insert feed id");
388 | }
389 | return array('success'=>false, 'message'=>'Device already exists');
390 | }
391 |
392 | public function delete($id) {
393 | $id = intval($id);
394 | if (!$this->exist($id)) {
395 | if (!$this->redis || !$this->load_device_to_redis($id)) {
396 | return array('success'=>false, 'message'=>'Device does not exist');
397 | }
398 | }
399 |
400 | $this->mysqli->query("DELETE FROM device WHERE `id` = '$id'");
401 | if (isset($device_exists_cache[$id])) { unset($device_exists_cache[$id]); } // Clear static cache
402 |
403 | if ($this->redis) {
404 | $userid = $this->redis->hget("device:$id",'userid');
405 | if (isset($userid)) {
406 | $this->redis->srem("user:device:$userid", $id);
407 | $this->redis->del("device:$id");
408 | }
409 | }
410 | }
411 |
412 | // Clear devices with empty input processLists
413 | public function clean($userid,$active=0,$dryrun=0) {
414 | $userid = (int) $userid;
415 | $active = (int) $active;
416 |
417 | $now = time();
418 |
419 | $deleted_inputs = 0;
420 | $deleted_nodes = 0;
421 |
422 | $result = $this->mysqli->query("SELECT `id`,`userid`,`nodeid`,`name`,`description`,`type`,`devicekey` FROM device WHERE userid = '$userid'");
423 | while ($row = $result->fetch_object()) {
424 |
425 | $id = $row->id;
426 | $nodeid = $row->nodeid;
427 |
428 | // Fetch inputs associated with node
429 | $inputs = array();
430 | if ($result2 = $this->mysqli->query("SELECT * FROM input WHERE `userid` = '$userid' AND `nodeid` = '$nodeid'")) {
431 | while ($row2 = $result2->fetch_object()) $inputs[] = $row2;
432 | }
433 |
434 | // Check that all node inputs are empty
435 | $inputs_empty = true;
436 | foreach ($inputs as $i) {
437 | $inputid = $i->id;
438 |
439 | if ($i->processList!=NULL && $i->processList!='') {
440 | $inputs_empty = false;
441 | }
442 |
443 | if ($active && $this->redis) {
444 | $input_time = $this->redis->hget("input:lastvalue:$inputid",'time');
445 | if (($now-$input_time)<$active) {
446 | $inputs_empty = false;
447 | }
448 | }
449 | }
450 |
451 | if ($inputs_empty) {
452 | // Delete node
453 | if (!$dryrun) $this->delete($id);
454 |
455 | // Delete inputs
456 | foreach ($inputs as $i) {
457 | $inputid = $i->id;
458 | if (!$dryrun) {
459 | $this->mysqli->query("DELETE FROM input WHERE userid = '$userid' AND id = '$inputid'");
460 | if ($this->redis) {
461 | $this->redis->del("input:$inputid");
462 | $this->redis->srem("user:inputs:$userid",$inputid);
463 | }
464 | }
465 | $deleted_inputs++;
466 | }
467 | $deleted_nodes++;
468 | }
469 | }
470 | if ($dryrun) return "DRYRUN: $deleted_nodes nodes to delete ($deleted_inputs inputs)";
471 | return "Deleted $deleted_nodes nodes ($deleted_inputs inputs)";
472 | }
473 |
474 | public function set_fields($id, $fields) {
475 | $id = intval($id);
476 | if (!$this->exist($id)) {
477 | if (!$this->redis || !$this->load_device_to_redis($id)) {
478 | return array('success'=>false, 'message'=>'Device does not exist');
479 | }
480 | }
481 | $success = true;
482 |
483 | $fields = json_decode(stripslashes($fields));
484 |
485 | if (isset($fields->name)) {
486 | if (preg_replace('/[^\p{N}\p{L}_\s\-:]/u','',$fields->name)!=$fields->name) return array('success'=>false, 'message'=>'invalid characters in device name');
487 | $stmt = $this->mysqli->prepare("UPDATE device SET name = ? WHERE id = ?");
488 | $stmt->bind_param("si",$fields->name,$id);
489 | if ($stmt->execute()) {
490 | $this->redis->hSet("device:".$id,"name",$fields->name);
491 | } else $success = false;
492 | $stmt->close();
493 | }
494 |
495 | if (isset($fields->description)) {
496 | if (preg_replace('/[^\p{N}\p{L}_\s\-:]/u','',$fields->description)!=$fields->description) return array('success'=>false, 'message'=>'invalid characters in device description');
497 | $stmt = $this->mysqli->prepare("UPDATE device SET description = ? WHERE id = ?");
498 | $stmt->bind_param("si",$fields->description,$id);
499 | if ($stmt->execute()) {
500 | $this->redis->hSet("device:".$id,"description",$fields->description);
501 | } else $success = false;
502 | $stmt->close();
503 | }
504 |
505 | if (isset($fields->nodeid)) {
506 | if (preg_replace('/[^\p{N}\p{L}_\s\-:]/u','',$fields->nodeid)!=$fields->nodeid) return array('success'=>false, 'message'=>'invalid characters in device nodeid');
507 | $stmt = $this->mysqli->prepare("UPDATE device SET nodeid = ? WHERE id = ?");
508 | $stmt->bind_param("si",$fields->nodeid,$id);
509 | if ($stmt->execute()) {
510 | $this->redis->hSet("device:".$id,"nodeid",$fields->nodeid);
511 | } else $success = false;
512 | $stmt->close();
513 | }
514 |
515 | if (isset($fields->type)) {
516 | if (preg_replace('/[^\/\|\,\w\s\-:]/','',$fields->type)!=$fields->type) return array('success'=>false, 'message'=>'invalid characters in device type');
517 | $stmt = $this->mysqli->prepare("UPDATE device SET type = ? WHERE id = ?");
518 | $stmt->bind_param("si",$fields->type,$id);
519 | if ($stmt->execute()) {
520 | $this->redis->hSet("device:".$id,"type",$fields->type);
521 | } else $success = false;
522 | $stmt->close();
523 | }
524 |
525 | if (isset($fields->devicekey)) {
526 | // 1. Only allow alphanumeric characters
527 | if (!ctype_alnum($fields->devicekey)) return array('success'=>false, 'message'=>'invalid characters in device key');
528 |
529 | // 2. Only allow 32 character length
530 | if (strlen($fields->devicekey)!=32) return array('success'=>false, 'message'=>'device key must be 32 characters long');
531 |
532 | $stmt = $this->mysqli->prepare("UPDATE device SET devicekey = ? WHERE id = ?");
533 | $stmt->bind_param("si",$fields->devicekey,$id);
534 | if ($stmt->execute()) {
535 | $this->redis->hSet("device:".$id,"devicekey",$fields->devicekey);
536 | } else $success = false;
537 | $stmt->close();
538 | }
539 |
540 | if ($success) {
541 | return array('success'=>true, 'message'=>'Field updated');
542 | } else {
543 | return array('success'=>false, 'message'=>'Field could not be updated');
544 | }
545 | }
546 |
547 | public function set_new_devicekey($id) {
548 | $id = intval($id);
549 | if (!$this->exist($id)) {
550 | if (!$this->redis || !$this->load_device_to_redis($id)) {
551 | return array('success'=>false, 'message'=>'Device does not exist');
552 | }
553 | }
554 |
555 | $devicekey = md5(uniqid(mt_rand(), true));
556 |
557 | $stmt = $this->mysqli->prepare("UPDATE device SET devicekey = ? WHERE id = ?");
558 | $stmt->bind_param("si",$devicekey,$id);
559 | $result = $stmt->execute();
560 | $stmt->close();
561 |
562 | if ($result) {
563 | $this->redis->hSet("device:".$id,"devicekey",$devicekey);
564 | return $devicekey;
565 | } else {
566 | return false;
567 | }
568 | }
569 |
570 | public function get_template_list() {
571 | return $this->load_template_list();
572 | }
573 |
574 | public function get_template_list_meta() {
575 | $templates = array();
576 |
577 | if ($this->redis) {
578 | if (!$this->redis->exists("device:templates:meta")) $this->load_template_list();
579 |
580 | $ids = $this->redis->sMembers("device:templates:meta");
581 | foreach ($ids as $id) {
582 | $template = $this->redis->hGetAll("device:template:$id");
583 | $template["control"] = (bool) $template["control"];
584 |
585 | $templates[$id] = $template;
586 | }
587 | }
588 | else {
589 | if (empty($this->templates)) { // Cache it now
590 | $this->load_template_list();
591 | }
592 | $templates = $this->templates;
593 | }
594 | ksort($templates);
595 | return $templates;
596 | }
597 |
598 | private function get_template_meta($id) {
599 | if ($this->redis) {
600 | if ($this->redis->exists("device:template:$id")) {
601 | $template = $this->redis->hGetAll("device:template:$id");
602 | $template["control"] = (bool) $template["control"];
603 |
604 | return $template;
605 | }
606 | }
607 | else {
608 | if (empty($this->templates)) { // Cache it now
609 | $this->load_template_list();
610 | }
611 | if(isset($this->templates[$id])) {
612 | return $this->templates[$id];
613 | }
614 | }
615 | return array('success'=>false, 'message'=>'Device template does not exist');
616 | }
617 |
618 | public function get_template($id) {
619 |
620 | $result = $this->get_template_meta($id);
621 | if (isset($result['success']) && $result['success'] == false) {
622 | return $result;
623 | }
624 | $module = $result['module'];
625 | $class = $this->get_module_class($module, self::TEMPLATE);
626 | if ($class != null) {
627 | return $class->get_template($id);
628 | }
629 | return array('success'=>false, 'message'=>'Device template class is not defined');
630 | }
631 |
632 | public function prepare_template($id) {
633 | $id = intval($id);
634 |
635 | $device = $this->get($id);
636 | if (isset($device['type']) && $device['type'] != 'null' && $device['type']) {
637 | $result = $this->get_template_meta($device['type']);
638 | if (isset($result["success"]) && $result["success"] == false) {
639 | return $result;
640 | }
641 | $module = $result['module'];
642 | $class = $this->get_module_class($module, self::TEMPLATE);
643 | if ($class != null) {
644 | return $class->prepare_template($device);
645 | }
646 | return array('success'=>false, 'message'=>'Device template class is not defined');
647 | }
648 | return array('success'=>false, 'message'=>'Device type not specified');
649 | }
650 |
651 | public function init($id, $template) {
652 | $id = intval($id);
653 |
654 | $device = $this->get($id);
655 | $result = $this->init_template($device, $template);
656 | if (isset($result['success']) && $result['success'] == false) {
657 | return $result;
658 | }
659 | return array('success'=>true, 'message'=>'Device initialized');
660 | }
661 |
662 | public function init_template($device, $template) {
663 | if (isset($template) && $template!==false) $template = json_decode($template);
664 |
665 | if (isset($device['type']) && $device['type'] != 'null' && $device['type']) {
666 | $result = $this->get_template_meta($device['type']);
667 | if (isset($result['success']) && $result['success'] == false) {
668 | return $result;
669 | }
670 | $module = $result['module'];
671 | $class = $this->get_module_class($module, self::TEMPLATE);
672 | if ($class != null) {
673 | return $class->init_template($device, $template);
674 | }
675 | return array('success'=>false, 'message'=>'Device template class is not defined');
676 | }
677 | return array('success'=>false, 'message'=>'Device type not specified');
678 | }
679 |
680 | public function reload_template_list() {
681 | $result = $this->load_template_list();
682 | if (isset($result['success']) && $result['success'] == false) {
683 | return $result;
684 | }
685 | return array('success'=>true, 'message'=>'Templates successfully reloaded');
686 | }
687 |
688 | private function load_template_list() {
689 |
690 | if ($this->redis) {
691 | foreach ($this->redis->sMembers("device:templates:meta") as $id) {
692 | $this->redis->del("device:template:$id");
693 | }
694 | $this->redis->del("device:templates:meta");
695 | }
696 | else {
697 | $this->templates = array();
698 | }
699 | $templates = array();
700 |
701 | $dir = scandir("Modules");
702 | for ($i=2; $iget_module_class($dir[$i], self::TEMPLATE);
705 | if ($class != null) {
706 | $result = $class->get_template_list();
707 | if (isset($result['success']) && $result['success'] == false) {
708 | return $result;
709 | }
710 | foreach($result as $key => $value) {
711 | $this->cache_template($dir[$i], $key, $value);
712 | $templates[$key] = $value;
713 | }
714 | }
715 | }
716 | }
717 |
718 | return $templates;
719 | }
720 |
721 | private function cache_template($module, $id, $template) {
722 | $meta = array(
723 | "module"=>$module
724 | );
725 | $meta["name"] = ((!isset($template->name) || $template->name == "" ) ? $id : $template->name);
726 | $meta["category"] = ((!isset($template->category) || $template->category== "" ) ? "General" : $template->category);
727 | $meta["group"] = ((!isset($template->group) || $template->group== "" ) ? "Miscellaneous" : $template->group);
728 | $meta["description"] = (!isset($template->description) ? "" : $template->description);
729 | $meta["control"] = (!isset($template->control) ? false : true);
730 |
731 | if ($this->redis) {
732 | $this->redis->sAdd("device:templates:meta", $id);
733 | $this->redis->hMSet("device:template:$id", $meta);
734 | }
735 | else {
736 | $this->templates[$id] = $meta;
737 | }
738 | }
739 |
740 | private function get_module_class($module, $type) {
741 | /*
742 | magic function __call (above) MUST BE USED with this.
743 | Load additional template module files.
744 | Looks in the folder Modules/modulename/ for a file modulename_template.php
745 | (module_name all lowercase but class ModulenameTemplate in php file that is CamelCase)
746 | */
747 | $module_file = "Modules/".$module."/".$module."_".$type.".php";
748 | $module_class = null;
749 | if(file_exists($module_file)){
750 | require_once($module_file);
751 |
752 | $module_class_name = ucfirst(strtolower($module)).ucfirst($type);
753 | $module_class = new $module_class_name($this);
754 | }
755 | return $module_class;
756 | }
757 | }
758 |
--------------------------------------------------------------------------------