342 Commits

Author SHA1 Message Date
Martin Preuss
1d9762c097 incremented version. 2026-04-14 23:55:19 +02:00
Martin Preuss
aed4e0800d avr: updated documentation. 2026-04-14 23:55:06 +02:00
Martin Preuss
4411162620 avr: begin preparations for using brightness module in device c03. 2026-04-14 23:54:41 +02:00
Martin Preuss
0df5c68fb3 avr: removed old lcd2/gui, replaced by lcd2/gui2. 2026-04-14 23:53:54 +02:00
Martin Preuss
7fb21a2f06 avr: fixed call for ATmega. 2026-04-14 23:50:51 +02:00
Martin Preuss
9d4e24222c avr: added device "minimal" (minimal example for a device using AqOS4Avr). 2026-04-14 23:50:29 +02:00
Martin Preuss
5ad304b988 avr: removed recv from led_simple (no need for it anymore). 2026-04-14 23:49:04 +02:00
Martin Preuss
8a9f74b0a1 avr: fixed includes.
for unknown reasons avra doesn't check #if statements correctly which
are meant to protect against double includes...
2026-04-14 23:47:21 +02:00
Martin Preuss
6b0ea55b3d avr: fixed includes. 2026-04-14 23:46:07 +02:00
Martin Preuss
ab7e28a9d5 avr: more code sharing. 2026-04-14 23:45:32 +02:00
Martin Preuss
d6e731b34e c03: removed old main firmware folder, make "test" folder new main. 2026-04-13 17:43:08 +02:00
Martin Preuss
a9dabf0eae gui2: fixed a bug. 2026-04-13 17:26:23 +02:00
Martin Preuss
830a25aff7 Revert "sensorwatch: added averaging."
This reverts commit f555bf7cd6.
2026-04-13 13:35:27 +02:00
Martin Preuss
ae2aef8743 added README. 2026-04-13 13:35:19 +02:00
Martin Preuss
f555bf7cd6 sensorwatch: added averaging. 2026-04-13 13:35:08 +02:00
Martin Preuss
7a58c4a8ad gui2: more code sharing. 2026-04-13 10:56:07 +02:00
Martin Preuss
0da8170df7 beeper_simple: remove msg receiption.
not needed for a beeper.
2026-04-13 00:22:29 +02:00
Martin Preuss
c4911a05cc minor formatting. 2026-04-13 00:20:47 +02:00
Martin Preuss
fa335392af avr: improved beeper code
dont start beeper already when setting timer because it might be quite some
time until the timer is called again which could make the beeper beep an
anoyingly long time.
2026-04-13 00:20:38 +02:00
Martin Preuss
f63eb308b3 avr: added a simple screen saver. 2026-04-12 23:04:25 +02:00
Martin Preuss
5fe4b4b705 gui2: renamed *layout2* to *layout* 2026-04-12 17:10:15 +02:00
Martin Preuss
98f1ab779d gui2: removed old layout modules. 2026-04-12 17:02:53 +02:00
Martin Preuss
710af679cb gui2: new layout modules basically work now. 2026-04-12 17:01:30 +02:00
Martin Preuss
505910bd12 replace MLayout with MCLayout. 2026-04-12 11:07:11 +02:00
Martin Preuss
42b7ce1814 gui2: implement OnGetDefaultWidth/Height 2026-04-12 11:03:10 +02:00
Martin Preuss
66be348ecd gui2: added mclayout (matrix layout ordering by column). 2026-04-12 01:31:23 +02:00
Martin Preuss
7d556c0f26 gui2: more work on preparing code sharing for all layout modules. 2026-04-10 00:07:53 +02:00
Martin Preuss
12cdc6c637 gui2: resize item if WIDGET_PACK_FILLED 2026-04-09 00:00:36 +02:00
Martin Preuss
b81d798008 gui2: more work on new layout module, started working on hlayout2. 2026-04-08 23:54:31 +02:00
Martin Preuss
2a5f09e239 gui2: added OBJ_CountDirectChildren 2026-04-08 23:54:05 +02:00
Martin Preuss
9da5f71ce1 gui2: added Widget_GetBorderAndSpacing 2026-04-08 23:53:51 +02:00
Martin Preuss
f0df0219a5 avr: fixed a call. 2026-04-08 23:53:33 +02:00
Martin Preuss
87c2314c2d avr: started working on another iteration of the layout modules.
This will become a base module for other layout modules (like hlayout,
vlayout and mlayout). All those modules will share the playment code.
2026-04-07 23:57:28 +02:00
Martin Preuss
5a9bff2d0b c03: more work on climate and netstats windows. 2026-04-07 23:56:14 +02:00
Martin Preuss
d7074388e6 avr: added netstats window
There seems to be a problem with MLayout (writes imaginary row at
screen Y position 0 for unknown reasons).
2026-04-06 19:02:59 +02:00
Martin Preuss
b00f697582 avr: added MainWindow_GetFirstChildOfContentWidget 2026-04-06 19:01:32 +02:00
Martin Preuss
e5dafccb29 avr: added ValueLabel_SetValue 2026-04-06 19:01:17 +02:00
Martin Preuss
0e501274eb c03: removed test code. 2026-04-06 01:56:54 +02:00
Martin Preuss
5c2f7496c3 c03: removed main menu again (use full window for that). 2026-04-06 01:55:09 +02:00
Martin Preuss
1540f62d04 c03: added main menu. 2026-04-06 01:54:24 +02:00
Martin Preuss
cc4696d408 c03: use RESSSOURCE_TXT_AQHOME 2026-04-06 01:53:18 +02:00
Martin Preuss
6176908c54 c03: use RESSSOURCE_IMG_ARROWBACK 2026-04-06 01:53:01 +02:00
Martin Preuss
6ad7bae155 c03: added ressource RESSSOURCE_TXT_AQHOME 2026-04-06 01:52:44 +02:00
Martin Preuss
487cd13297 c03, gui2: new GUI now basically works. 2026-04-06 01:45:56 +02:00
Martin Preuss
69daa1465b another name change. 2026-03-30 21:53:26 +02:00
Martin Preuss
509ec83661 test commit. 2026-03-30 21:52:17 +02:00
Martin Preuss
95197429ab fixed device description. 2026-03-30 00:19:22 +02:00
Martin Preuss
f7a66d6d81 n30: for code size reduction we don't use stats app. But we need to send DEVICE msg. 2026-03-30 00:19:05 +02:00
Martin Preuss
47894e6d89 remove unneeded files. 2026-03-30 00:18:31 +02:00
Martin Preuss
4b7c33f43e changed repository name. 2026-03-28 21:55:43 +01:00
Martin Preuss
612013f400 some docu work. 2026-03-28 21:43:38 +01:00
Martin Preuss
ca1c5c0cb9 remove unneeded file. 2026-03-28 21:43:28 +01:00
Martin Preuss
95e4de1f33 remove old files. 2026-03-28 21:40:51 +01:00
Martin Preuss
fc020088f4 fixed formatting problems. 2026-03-28 18:09:22 +01:00
Martin Preuss
f567630aaa fixed formatting errors. 2026-03-28 18:07:47 +01:00
Martin Preuss
28f64a68e6 add links to example nodes. 2026-03-28 18:06:10 +01:00
Martin Preuss
bc79d72077 integrate images into README.md 2026-03-28 17:58:01 +01:00
Martin Preuss
ee74f59563 doc: added images to README.md 2026-03-28 17:48:32 +01:00
Martin Preuss
66f061be80 doc: added descriptions and images for current nodes n29-n30. 2026-03-28 17:44:04 +01:00
Martin Preuss
82d877d5e3 fixed READMEs. 2026-03-28 17:43:33 +01:00
Martin Preuss
59d1ef7666 added warning for when firmware gets to big thus overwriting bootloader. 2026-03-28 17:43:08 +01:00
Martin Preuss
6ea1e97835 n30: fixed LED position. 2026-03-28 02:11:33 +01:00
Martin Preuss
a7ddcd2c81 c03/test: create climate window again. 2026-03-26 21:08:20 +01:00
Martin Preuss
aba71f73e0 n30: added a warning message if to much code. 2026-03-26 21:07:49 +01:00
Martin Preuss
66bc71b2bd sk6812: minor reorganizing. works now with newer BTF LED stripes. 2026-03-26 21:07:22 +01:00
Martin Preuss
02a43398a9 added VALUE_ID_LED_TYPE 2026-03-26 21:06:30 +01:00
Martin Preuss
915e525daf avr: fixed includes for sk6812 module. 2026-03-26 21:05:43 +01:00
Martin Preuss
34b6bab711 avr: added README 2026-03-26 21:05:06 +01:00
Martin Preuss
c2dcd94c14 fixed a typo. 2026-03-26 21:04:53 +01:00
Martin Preuss
bd2d5afcb5 incremented version. 2026-03-25 20:46:51 +01:00
Martin Preuss
32a2193074 n30: add value "RGBWTYPE" 2026-03-25 20:46:43 +01:00
Martin Preuss
f1d67027fa sk6812: added SetValueType. 2026-03-25 20:46:15 +01:00
Martin Preuss
0c810a7842 sk6812: read and write led strip type. 2026-03-25 20:45:50 +01:00
Martin Preuss
946fdfe39e n30: decrease ridge height. 2026-03-25 20:45:05 +01:00
Martin Preuss
ab9c377a25 fixed a typo. 2026-03-25 20:44:48 +01:00
Martin Preuss
a0a28aec45 apps_motion: report motion sensor change also via LED_Simple module. 2026-03-24 23:16:10 +01:00
Martin Preuss
d2cbca9d5a use a default repeat value (setValue will be removed from LED_Simple module). 2026-03-24 23:15:16 +01:00
Martin Preuss
67be74d2ac avr: move timing control from ma_light to sk6812.
This allows for better control over the LED strip. We can now trigger the
LED strip externally (e.g. by setting a new RGBW value).
2026-03-24 23:13:28 +01:00
Martin Preuss
42874f27cd aqhome-tool: fixed a bug. 2026-03-24 23:11:00 +01:00
Martin Preuss
c988876c79 network: only call led code if module activated. 2026-03-24 18:25:08 +01:00
Martin Preuss
c6510f10bf network: let LED temporarily enter ID mode when receiving PING.
this can be used to identify nodes on the network.
2026-03-24 18:19:12 +01:00
Martin Preuss
e23bfde746 led_simple: improved module, added repeats, id mode and activity mode.
modes:
- heartbeat mode (as before)
- id mode (slowly blink for 30s after receiving PING)
- activity mode (short blink to signal e.g. network activity)
The latter two modes fallback to heartbeat mode after some time.
2026-03-24 18:18:02 +01:00
Martin Preuss
07a6c5a540 gui2: finalized mlayout module. 2026-03-23 23:20:13 +01:00
Martin Preuss
0015a0a06c gui2: mlayout works now (in column mode)! 2026-03-23 23:15:38 +01:00
Martin Preuss
e125c4b027 test WIDGET_PACK_FILLED with MLAYOUT. 2026-03-23 22:20:42 +01:00
Martin Preuss
8eed88394f gui2: X-layout works now! 2026-03-23 22:20:11 +01:00
Martin Preuss
b9e40f236e gui2: bug hunt... 2026-03-23 00:22:45 +01:00
Martin Preuss
68624e92e0 added tests for gui2. 2026-03-23 00:22:21 +01:00
Martin Preuss
41d60f4331 added DEBUG4. 2026-03-23 00:22:08 +01:00
Martin Preuss
a727d600f1 label: enabled border drawing. 2026-03-23 00:21:25 +01:00
Martin Preuss
e7c195e635 gui2: clear CF on error in input. 2026-03-23 00:21:05 +01:00
Martin Preuss
30be5b3681 gui2: fixed an important bug (was decrementing r25!) 2026-03-23 00:20:36 +01:00
Martin Preuss
bd5a51b4d2 mlayout: works for first row now (still WIP!) 2026-03-21 13:09:06 +01:00
Martin Preuss
1a442cfb21 modified comment. 2026-03-21 08:29:31 +01:00
Martin Preuss
4eac2a105e gui2: added OBJ_SkipObjects 2026-03-21 08:29:18 +01:00
Martin Preuss
69a16c4c0f gui2: use bigcall (code becomes too large for rjmp/rcall). 2026-03-21 08:29:06 +01:00
Martin Preuss
5e3a8f444a gui2: 2nd try for a matrix layout. 2026-03-21 08:28:23 +01:00
Martin Preuss
722528f4e1 gui2: more work on mlayout (first try) 2026-03-21 08:27:52 +01:00
Martin Preuss
2878d3aaa2 sk6812: prepare use of multiple timing types.
timing is very tight so we need an extra routine per timing type for
sending a byte to the led strip.
2026-03-21 00:40:03 +01:00
Martin Preuss
c5df904fc4 n30: decreased size of cable hole again. 2026-03-21 00:07:09 +01:00
Martin Preuss
6bb26b7c90 n30: removed mentioning of old module. 2026-03-20 22:31:47 +01:00
Martin Preuss
a0e73d5788 sk6812: fixed timing issues (works again with BTF-Lighting LED strips). 2026-03-20 22:30:41 +01:00
Martin Preuss
d877508e85 avr: share code (use Main_HandleValueMsg) 2026-03-20 22:28:58 +01:00
Martin Preuss
c2dc819aec avr: fixed comments. 2026-03-20 22:28:09 +01:00
Martin Preuss
1f801c41a1 gui2: started working on matrix layout
this will later become also the base class for new implementations of
HLayout and VLayout to simplify things.
2026-03-18 01:02:11 +01:00
Martin Preuss
21b8c1fe56 minor formatting 2026-03-18 01:01:27 +01:00
Martin Preuss
440f3bcf0e c01: switched COM from old UART module to com2w 2026-03-17 00:02:13 +01:00
Martin Preuss
3bf327bb2d avr: fixed calls for 64K ATmega. 2026-03-17 00:01:23 +01:00
Martin Preuss
8085043ef6 extended memstats message and handling. 2026-03-17 00:00:38 +01:00
Martin Preuss
e6aeaabb32 e03: added network stats screen which is updated every minute. 2026-03-16 22:44:48 +01:00
Martin Preuss
70ace036a8 e02: use LED in simple blink mode. 2026-03-16 22:44:12 +01:00
Martin Preuss
1112a1bf6f avr: remove debug code (need the space) 2026-03-16 22:43:57 +01:00
Martin Preuss
f5c27d7174 lcd: remove debug code. 2026-03-16 22:43:32 +01:00
Martin Preuss
52b400d9f7 avr: adapted to latest changes. 2026-03-16 22:42:47 +01:00
Martin Preuss
fd6616c3c4 n29: minor cleanup. 2026-03-16 21:38:18 +01:00
Martin Preuss
ab77a71216 avr: added docu 2026-03-16 21:38:01 +01:00
Martin Preuss
5c18f5bf9a avr: cleanup handling of apps and modules in devices/all 2026-03-16 21:37:46 +01:00
Martin Preuss
dbb004234f increased version. 2026-03-16 18:05:13 +01:00
Martin Preuss
646c28e00a added device description files for e02 and e03. 2026-03-16 18:05:00 +01:00
Martin Preuss
47f8ef2a62 avr: started using 0.96" display on e03 2026-03-16 18:04:38 +01:00
Martin Preuss
1d39c3c773 fixed a bug (dayOfWeek starts with 0 in GWEN_TIMESTAMP, 1 in DS3231) 2026-03-16 18:04:00 +01:00
Martin Preuss
d688cd3d26 work on documentation. 2026-03-16 18:03:13 +01:00
Martin Preuss
1813c9e09e n30: decrease size of cable hole 2026-03-15 20:27:03 +01:00
Martin Preuss
274c404732 c03: remove border, use new ressource RESSSOURCE_TXT_ROOMCLIMATE 2026-03-15 20:26:43 +01:00
Martin Preuss
13a3c3a189 c03: added ressource RESSSOURCE_TXT_ROOMCLIMATE 2026-03-15 20:26:12 +01:00
Martin Preuss
8671214a2d avr: fixed apidoc 2026-03-15 20:25:50 +01:00
Martin Preuss
4140e2dc52 fixed typo 2026-03-15 20:25:35 +01:00
Martin Preuss
6a3c8b6cfa flashnode.sh: added devices e02 and e03 2026-03-15 20:25:05 +01:00
Martin Preuss
6d3a27977a avr: added time message 2026-03-15 20:24:48 +01:00
Martin Preuss
6c83991df7 avr: added NETMSG_Debug_Write2 (sends bytes from a buffer) 2026-03-15 20:23:49 +01:00
Martin Preuss
1d8b467b1d avr: added devices e02 and e03 2026-03-15 20:23:18 +01:00
Martin Preuss
a0fc10cccd aqhome-tool: added command "time" to set node network time. 2026-03-15 20:22:36 +01:00
Martin Preuss
2a76e82923 avr: activate module ds3231 2026-03-15 20:21:57 +01:00
Martin Preuss
b6f710bd8b added node message type TIME. 2026-03-15 20:21:30 +01:00
Martin Preuss
5119c38ca6 avr: added divide8 2026-03-15 20:20:33 +01:00
Martin Preuss
393df322f0 avr: added rtc module for ds3231 2026-03-15 20:20:06 +01:00
Martin Preuss
ce4a4afc68 minor formatting. 2026-03-12 16:28:43 +01:00
Martin Preuss
9d92564b2e gui2: fixed an important bug. 2026-03-12 16:28:28 +01:00
Martin Preuss
2f570ea110 gui2: more work. SensorWatch doesn't fully work, yet (also red background). 2026-03-12 00:37:44 +01:00
Martin Preuss
092c667291 gui2: fixed more bugs in layout code. Now basically works! 2026-03-11 19:53:19 +01:00
Martin Preuss
f6736c345d n30: decreases ridge height, increased height of power connection (open upper border) 2026-03-11 16:05:30 +01:00
Martin Preuss
51cedae0cb gui2: fixed problems with layouts
need to look into spread mode, too.
2026-03-11 00:32:08 +01:00
Martin Preuss
282cd738b4 gui2: started working on SensorWatch for GUI2. Works so far. 2026-03-10 00:15:52 +01:00
Martin Preuss
d3f24284ca gui2: added widgets. 2026-03-09 21:06:16 +01:00
Martin Preuss
2827f4e063 gui2: added generic button class
this gives child elements a button behavior.
2026-03-09 18:20:51 +01:00
Martin Preuss
f5e19ac0a1 gui2: removed debug code. 2026-03-09 14:03:40 +01:00
Martin Preuss
cc7f2dc1db gui2: main windows basically work now! 2026-03-09 13:56:51 +01:00
Martin Preuss
e3ae1f9b35 gui2: fixed a bug. 2026-03-09 09:14:51 +01:00
Martin Preuss
e4668378fe gui2: increase borders of label widget. 2026-03-09 09:14:39 +01:00
Martin Preuss
e24300f16b gui2: fixed a bug. 2026-03-09 09:09:03 +01:00
Martin Preuss
648b0f33c5 gui2: fixed a bug, added debug code. 2026-03-09 02:28:33 +01:00
Martin Preuss
0758579b43 gui2: more work (vlayout doesn't work, yet). 2026-03-09 02:08:33 +01:00
Martin Preuss
92efebccf1 gui2: basic start works! 2026-03-07 15:29:28 +01:00
Martin Preuss
7bd83b32b4 gui2: more code sharing. 2026-03-07 14:03:58 +01:00
Martin Preuss
3f3cdaac73 gui2: more work on gui. 2026-03-07 13:47:05 +01:00
Martin Preuss
ef3ed9df12 gui2: create root window 2026-03-07 01:51:40 +01:00
Martin Preuss
ffde6b0ddb gui2: added MainWindow_GetContentWidget. 2026-03-07 01:29:13 +01:00
Martin Preuss
f88745c3fd gui2: added default Layout handler to widget class. 2026-03-07 01:17:38 +01:00
Martin Preuss
283738f4f3 gui2: more work on layouting. 2026-03-07 01:13:02 +01:00
Martin Preuss
2cf4e414d2 gui2: more work on layout code. 2026-03-07 00:44:56 +01:00
Martin Preuss
0af5aed2f6 gui2: started working on autolayout. 2026-03-07 00:04:36 +01:00
Martin Preuss
a8cb442502 gui2: improved signal handling, make use of secondary signal maps. 2026-03-06 16:00:14 +01:00
Martin Preuss
16b820fae1 n30: adjusted enclosure
- decreased length, adjusted pos of LED hole
- decreased ridge_height
- increased power connection height
- decreased size of cable hole
2026-03-06 00:27:35 +01:00
Martin Preuss
261ddba7c4 gui2: added RootWindow_SetApp 2026-03-04 23:35:18 +01:00
Martin Preuss
ae137efb26 gui2: started working on GuiApp, MainWindow, RootWindow 2026-03-04 23:26:37 +01:00
Martin Preuss
afdd52eaf6 gui2: reorganized init/alloc code, added Widget_Resize, Widget_Move 2026-03-04 23:26:05 +01:00
Martin Preuss
0081c33c00 gui2: added Object_Alloc, OBJ_GetRoot 2026-03-04 23:25:12 +01:00
Martin Preuss
8c59b92100 minor formatting. 2026-03-04 23:24:24 +01:00
Martin Preuss
e6e1bf0535 minor formatting. 2026-03-04 00:08:35 +01:00
Martin Preuss
fe8681292f added Widget_OnDraw and Widget_OnDrawNop 2026-03-04 00:08:10 +01:00
Martin Preuss
1edd34fb5a added OBJ_GetChildAt 2026-03-04 00:07:48 +01:00
Martin Preuss
f496e6587b started working on C03 test firmware to test new gui2 module. 2026-03-03 23:52:25 +01:00
Martin Preuss
0d6bbd1147 gui2: started working on a SDRAM based GUI implementation. 2026-03-03 23:51:52 +01:00
Martin Preuss
639136431b incremented version number. 2026-03-03 23:50:35 +01:00
Martin Preuss
5692e65627 removed old nodes from flasher tool. 2026-03-01 21:51:50 +01:00
Martin Preuss
baadca7c95 added missing files. 2026-03-01 21:48:18 +01:00
Martin Preuss
43d03b0ace c03: added local TODO file. 2026-03-01 21:48:04 +01:00
Martin Preuss
d8558015b2 removed old code.
aqhome-storage has long been incorporated into aqhome-data.
2026-03-01 21:47:44 +01:00
Martin Preuss
1be66e6c7a aqhome-cgi: fixed a bug. 2026-03-01 21:46:38 +01:00
Martin Preuss
8597f89227 aqhome-cgi: added missing files. 2026-03-01 21:46:23 +01:00
Martin Preuss
d2533f66af n29: add enclosures to build files, add type-2 enclosure for n29
type-2 enclosure is without holes for motion detector and photo led.
2026-03-01 20:48:50 +01:00
Martin Preuss
0b71b9dff7 add enclosure.scad to build file. 2026-03-01 20:47:41 +01:00
Martin Preuss
717c9467a8 uncomment value. 2026-03-01 01:09:29 +01:00
Martin Preuss
b02ae49484 added .gitignore 2026-03-01 01:09:17 +01:00
Martin Preuss
69420d9c7d added device n30. 2026-03-01 01:09:04 +01:00
Martin Preuss
bdd2d3bdc6 added doc folder with first example circuit. 2026-03-01 01:06:20 +01:00
Martin Preuss
d6e87aeba5 eventloop_select: introduce error variable
abort process if too many select errors. Prevents error logs from
filling log partition.

after 90 select errors the log level will be increased for the next calls.
after 100 select errors the process aborts (will be restarted by systemd).

TODO: inspect sockets to find the bad one.
2026-02-26 23:18:55 +01:00
Martin Preuss
56f2cf6870 minor changes on enclosure for N29 devices.
- decreased height of holding ridge (was a bit tight)
- moved drilling holes in lid (the lower one was conflicting with the
  network connectors)
2026-02-26 17:35:15 +01:00
Martin Preuss
bf8f584a4a add support for n29. 2026-02-16 01:14:50 +01:00
Martin Preuss
b1efa104f0 add missing parts for e01. 2026-02-16 01:14:39 +01:00
Martin Preuss
5fc3916457 avr: started work on com2w_router for S03. 2026-02-16 01:12:25 +01:00
Martin Preuss
1b0519437c increased version. 2026-02-16 01:11:55 +01:00
Martin Preuss
9e5fd0bf8c add support for e01. 2026-02-16 01:11:40 +01:00
Martin Preuss
e117153622 add device file for n29. 2026-02-16 01:11:27 +01:00
Martin Preuss
00bea4d9f6 s03: use 16MHz clock (should work with 3V3). 2026-02-16 01:10:49 +01:00
Martin Preuss
f7475b0b31 avr: added support for 16MHz clock. 2026-02-16 01:10:15 +01:00
Martin Preuss
7c37ad930b avr: updated string. 2026-02-16 01:09:50 +01:00
Martin Preuss
bfd991a768 avr: removed old GUI code, renamed gui2 to gui 2026-02-16 01:09:39 +01:00
Martin Preuss
1e8c231707 avr: remove c02, add e01 and n29 2026-02-16 01:07:09 +01:00
Martin Preuss
aef7434f9d avr: minor fixes in screensaver code. 2026-02-09 19:07:56 +01:00
Martin Preuss
b797c09f67 avr: improved apidoc 2026-02-09 19:07:34 +01:00
Martin Preuss
4fec5433b3 avr: minor changes
- save Y in GuiApp_ShowView
- fix apidoc
- change order in guiAppShowCurrent
2026-02-09 19:07:05 +01:00
Martin Preuss
05ac62ef54 formatting. 2026-02-09 19:05:50 +01:00
Martin Preuss
409205dcd7 avr: switch to iterative code for Widget_TreeMatchFlagsAndSendSignalToActive 2026-02-09 00:35:42 +01:00
Martin Preuss
599b27c7d2 avr: fixed window redrawing for main views. 2026-02-08 00:33:08 +01:00
Martin Preuss
1dd9c8de6e avr: fixed apidoc. 2026-02-07 16:19:21 +01:00
Martin Preuss
36f52a396b avr: set screen saver timeout to 10 mins. 2026-02-07 16:18:59 +01:00
Martin Preuss
28deb9c591 avr/gui2: added screen saver app
screen saver can be turned off by:
- touching and releasing the display
- specific messages (e.g. motion detection msg from other nodes)
2026-02-07 16:09:31 +01:00
Martin Preuss
39d1060334 n28 enclosure: decreased height by 1mm, increased ridge depth and height 2026-02-06 17:51:50 +01:00
Martin Preuss
38eb21869e n28: added info. 2026-02-02 21:21:05 +01:00
Martin Preuss
a8cdf6ec58 c03: use beeper module. 2026-02-02 21:20:47 +01:00
Martin Preuss
40033db235 added simple beeper module. 2026-02-02 21:20:25 +01:00
Martin Preuss
c8e5317cac aqhome-tool: fixed a bug. 2026-02-02 21:19:53 +01:00
Martin Preuss
74b4098608 ili9341: tested backlight dimming (doesn't work with my displays, either)
probably the LED pins are hardcoded with my displays. They respond neither
to PWM on the LED pin nor to ili9341 commands.
2026-01-27 20:32:38 +01:00
Martin Preuss
7411fd2a13 ili9341: added backlight handling via PWM (doesn't work with my displays).
my displays don't accept PWM...
2026-01-27 20:31:00 +01:00
Martin Preuss
4b4d44d80f com2w: don't use activity LED for now. 2026-01-27 20:29:53 +01:00
Martin Preuss
0fa44cfa4b n26: fixed pinout. 2026-01-27 20:29:26 +01:00
Martin Preuss
653e63ad34 n16: use activity LED. 2026-01-27 20:29:10 +01:00
Martin Preuss
a7cf31b1c1 minor reformatting. 2026-01-27 20:28:39 +01:00
Martin Preuss
ce240f087e c03: changed default colors for images. 2026-01-27 20:28:22 +01:00
Martin Preuss
3b5d95f8b9 c03: fixed a typo. 2026-01-27 20:27:56 +01:00
Martin Preuss
c1da37d973 c03: define backlight pin for display. 2026-01-27 20:27:43 +01:00
Martin Preuss
eba269f2e7 avr: use activity LED for door and motion sensors. 2026-01-27 20:24:44 +01:00
Martin Preuss
a4b49f8979 n28: increase hook tolerance, add ridges for fixing pcb. 2026-01-27 20:24:11 +01:00
Martin Preuss
0df67a3e0b avr: remove init code (sensorWatch now reads from eeprom). 2026-01-24 01:03:52 +01:00
Martin Preuss
fdfc040d19 avr: use macros instead of costly calls for SPI on ILI9341. 2026-01-24 01:03:31 +01:00
Martin Preuss
2085ba6bbe avr: fixed a bug. 2026-01-24 01:02:33 +01:00
Martin Preuss
9a1ca45329 avr: use RESSSOURCE_IMG_DEBUGEEPROM 2026-01-23 22:23:50 +01:00
Martin Preuss
026e9179e7 avr: fixed a bug. 2026-01-23 22:23:32 +01:00
Martin Preuss
c59cdb1470 avr: converted 48x48 icons to RLE. Added RESSSOURCE_IMG_DEBUGEEPROM 2026-01-23 22:23:17 +01:00
Martin Preuss
74f4e32767 avr: fixed two bugs. 2026-01-23 21:42:27 +01:00
Martin Preuss
b46c65837f avr: added reset button. 2026-01-23 21:42:06 +01:00
Martin Preuss
8007c8c79a avr: shortened update interval (maybe not necessary?) 2026-01-23 21:41:43 +01:00
Martin Preuss
eb30e59c13 avr: added ressource RESSSOURCE_IMG_RESET 2026-01-23 21:41:10 +01:00
Martin Preuss
2636fa8b21 fixed apidoc. 2026-01-23 21:40:42 +01:00
Martin Preuss
50eed94821 avr: added local TODO 2026-01-23 20:49:05 +01:00
Martin Preuss
b95c09ad82 avr: make climate page the main page, added eeprom browser. 2026-01-23 20:48:48 +01:00
Martin Preuss
b4969019b2 avr: added images to ressources. 2026-01-23 20:48:06 +01:00
Martin Preuss
d963458a91 avr: fixed handler for font 6x8 2026-01-23 20:47:39 +01:00
Martin Preuss
664b8a1ea5 avr: removed needed instruction. 2026-01-23 20:47:15 +01:00
Martin Preuss
be39ad0365 avr: added Widget_DrawCharAt 2026-01-23 20:46:58 +01:00
Martin Preuss
41a70a8280 avr: added Button_DefaultSignalmap 2026-01-23 20:46:32 +01:00
Martin Preuss
e4e7aeb12e avr: added HexByteToAscii, HexWordToAscii 2026-01-23 20:46:08 +01:00
Martin Preuss
604734b6e3 avr: minor changes. 2026-01-22 00:06:09 +01:00
Martin Preuss
d64157f7ef c03: implemented setdata message handling for SensorWatch
storing/restoring data from EEPROM doesn't work, yet.
2026-01-21 22:48:44 +01:00
Martin Preuss
e5a999e9b6 c03: define TLV ids for sensorWatch 2026-01-21 22:47:40 +01:00
Martin Preuss
7a4f462fa2 added to apidoc. 2026-01-21 22:47:20 +01:00
Martin Preuss
a22d774b28 aqhome-tool: setdata can now use ":" to separate bytes/words 2026-01-21 22:47:03 +01:00
Martin Preuss
faa5991024 avr: work on eeprom TLV code. 2026-01-21 22:46:18 +01:00
Martin Preuss
dbe42c5bcb sensorwatch: introduce upper and lower limits. 2026-01-21 00:56:49 +01:00
Martin Preuss
864f815d91 updated TODO 2026-01-20 00:15:21 +01:00
Martin Preuss
94092a82d9 updated TODO. 2026-01-20 00:03:30 +01:00
Martin Preuss
242c3061bf c03: removed outdated files. 2026-01-19 23:25:59 +01:00
Martin Preuss
3225350be5 c03: added network stats window, fixed some bugs. 2026-01-19 23:24:58 +01:00
Martin Preuss
041f0fad6b avr: cleanup structure of graphics apps and gui2. 2026-01-19 21:10:51 +01:00
Martin Preuss
504e7568d4 gui2: added default signal maps, create first app for c03 graphics interface. 2026-01-19 20:51:46 +01:00
Martin Preuss
0bcb3b40f7 n28: enclosure with bigger slits for closing hooks. 2026-01-19 19:10:58 +01:00
Martin Preuss
519751043d ImageView: preset correct background color. 2026-01-19 18:44:00 +01:00
Martin Preuss
7b4bac534f gui2: removed debug widgets and functions (buttons work now). 2026-01-19 18:43:40 +01:00
Martin Preuss
79dde7402f updated TODO file. 2026-01-19 18:35:45 +01:00
Martin Preuss
b1a002013a Object: ignore selector when "0" in signal map. 2026-01-19 18:35:28 +01:00
Martin Preuss
ebb20150ca ImageView: handle PRESSED flag.
needed when used as button.
2026-01-19 18:34:56 +01:00
Martin Preuss
edc291261f gui2: added Button, RootWindow. Touch now works again. 2026-01-19 18:33:36 +01:00
Martin Preuss
715c4f22c4 updated TODO. 2026-01-19 00:50:01 +01:00
Martin Preuss
7fce521671 removed implemented features from TODO 2026-01-19 00:47:49 +01:00
Martin Preuss
065d301292 avr: more work on relative coords->absolute coords 2026-01-19 00:47:32 +01:00
Martin Preuss
c578ddd1a6 avr: added texts to ressources. 2026-01-19 00:47:01 +01:00
Martin Preuss
13f682b365 avr: added sensor watches for Temp and hunidity. 2026-01-18 20:31:17 +01:00
Martin Preuss
ff5c49ad7a add entry to todo. 2026-01-18 20:02:37 +01:00
Martin Preuss
a11c5ac536 avr: added text aligning in labels. 2026-01-18 20:02:25 +01:00
Martin Preuss
fb308ba9fc minor changes. 2026-01-18 14:28:16 +01:00
Martin Preuss
b060610f3f added TODO entries. 2026-01-18 14:28:07 +01:00
Martin Preuss
34176ea2ed c03: use complete SensorWatch for CO2. 2026-01-18 14:27:51 +01:00
Martin Preuss
46afcb3ed1 avr: completed SensorWatch (now also prints value) 2026-01-18 14:27:29 +01:00
Martin Preuss
64bc199753 avr: added ValueLabel. 2026-01-18 14:27:01 +01:00
Martin Preuss
73f9273014 avr: added routines to write text from SDRAM 2026-01-18 14:26:46 +01:00
Martin Preuss
b1693168c4 fixed apidoc. 2026-01-18 14:26:11 +01:00
Martin Preuss
276fd28782 use register r25 instead of r26 (r26 is XL!) 2026-01-18 14:25:48 +01:00
Martin Preuss
e9f1aec122 avr: added itoa 2026-01-18 14:25:21 +01:00
Martin Preuss
31cce1a609 remove implemented stuff from TODO. 2026-01-18 01:00:31 +01:00
Martin Preuss
c2d1234e00 avr: most stuff for SensorWatch implemented. 2026-01-18 00:49:08 +01:00
Martin Preuss
0443032de7 avr: added widget SensorWatch
shows a pictogram for a sensor and changes background color according to
values received from a given sensor.
2026-01-18 00:36:22 +01:00
Martin Preuss
3009e9d0d0 TODO: RLE implemented. 2026-01-17 17:16:40 +01:00
Martin Preuss
a52d7dd783 avr: added Widget_OnDrawNop 2026-01-17 17:15:48 +01:00
Martin Preuss
c2489e1866 avr: more work on gui2
- decreased complexitiy by removing guicntl
- changed fonts to allow for storing bitmaps in ressource segment
- add fonts to ressources for display node c03
- added some safety features (check pointers, add magic field to objects etc)
- moved style.asm to device folder
2026-01-17 15:23:17 +01:00
Martin Preuss
20189eb1eb added local TODO 2026-01-16 16:45:31 +01:00
Martin Preuss
48d0a3f41f added README 2026-01-16 16:45:04 +01:00
Martin Preuss
21bb33f148 fixed typos. 2026-01-16 16:44:46 +01:00
Martin Preuss
0d0b1d2fc6 write images in IDX2RLE format. 2026-01-16 16:43:14 +01:00
Martin Preuss
283374b5cd avr: add support for run-length-encoded images, converted existing images
saves much ressource space especially with larger images!
2026-01-16 16:42:49 +01:00
Martin Preuss
0aa4e6846a add comment. 2026-01-16 16:41:38 +01:00
Martin Preuss
99739325df avr: take all parents pos into account when calculating abs pos
allows for positions relative to parent in widget tables.
2026-01-15 17:27:56 +01:00
Martin Preuss
55292bddf1 avr: simplify handling of object tree
- set CFLAG if pointer valid, cleared otherwise
- check for inbound NULL pointer
2026-01-15 17:26:54 +01:00
Martin Preuss
37689fbc1d gui2: more work, added image viewer. 2026-01-14 22:57:14 +01:00
Martin Preuss
99c58d13e2 add ressources, use signals for GuiApp.
makes it easier to extend.
2026-01-14 21:19:46 +01:00
Martin Preuss
10c3f3c40d more work on images on ili9341
- reading images from predefined ressources (stored at 0x8000+) works
- drawing IDX2 images within widgets works
- added some image ressources in IDX2 format
2026-01-14 00:13:02 +01:00
Martin Preuss
edb8e7b859 texts inside labels now work. 2026-01-13 00:00:19 +01:00
Martin Preuss
caa2a92722 another GUI approach, this time more generic. 2026-01-12 22:47:08 +01:00
Martin Preuss
155b9c6f52 use markdown from gitea. 2025-12-26 23:57:54 +01:00
Martin Preuss
6468ee18b9 added license. 2025-12-26 23:57:37 +01:00
Martin Preuss
5374b6f295 added text to README. 2025-12-24 22:16:45 +01:00
Martin Preuss
00c607a65b sk6812: no longer set state when setting color. 2025-12-24 22:14:57 +01:00
Martin Preuss
c3e4435b90 n28: more work on enclosure. 2025-12-24 22:14:40 +01:00
Martin Preuss
8d65dd2182 added README. 2025-12-24 22:14:11 +01:00
Martin Preuss
bf1a34b449 n28: minor redesigning of the enclosure 2025-12-20 14:30:51 +01:00
Martin Preuss
3038208eab n28: decrease enclosure width 2025-12-20 13:08:08 +01:00
Martin Preuss
636fc026aa more work on pages and ressources. 2025-12-15 21:12:51 +01:00
Martin Preuss
06f006ee42 n28: added lid to enclosure. 2025-12-15 16:21:44 +01:00
Martin Preuss
f9a6e4b1d7 n28: cut out upper bar from network connector holes 2025-12-12 00:04:38 +01:00
Martin Preuss
9e8a7c338d n28: more ventilation slits, decrease height, remove posts. 2025-12-11 23:54:14 +01:00
Martin Preuss
202439b8b6 n28: make ventilation slits bigger. 2025-12-11 23:01:02 +01:00
Martin Preuss
077e5cbedb added first enclosure for n28. 2025-12-11 22:48:56 +01:00
Martin Preuss
3083b433a3 aqhome-tool: started working on code to convert BMP file to assembler data. 2025-11-22 14:10:42 +01:00
Martin Preuss
dbba5617c7 avr: add touch handler routine to button module. 2025-11-22 14:10:06 +01:00
Martin Preuss
d8058dc711 avr: added 12x16 font, remove unneeded chars from fonts. 2025-11-18 23:03:00 +01:00
Martin Preuss
db6d28b869 control layout of windows via style.asm 2025-11-18 23:02:07 +01:00
Martin Preuss
b520ccb165 avr: fixed button code. 2025-11-18 19:14:40 +01:00
Martin Preuss
059654b16f smaller button routines. 2025-11-17 23:45:10 +01:00
Martin Preuss
5dcb48d671 incremented version. 2025-11-17 23:04:08 +01:00
Martin Preuss
f6e852be74 started working on static gui 2025-11-17 23:03:56 +01:00
Martin Preuss
defd869c26 removed some newlines. 2025-11-17 23:03:15 +01:00
Martin Preuss
ceaf0e0ee5 started working on buttons. 2025-11-17 23:02:55 +01:00
Martin Preuss
35557d7b22 renamed some vars. 2025-11-17 23:02:39 +01:00
Martin Preuss
102f4e65e3 added c03. 2025-11-17 23:02:19 +01:00
Martin Preuss
fbcfd65e7f more work on dialogs. 2025-11-17 23:01:26 +01:00
Martin Preuss
520f371560 com2w: added support for pin change interrupts on atmega644P 2025-11-17 23:00:40 +01:00
Martin Preuss
f8c914538a fixed urls. 2025-11-17 22:59:35 +01:00
Martin Preuss
049d651fec avr: improved modules ILI9341 and XPT2046. 2025-11-10 22:29:37 +01:00
Martin Preuss
ece4fe824a xpt2046 now basically works. 2025-11-10 19:33:40 +01:00
Martin Preuss
03f794b253 xpt2046: reading values works. 2025-11-10 15:55:31 +01:00
Martin Preuss
29f74c0eae avr: started working on xpt2046 module. 2025-11-10 14:49:23 +01:00
Martin Preuss
e82c1cbe5c avr: report motion and door values every 2m. 2025-11-10 14:48:36 +01:00
Martin Preuss
9a37bae20d aqhome-data: really fixed bug with creating bad device objects. 2025-11-07 17:26:03 +01:00
Martin Preuss
783815427a Prepared release 0.0.21 2025-11-07 17:04:18 +01:00
Martin Preuss
9300e515e7 more work on c02 and lcd drivers. 2025-11-07 17:04:08 +01:00
Martin Preuss
660f2502c1 aqhome-data: fixed a bug (was creating device instead of value object). 2025-11-07 17:03:32 +01:00
Martin Preuss
5b72686904 avr: added titled windows 2025-11-03 21:02:25 +01:00
Martin Preuss
e58e9b846c avr: work on simple GUI module to be used by node c02. 2025-11-03 17:23:40 +01:00
Martin Preuss
b4fee78ad8 aqhome-cgi: fixed a bug with RGBW values on pages. 2025-10-29 23:47:15 +01:00
Martin Preuss
06d287b53d prepared release 0.0.20. 2025-10-27 23:15:42 +01:00
Martin Preuss
7c5abc0f3b aqhome-cgi: more code sharing, adding page handling
allows to define your own pages with graphs, sensors and actors.
2025-10-27 23:15:28 +01:00
Martin Preuss
1b0145c97d Merge branch 'mp-2025_10-setdata_double' 2025-10-26 17:01:07 +01:00
361 changed files with 27628 additions and 12034 deletions

9
0BUILD
View File

@@ -2,7 +2,7 @@
<gwbuild> <gwbuild>
<project name="aqhome" version="0.0.19" so_current="0" so_age="0" so_revision="19" write_config_h="TRUE"> <project name="aqhome" version="0.3.0" so_current="4" so_age="3" so_revision="0" write_config_h="TRUE">
<setVar name="package">$(project_name)</setVar> <setVar name="package">$(project_name)</setVar>
<setVar name="version"> <setVar name="version">
$(project_vmajor).$(project_vminor).$(project_vpatchlevel) $(project_vmajor).$(project_vminor).$(project_vpatchlevel)
@@ -180,6 +180,7 @@
devices devices
etc etc
scripts scripts
doc
</subdirs> </subdirs>
<ifVarMatches name="option_with_avr" value="TRUE" > <ifVarMatches name="option_with_avr" value="TRUE" >
@@ -256,4 +257,10 @@
</project> </project>
<extradist>
COPYING
README.md
</extradist>
</gwbuild> </gwbuild>

344
COPYING Normal file
View File

@@ -0,0 +1,344 @@
License
=======
The following is the license text for the GPL in a version that applies to
AqHome.
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
<https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Moe Ghoul>, 1 April 1989
Moe Ghoul, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

82
README.md Normal file
View File

@@ -0,0 +1,82 @@
# Aquamaniac Home Control System (AqHomeControl)
## Quickstart
### Prerequisites
To compile the project the following software needs to be installed:
- gwenhywfar (general purpose library, also containing some development tools)
- avrasm (Opensource avr assembler)
- gcc (GNU C compiler)
### Building
- download and unpack this project
- cd into the main folder
- create a build folder (e.g. "mkdir build")
- cd into that "build" folder
- configure package for building ("gwbuild -s ../")
- build autogenerated files ("gwbuild -p" and "gwbuild -Btm2builder")
- build project ("gwbuild -j NUM_OF_CPUS")
- install project ("sudo gwbuild -i")
## Overview
See https://gitea.aqbanking.de/martin/aqhomecontrol/wiki for more information (in German).
The Aquamaniac Home automation/control system consists of multiple parts:
- nodes
- small PCBs with AVR microcontrollers, for now:
- AtTiny84
- AtTiny85
- AtTiny841
- AtMega644
- examples:
- [N28](https://gitea.aqbanking.de/martin/aqhomecontrol/src/branch/master/doc/mcu-examples/n28/)
- [N29](https://gitea.aqbanking.de/martin/aqhomecontrol/src/branch/master/doc/mcu-examples/n29/)
- [N30](https://gitea.aqbanking.de/martin/aqhomecontrol/src/branch/master/doc/mcu-examples/n30/)
- decentralized, nodes just broadcast measured data over a two-wire
communication network (clock/data), addresses are auto-assigned, no prior
setup necessary, no central bottleneck or single point of failure
- node types
- environmental measuring (temperature, humidity, CO2 and others)
- motion detection, door/window sensors
- LED strip controllers
- modular operating system written in AVR assembler
- event-driven GUI for ATmega MCUs (work in progress)
- driver modules for busses:
- one-wire bus
- two-wire-bus
- SPI bus
- UART
- driver modules for devices/sensors (incomplete list):
- DS18b20 sensors for temperature measurement
- SI7021 sensors for temperature and humidity measurement
- SGP_30 sensors for air quality measurement
- ccs811 sensors for air quality measurement
- SK6812 LED driver
- ds3231 realtime clock module
- I2C displays (work-in-progress)
- SPI displays (work-in-progress)
- PC applications
- aqhome-data
- TCP data service receiving sensor data
- aqhome-nodes
- service receiving sensor data via inter-node bus from nodes
and forwarding that data to aqhome-data
- aqhome-mqttlog
- service exchanging data between aqhome-data service and a
mqtt server
- aqhome-react
- service reacting to data received by aqhome-data (home automation service)
- aqhome-tool
- tool to manipulate aqhome-data (e.g. edit devices and values,
add data points, create graphs from aqhome-data)
- tool to flash firmware for nodes, list nodes
- aqhome-cgi
- HTTP service for user interaction with aqhome-data

View File

@@ -17,6 +17,14 @@
#include <gwenhywfar/debug.h> #include <gwenhywfar/debug.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
/* ------------------------------------------------------------------------------------------------ /* ------------------------------------------------------------------------------------------------
@@ -680,5 +688,133 @@ AQH_USER_LIST *AQH_ModService_LoadRawUsers(AQH_MODULE *m)
void AQH_ModService_EscapeToBuffer(const char *src, GWEN_BUFFER *buf)
{
while (*src) {
unsigned char x;
x=(unsigned char)*src;
if (!(
(x>='A' && x<='Z') ||
(x>='a' && x<='z') ||
(x>='0' && x<='9') ||
NULL!=strchr("-_", x)
)) {
unsigned char c;
GWEN_Buffer_AppendByte(buf, '%');
c=(((unsigned char)(*src))>>4)&0xf;
if (c>9)
c+=7;
c+='0';
GWEN_Buffer_AppendByte(buf, c);
c=((unsigned char)(*src))&0xf;
if (c>9)
c+=7;
c+='0';
GWEN_Buffer_AppendByte(buf, c);
}
else
GWEN_Buffer_AppendByte(buf, *src);
src++;
} /* while */
}
void AQH_ModService_UnescapeToBuffer(const char *src, GWEN_BUFFER *buf)
{
while (*src) {
int charHandled=0;
if (*src=='%') {
if (strlen(src)>2) {
unsigned char d1, d2;
unsigned char c;
if (isxdigit((int)src[1]) && isxdigit((int)src[2])) {
/* skip '%' */
src++;
/* read first digit */
d1=(unsigned char)(toupper(*src));
/* get second digit */
src++;
d2=(unsigned char)(toupper(*src));
/* compute character */
d1-='0';
if (d1>9)
d1-=7;
c=(d1<<4)&0xf0;
d2-='0';
if (d2>9)
d2-=7;
c+=(d2&0xf);
/* store character */
GWEN_Buffer_AppendByte(buf, (char)c);
charHandled=1;
}
}
}
if (!charHandled)
GWEN_Buffer_AppendByte(buf, *src);
src++;
} /* while */
}
int AQH_ModService_FileIsCurrent(const char *sPath, int seconds)
{
struct stat sb;
time_t t1;
if (lstat(sPath, &sb)==-1) {
DBG_ERROR(NULL, "Error on lstat(%s): %s (%d)", sPath, strerror(errno), errno);
return 0;
}
t1=time(0);
if ((t1-sb.st_mtime)<(time_t) seconds) {
DBG_DEBUG(NULL, "File %s is current", sPath);
return 1;
}
return 0;
}
int AQH_ModService_RespondWithMimeFile(AQCGI_REQUEST *rq, const char *sFilename, const char *sMimeType, GWEN_BUFFER *dbuf)
{
GWEN_BUFFER *ibuf;
int rv;
ibuf=GWEN_Buffer_new(0, 1024, 0, 1);
/* read file */
rv=GWEN_SyncIo_Helper_ReadFile(sFilename, ibuf);
if (rv<0) {
DBG_ERROR(NULL, "Error reading \"%s\" (%d)", sFilename, rv);
return rv;
}
else {
GWEN_Buffer_Reset(dbuf);
GWEN_Buffer_AppendBytes(dbuf, GWEN_Buffer_GetStart(ibuf), GWEN_Buffer_GetUsedBytes(ibuf));
if (sMimeType && *sMimeType) {
GWEN_BUFFER *tbuf;
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
GBAA(tbuf, "Content-type: %s", sMimeType);
AQCGI_Request_AddResponseHeaderData(rq, GWEN_Buffer_GetStart(tbuf));
GWEN_Buffer_free(tbuf);
}
AQCGI_Request_AddFlags(rq, AQH_MODSERVICE_RQFLAGS_RAWFILE);
}
GWEN_Buffer_free(ibuf);
return 0;
}

View File

@@ -80,6 +80,12 @@ void AQH_ModService_SetLoadSubModuleFn(AQH_MODULE *m, AQH_MODSERVICE_LOADSUBMODU
void AQH_ModService_SetAddHeaderFn(AQH_MODULE *m, AQH_MODSERVICE_ADDHEADER_FN fn); void AQH_ModService_SetAddHeaderFn(AQH_MODULE *m, AQH_MODSERVICE_ADDHEADER_FN fn);
void AQH_ModService_SetAddFooterFn(AQH_MODULE *m, AQH_MODSERVICE_ADDFOOTER_FN fn); void AQH_ModService_SetAddFooterFn(AQH_MODULE *m, AQH_MODSERVICE_ADDFOOTER_FN fn);
void AQH_ModService_EscapeToBuffer(const char *src, GWEN_BUFFER *buf);
void AQH_ModService_UnescapeToBuffer(const char *src, GWEN_BUFFER *buf);
int AQH_ModService_FileIsCurrent(const char *sPath, int seconds);
int AQH_ModService_RespondWithMimeFile(AQCGI_REQUEST *rq, const char *sFilename, const char *sMimeType, GWEN_BUFFER *dbuf);
#endif #endif

View File

@@ -61,6 +61,7 @@
mdevices_vgraph.h mdevices_vgraph.h
mdevices_device.h mdevices_device.h
mdevices_setdevice.h mdevices_setdevice.h
mdevices_page.h
</headers> </headers>
@@ -81,6 +82,7 @@
mdevices_vgraph.c mdevices_vgraph.c
mdevices_device.c mdevices_device.c
mdevices_setdevice.c mdevices_setdevice.c
mdevices_page.c
</sources> </sources>

View File

@@ -21,6 +21,7 @@
#include "aqhome-cgi/modules/devices/mdevices_vgraph.h" #include "aqhome-cgi/modules/devices/mdevices_vgraph.h"
#include "aqhome-cgi/modules/devices/mdevices_device.h" #include "aqhome-cgi/modules/devices/mdevices_device.h"
#include "aqhome-cgi/modules/devices/mdevices_setdevice.h" #include "aqhome-cgi/modules/devices/mdevices_setdevice.h"
#include "aqhome-cgi/modules/devices/mdevices_page.h"
#include "aqhome-cgi/service/module.h" #include "aqhome-cgi/service/module.h"
#include "aqhome-cgi/modules/mdataclient.h" #include "aqhome-cgi/modules/mdataclient.h"
@@ -61,6 +62,11 @@ static void _handleRqSetDataPost(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *
static void _handleRqGraphGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf); static void _handleRqGraphGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf);
static void _handleRqDeviceGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf); static void _handleRqDeviceGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf);
static void _handleRqDevicePost(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf); static void _handleRqDevicePost(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf);
static void _handleRqPageGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf);
static void _handleRqPagePost(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf);
static void _handleRqPageGraphGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf);
static AQDG_GRAPH_DATAPAIR_LIST *_createDataPairListFromDataPoints(const uint64_t *dataPoints, uint64_t numValues);
static void _addValueActionToForm(const AQH_VALUE *value, GWEN_BUFFER *dbuf); static void _addValueActionToForm(const AQH_VALUE *value, GWEN_BUFFER *dbuf);
static void _addLastValueToForm(AQH_DATACLIENT *dc, const AQH_VALUE *value, GWEN_BUFFER *dbuf); static void _addLastValueToForm(AQH_DATACLIENT *dc, const AQH_VALUE *value, GWEN_BUFFER *dbuf);
@@ -81,6 +87,9 @@ static AQH_MODSERVICE_HANDLER_ENTRY _requestTable[]={
{"value.html", AQCGI_REQUEST_METHOD_GET, P_DEVICEREAD | P_VALUEREAD, _handleRqValueGet}, {"value.html", AQCGI_REQUEST_METHOD_GET, P_DEVICEREAD | P_VALUEREAD, _handleRqValueGet},
{"setdata.html", AQCGI_REQUEST_METHOD_POST, P_VALUEWRITE, _handleRqSetDataPost}, {"setdata.html", AQCGI_REQUEST_METHOD_POST, P_VALUEWRITE, _handleRqSetDataPost},
{"graph.html", AQCGI_REQUEST_METHOD_GET, P_DEVICEREAD | P_VALUEREAD, _handleRqGraphGet}, {"graph.html", AQCGI_REQUEST_METHOD_GET, P_DEVICEREAD | P_VALUEREAD, _handleRqGraphGet},
{"page.html", AQCGI_REQUEST_METHOD_GET, P_DEVICEREAD | P_VALUEREAD, _handleRqPageGet},
{"page.html", AQCGI_REQUEST_METHOD_POST, P_VALUEWRITE, _handleRqPagePost},
{"pgraph.html", AQCGI_REQUEST_METHOD_GET, P_DEVICEREAD | P_VALUEREAD, _handleRqPageGraphGet},
{NULL, 0, 0, NULL} {NULL, 0, 0, NULL}
}; };
@@ -171,6 +180,26 @@ void _handleRqDevicePost(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session,
void _handleRqPageGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf)
{
AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunPageGet, dbuf);
}
void _handleRqPagePost(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf)
{
AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunPagePost, dbuf);
}
void _handleRqPageGraphGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf)
{
AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunPageGraph, dbuf);
}
@@ -227,6 +256,44 @@ uint32_t AQH_ModDevices_RgbwToHtmlColor(uint32_t colorIn)
uint32_t AQH_ModDevices_RgbwFromComponents(int r, int g, int b, int w)
{
uint32_t colorOut;
colorOut=((r & 0xff)<<16) | ((g & 0xff)<<24) | (b & 0xff) | ((w & 0xff)<<8);
return colorOut;
}
int AQH_ModDevices_RgbwGetR(uint32_t color)
{ /* GGRRWWBB */
return (color & 0x00ff0000)>>16;
}
int AQH_ModDevices_RgbwGetG(uint32_t color)
{ /* GGRRWWBB */
return (color & 0xff000000)>>24;
}
int AQH_ModDevices_RgbwGetB(uint32_t color)
{ /* GGRRWWBB */
return (color & 0x000000ff);
}
int AQH_ModDevices_RgbwGetW(uint32_t color)
{ /* GGRRWWBB */
return (color & 0x0000ff00)>>8;
}
void _addValueActionToForm(const AQH_VALUE *value, GWEN_BUFFER *dbuf) void _addValueActionToForm(const AQH_VALUE *value, GWEN_BUFFER *dbuf)
{ {
const char *sValueName; const char *sValueName;
@@ -392,4 +459,107 @@ AQH_DEVICE *AQH_ModDevices_GetDevice(AQH_DATACLIENT *dc, const char *sDeviceName
int AQH_ModDevices_ValueGetLastDataAsInt(AQH_DATACLIENT *dc, const AQH_VALUE *value, int defaultValue)
{
const char *sValueSystemName;
uint64_t dataPoints[2];
uint64_t recvdNum;
// uint64_t timestamp;
union {double f; uint64_t i;} u;
int intVal;
sValueSystemName=AQH_Value_GetNameForSystem(value);
recvdNum=AQH_DataClient_GetLastData(dc, sValueSystemName, &dataPoints[0], 1);
if (recvdNum>0) {
// timestamp=dataPoints[0];
u.i=dataPoints[1];
intVal=(int) u.f;
}
else {
DBG_INFO(NULL, "No last value for \"%s\"", sValueSystemName);
intVal=defaultValue;
}
return intVal;
}
uint32_t AQH_ModDevices_ValueGetLastDataAsUint32(AQH_DATACLIENT *dc, const AQH_VALUE *value, uint32_t defaultValue)
{
const char *sValueSystemName;
uint64_t dataPoints[2];
uint64_t recvdNum;
// uint64_t timestamp;
union {double f; uint64_t i;} u;
uint32_t uintVal;
sValueSystemName=AQH_Value_GetNameForSystem(value);
recvdNum=AQH_DataClient_GetLastData(dc, sValueSystemName, &dataPoints[0], 1);
if (recvdNum>0) {
// timestamp=dataPoints[0];
u.i=dataPoints[1];
uintVal=(uint32_t) u.f;
DBG_ERROR(NULL, "Transformed value %.2f -> %08x", u.f, uintVal);
}
else {
DBG_INFO(NULL, "No last value for \"%s\"", sValueSystemName);
uintVal=defaultValue;
}
return uintVal;
}
AQDG_GRAPH_DATAPAIR_LIST *AQH_ModDevices_RequestDataPairList(AQH_DATACLIENT *dc, const char *systemValueName,
uint64_t tsBegin, uint64_t tsEnd, uint64_t num)
{
uint64_t *dataPoints;
uint64_t recvdNum;
dataPoints=malloc(num*sizeof(uint64_t)*2);
recvdNum=AQH_DataClient_GetPeriodData(dc, systemValueName, dataPoints, num, tsBegin, tsEnd);
if (recvdNum>0) {
AQDG_GRAPH_DATAPAIR_LIST *dpList;
dpList=_createDataPairListFromDataPoints(dataPoints, recvdNum);
free(dataPoints);
return dpList;
}
else {
DBG_ERROR(NULL, "No data received for %s", systemValueName);
free(dataPoints);
return NULL;
}
}
AQDG_GRAPH_DATAPAIR_LIST *_createDataPairListFromDataPoints(const uint64_t *dataPoints, uint64_t numValues)
{
AQDG_GRAPH_DATAPAIR_LIST *dpList;
uint64_t i;
DBG_DEBUG(NULL, "Got %d datapoints", (int) numValues);
dpList=AQDG_Graph_DataPair_List_new();
for(i=0; i<numValues; i++) {
AQDG_GRAPH_DATAPAIR *dp;
double timestamp;
union {double f; uint64_t i;} u;
timestamp=(double)(*(dataPoints++));
u.i=*(dataPoints++);
dp=AQDG_Graph_DataPair_new();
AQDG_Graph_DataPair_SetValueX(dp, timestamp);
AQDG_Graph_DataPair_SetValueY(dp, u.f);
AQDG_Graph_DataPair_List_Add(dp, dpList);
}
AQDG_Graph_DataPair_List_SortByValueX(dpList, 1);
return dpList;
}

View File

@@ -17,6 +17,8 @@
#include <aqcgi/request.h> #include <aqcgi/request.h>
#include <aqdiagram/graph/datapair.h>
#include <gwenhywfar/buffer.h> #include <gwenhywfar/buffer.h>
@@ -44,9 +46,23 @@ int AQH_ModDevices_Create(AQH_SERVICE *sv);
uint32_t AQH_ModDevices_ColorFromHexString(const char *s); uint32_t AQH_ModDevices_ColorFromHexString(const char *s);
uint32_t AQH_ModDevices_HtmlColorToValueRGBW(uint32_t colorIn); uint32_t AQH_ModDevices_HtmlColorToValueRGBW(uint32_t colorIn);
uint32_t AQH_ModDevices_RgbwToHtmlColor(uint32_t colorIn); uint32_t AQH_ModDevices_RgbwToHtmlColor(uint32_t colorIn);
uint32_t AQH_ModDevices_RgbwFromComponents(int r, int g, int b, int w);
int AQH_ModDevices_RgbwGetR(uint32_t color);
int AQH_ModDevices_RgbwGetG(uint32_t color);
int AQH_ModDevices_RgbwGetB(uint32_t color);
int AQH_ModDevices_RgbwGetW(uint32_t color);
AQH_VALUE *AQH_ModDevices_GetValueForDevice(AQH_DATACLIENT *dc, const char *sDeviceName, const char *sValueName); AQH_VALUE *AQH_ModDevices_GetValueForDevice(AQH_DATACLIENT *dc, const char *sDeviceName, const char *sValueName);
AQH_DEVICE *AQH_ModDevices_GetDevice(AQH_DATACLIENT *dc, const char *sDeviceName); AQH_DEVICE *AQH_ModDevices_GetDevice(AQH_DATACLIENT *dc, const char *sDeviceName);
int AQH_ModDevices_ValueGetLastDataAsInt(AQH_DATACLIENT *dc, const AQH_VALUE *value, int defaultValue);
uint32_t AQH_ModDevices_ValueGetLastDataAsUint32(AQH_DATACLIENT *dc, const AQH_VALUE *value, uint32_t defaultValue);
AQDG_GRAPH_DATAPAIR_LIST *AQH_ModDevices_RequestDataPairList(AQH_DATACLIENT *dc, const char *systemValueName,
uint64_t tsBegin, uint64_t tsEnd, uint64_t num);

View File

@@ -0,0 +1,961 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2025 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./mdevices_page.h"
#include "aqhome-cgi/service/module.h"
#include "aqhome-cgi/modules/mdataclient.h"
#include <aqdiagram/graph/timegraph.h>
#include <aqdiagram/graph/w_graph.h>
#include <aqdiagram/draw/context_cairo.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/timestamp.h>
#include <gwenhywfar/text.h>
#include <gwenhywfar/directory.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
/* ------------------------------------------------------------------------------------------------
* defs and enums
* ------------------------------------------------------------------------------------------------
*/
#define GBAS GWEN_Buffer_AppendString
#define GBAA GWEN_Buffer_AppendArgs
enum {
MY_LAYOUT_NONE=0,
MY_LAYOUT_HORIZONTAL,
MY_LAYOUT_VERTICAL
};
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static void _writePage(AQH_MODULE *m, AQH_DATACLIENT *dc, GWEN_XMLNODE *nPage, GWEN_BUFFER *dbuf);
static void _writeItem(AQH_MODULE *m, AQH_DATACLIENT *dc, const char *sPageId, GWEN_XMLNODE *nItem, GWEN_BUFFER *dbuf);
static void _writeActor(AQH_DATACLIENT *dc, const char *sPageId, GWEN_XMLNODE *n, int layout, GWEN_BUFFER *dbuf);
static void _writeGraph(const char *sPageId, GWEN_XMLNODE *n, int layout, GWEN_BUFFER *dbuf);
static void _handlePageActor(AQCGI_REQUEST *rq, AQH_DATACLIENT *dc, const char *sActorId, GWEN_XMLNODE *nActor);
static void _handlePageGraph(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_DATACLIENT *dc, const char *sGraphId, GWEN_XMLNODE *nGraph, GWEN_BUFFER *dbuf);
static void _genPageGraph(AQH_DATACLIENT *dc, const char *sGraphId, const char *sFilename, GWEN_XMLNODE *nGraph);
static void _addCurves(AQH_DATACLIENT *dc, AQDG_GRAPH *g, GWEN_XMLNODE *nGraph, uint64_t tsBegin, uint64_t tsEnd);
static AQDG_GRAPH_DATAPAIR_LIST *_readCurveData(AQH_DATACLIENT *dc, GWEN_XMLNODE *nCurve, uint64_t tsBegin, uint64_t tsEnd);
static uint64_t _parseTime(const char *s);
static GWEN_XMLNODE *_getSubItemNode(GWEN_XMLNODE *nPage, const char *sId, const char *sElementName);
static void _mkPathForGraph(AQH_MODULE *m, const char *sGraphId, GWEN_BUFFER *dbuf);
static void _addGraphLink(const char *sPageId, const char *sGraphId, int w, int h, GWEN_BUFFER *dbuf);
static void _writeRgbwToForm(const char *sValueName, uint32_t color, GWEN_BUFFER *dbuf);
static void _writeOnOffToForm(const char *sValueName, int intVal, GWEN_BUFFER *dbuf);
static void _writeOnOffAutoToForm(const char *sValueName, int intVal, GWEN_BUFFER *dbuf);
static void _setRgbwData(AQH_DATACLIENT *dc, GWEN_DB_NODE *dbPost, const char *sValueName, const AQH_VALUE *value);
static int _getColorComponent(GWEN_DB_NODE *dbPost, const char *sValueName, const char *sComponent, int defaultValue);
static void _setOnOffData(AQH_DATACLIENT *dc, const AQH_VALUE *value, const char *sValue);
static void _setOnOffAutoData(AQH_DATACLIENT *dc, const AQH_VALUE *value, const char *sValue);
static void _sendPageList(AQH_MODULE *m, GWEN_BUFFER *dbuf);
static GWEN_STRINGLIST *_listPageFiles(AQH_MODULE *m);
static GWEN_XMLNODE *_readPage(AQH_MODULE *m, const char *sPageName);
static GWEN_XMLNODE *_readPageFile(const char *sFilename);
static int _layoutFromString(const char *s);
/* ------------------------------------------------------------------------------------------------
* code
* ------------------------------------------------------------------------------------------------
*/
void AQH_ModDevices_RunPageGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf)
{
GWEN_DB_NODE *dbQuery;
const char *sPageId;
DBG_INFO(NULL, "RunPageGet");
dbQuery=AQCGI_Request_GetDbQuery(rq);
sPageId=dbQuery?GWEN_DB_GetCharValue(dbQuery, "page", 0, NULL):NULL;
if (sPageId && *sPageId) {
GWEN_XMLNODE *fileNode;
fileNode=_readPage(m, sPageId);
if (fileNode) {
GWEN_XMLNODE *nPage;
nPage=GWEN_XMLNode_FindFirstTag(fileNode, "page", NULL, NULL);
if (nPage) {
_writePage(m, dc, nPage, dbuf);
AQCGI_Request_AddResponseHeaderData(rq, "Refresh: 120");
}
else {
DBG_ERROR(NULL, "No page element in file for \"%s\"", sPageId);
}
GWEN_XMLNode_free(fileNode);
}
else {
DBG_INFO(NULL, "here");
}
}
else {
DBG_ERROR(NULL, "Reading page list");
_sendPageList(m, dbuf);
}
}
void AQH_ModDevices_RunPageGraph(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf)
{
GWEN_DB_NODE *dbQuery;
const char *sPageId;
const char *sGraphId;
DBG_INFO(NULL, "RunPageGraphGet");
dbQuery=AQCGI_Request_GetDbQuery(rq);
sPageId=GWEN_DB_GetCharValue(dbQuery, "page", 0, NULL);
sGraphId=GWEN_DB_GetCharValue(dbQuery, "graph", 0, NULL);
if (sPageId && *sPageId && sGraphId && *sGraphId) {
GWEN_XMLNODE *fileNode;
fileNode=_readPage(m, sPageId);
if (fileNode) {
GWEN_XMLNODE *nPage;
GWEN_XMLNODE *nGraph;
nPage=GWEN_XMLNode_FindFirstTag(fileNode, "page", "id", sPageId);
nGraph=_getSubItemNode(nPage, sGraphId, "graph");
if (nPage && nGraph)
_handlePageGraph(m, rq, dc, sGraphId, nGraph, dbuf);
else {
DBG_ERROR(NULL, "Graph %s/%s not found", sPageId, sGraphId);
}
GWEN_XMLNode_free(fileNode);
}
else {
DBG_INFO(NULL, "here");
}
}
}
void AQH_ModDevices_RunPagePost(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf)
{
GWEN_DB_NODE *dbPost;
const char *sPageId;
const char *sActorId;
DBG_INFO(NULL, "RunPagePost");
dbPost=AQCGI_Request_GetDbPostBody(rq);
sPageId=GWEN_DB_GetCharValue(dbPost, "page", 0, NULL);
sActorId=GWEN_DB_GetCharValue(dbPost, "actor", 0, NULL);
if (sPageId && *sPageId && sActorId && *sActorId) {
GWEN_XMLNODE *fileNode;
fileNode=_readPage(m, sPageId);
if (fileNode) {
GWEN_XMLNODE *nPage;
GWEN_XMLNODE *nActor;
nPage=GWEN_XMLNode_FindFirstTag(fileNode, "page", "id", sPageId);
nActor=_getSubItemNode(nPage, sActorId, "actor");
if (nPage && nActor) {
_handlePageActor(rq, dc, sActorId, nActor);
}
else {
DBG_ERROR(NULL, "Actor %s/%s not found", sPageId, sActorId);
}
GWEN_XMLNode_free(fileNode);
}
else {
DBG_INFO(NULL, "here");
}
}
if (sPageId && *sPageId) {
GWEN_BUFFER *pbuf;
pbuf=GWEN_Buffer_new(0, 256, 0, 1);
GBAS(pbuf, "Location: page.html?page=");
GWEN_Text_EscapeToBuffer(sPageId, pbuf);
AQCGI_Request_AddResponseHeaderData(rq, GWEN_Buffer_GetStart(pbuf));
GWEN_Buffer_free(pbuf);
}
AQCGI_Request_SetResponseCode(rq, 303);
AQCGI_Request_SetResponseText(rq, "See other");
}
void _handlePageActor(AQCGI_REQUEST *rq, AQH_DATACLIENT *dc, const char *sActorId, GWEN_XMLNODE *nActor)
{
GWEN_DB_NODE *dbPost;
const char *sDeviceName;
const char *sValueName;
dbPost=AQCGI_Request_GetDbPostBody(rq);
sDeviceName=GWEN_XMLNode_GetProperty(nActor, "device", NULL);
sValueName=GWEN_XMLNode_GetProperty(nActor, "value", NULL);
if (sDeviceName && *sDeviceName && sValueName && *sValueName) {
AQH_VALUE *value;
value=AQH_ModDevices_GetValueForDevice(dc, sDeviceName, sValueName);
if (value) {
const char *sSystemValueName;
sSystemValueName=AQH_Value_GetNameForSystem(value);
if (sSystemValueName) {
const char *sData;
sData=GWEN_DB_GetCharValue(dbPost, sActorId, 0, NULL);
DBG_INFO(NULL, "Setting value %s to %s", sSystemValueName, sData?sData:"empty/no value");
switch(AQH_Value_GetModality(value)) {
case AQH_ValueModality_RGBW: _setRgbwData(dc, dbPost, sActorId, value); break;
case AQH_ValueModality_OnOff: _setOnOffData(dc, value, sData); break;
case AQH_ValueModality_OnOffAuto: _setOnOffAutoData(dc, value, sData); break;
default:
break;
} /* switch */
}
}
}
}
void _handlePageGraph(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_DATACLIENT *dc, const char *sGraphId, GWEN_XMLNODE *nGraph, GWEN_BUFFER *dbuf)
{
GWEN_BUFFER *fbuf;
int refreshTime;
refreshTime=GWEN_XMLNode_GetIntProperty(nGraph, "refreshTime", 120);
fbuf=GWEN_Buffer_new(0, 256, 0, 1);
_mkPathForGraph(m, sGraphId, fbuf);
if (!AQH_ModService_FileIsCurrent(GWEN_Buffer_GetStart(fbuf), refreshTime)) {
_genPageGraph(dc, sGraphId, GWEN_Buffer_GetStart(fbuf), nGraph);
}
AQH_ModService_RespondWithMimeFile(rq, GWEN_Buffer_GetStart(fbuf), "image/png", dbuf);
GWEN_Buffer_free(fbuf);
}
void _genPageGraph(AQH_DATACLIENT *dc, const char *sGraphId, const char *sFilename, GWEN_XMLNODE *nGraph)
{
const char *s;
const char *sTitle;
int w;
int h;
int precision;
uint64_t tsBegin;
uint64_t tsEnd;
AQDG_GRAPH *g;
AQDG_DRAW_CONTEXT *drawContext;
AQDG_OBJECT *graphObject;
uint32_t tickFlags=0;
double upperLimit;
double lowerLimit;
sTitle=GWEN_XMLNode_GetProperty(nGraph, "title", "untitled");
w=GWEN_XMLNode_GetIntProperty(nGraph, "width", AQH_MODDEVICES_GRAPH_WIDTH);
h=GWEN_XMLNode_GetIntProperty(nGraph, "height", AQH_MODDEVICES_GRAPH_HEIGHT);
precision=GWEN_XMLNode_GetIntProperty(nGraph, "precision", 2);
tsBegin=_parseTime(GWEN_XMLNode_GetProperty(nGraph, "begin", "-4h"));
tsEnd=_parseTime(GWEN_XMLNode_GetProperty(nGraph, "end", "0"));
s=GWEN_XMLNode_GetProperty(nGraph, "lowerLimit", NULL);
if (s && *s) {
if (1==sscanf(s, "%lf", &lowerLimit))
tickFlags|=AQDG_TIMEGRAPH_SETUPTICKS_FLAGS_MINY;
else {
DBG_ERROR(NULL, "Ignoring invalid lowerLimit (%s)", s);
}
}
s=GWEN_XMLNode_GetProperty(nGraph, "upperLimit", NULL);
if (s && *s) {
if (1==sscanf(s, "%lf", &upperLimit))
tickFlags|=AQDG_TIMEGRAPH_SETUPTICKS_FLAGS_MAXY;
else {
DBG_ERROR(NULL, "Ignoring invalid upperLimit (%s)", s);
}
}
g=AQDG_TimeGraph_new(sTitle, NULL, "Value", NULL, precision);
_addCurves(dc, g, nGraph, tsBegin, tsEnd);
AQDG_TimeGraph_SetupTicks(g, tickFlags, lowerLimit, upperLimit);
DBG_DEBUG(NULL, "Draw graph for %s", sGraphId);
drawContext=AQDG_Draw_ContextCairo_Png_new(sFilename, w, h);
graphObject=AQDG_GraphWidget_new(NULL, AQDG_OBJECT_OPTIONS_STRETCHX | AQDG_OBJECT_OPTIONS_STRETCHY, drawContext);
AQDG_Object_SetWidth(graphObject, w);
AQDG_Object_SetHeight(graphObject, h);
AQDG_GraphWidget_SetupDefaultPens(graphObject);
AQDG_GraphWidget_SetupDefaultFonts(graphObject);
AQDG_GraphWidget_FinishWithGraph(graphObject, g);
AQDG_Object_free(graphObject);
}
void _addCurves(AQH_DATACLIENT *dc, AQDG_GRAPH *g, GWEN_XMLNODE *nGraph, uint64_t tsBegin, uint64_t tsEnd)
{
GWEN_XMLNODE *nCurve;
nCurve=GWEN_XMLNode_FindFirstTag(nGraph, "curve", NULL, NULL);
while(nCurve) {
const char *sModifier;
sModifier=GWEN_XMLNode_GetProperty(nCurve, "modifier", NULL);
if (sModifier && *sModifier) {
AQDG_GRAPH_DATAPAIR_LIST *dpList;
dpList=_readCurveData(dc, nCurve, tsBegin, tsEnd);
if (dpList) {
const char *sCurveLabel;
sCurveLabel=GWEN_XMLNode_GetProperty(nCurve, "title", NULL);
DBG_DEBUG(NULL, "Adding data for %s", sCurveLabel?sCurveLabel:"<no title>");
AQDG_TimeGraph_ModifyDataAndAddCurve(g, sCurveLabel?sCurveLabel:"<no title>", sModifier, dpList);
}
}
nCurve=GWEN_XMLNode_FindNextTag(nCurve, "curve", NULL, NULL);
}
}
AQDG_GRAPH_DATAPAIR_LIST *_readCurveData(AQH_DATACLIENT *dc, GWEN_XMLNODE *nCurve, uint64_t tsBegin, uint64_t tsEnd)
{
const char *sDeviceName;
const char *sValueName;
sDeviceName=GWEN_XMLNode_GetProperty(nCurve, "device", NULL);
sValueName=GWEN_XMLNode_GetProperty(nCurve, "value", NULL);
if (sDeviceName && *sDeviceName && sValueName && *sValueName) {
AQDG_GRAPH_DATAPAIR_LIST *dpList;
GWEN_BUFFER *vbuf;
vbuf=GWEN_Buffer_new(0, 64, 0, 1);
GBAA(vbuf, "%s/%s", sDeviceName, sValueName);
dpList=AQH_ModDevices_RequestDataPairList(dc, GWEN_Buffer_GetStart(vbuf), tsBegin, tsEnd, 100000);
if (dpList) {
GWEN_Buffer_free(vbuf);
return dpList;
}
GWEN_Buffer_free(vbuf);
}
return NULL;
}
// TODO: move to aqhome.{c,h}
uint64_t _parseTime(const char *s)
{
if (s && *s) {
if (*s=='-') {
uint64_t x=0;
uint64_t now=time(NULL);
s++;
while(*s && isdigit(*s)) {
unsigned int i;
i=*(s++)-'0';
x*=10;
x+=i;
}
if (*s) {
switch(*s) {
case 0:
case 'm': x*=60; break;
case 'h': x*=(60*60); break;
case 'd': x*=(60*60*24); break;
case 'w': x*=(60*60*24*7); break;
case 'M': x*=(60*60*24*30); break;
case 'y': x*=(60*60*24*365); break;
default: break;
}
}
return (now-x);
}
if (*s=='@') {
int y, m, d, H, M, S;
if (6==sscanf(s+1, "%d/%d/%d-%d:%d:%d", &y, &m, &d, &H, &M, &S)) {
GWEN_TIMESTAMP *ts;
uint64_t x=0;
ts=GWEN_Timestamp_new(y, m, d, H, M, S);
x=GWEN_Timestamp_toTimeT(ts);
GWEN_Timestamp_free(ts);
return x;
}
else {
DBG_ERROR(NULL, "Invalid timespec [%s], expected: @YYYY/MM/DD-HH:MM:SS", s);
return (uint64_t) (-1);
}
}
else {
unsigned long int x;
if (1!=sscanf(s, "%lu", &x)) {
DBG_ERROR(NULL, "ERROR: Invalid timestamp");
return (uint64_t) (-1);
}
return (uint64_t) x;
}
}
return 0;
}
GWEN_XMLNODE *_getSubItemNode(GWEN_XMLNODE *nPage, const char *sId, const char *sElementName)
{
GWEN_XMLNODE *nItem;
nItem=GWEN_XMLNode_FindFirstTag(nPage, "item", NULL, NULL);
while(nItem) {
GWEN_XMLNODE *nGraph;
nGraph=GWEN_XMLNode_FindFirstTag(nItem, sElementName, "id", sId);
if (nGraph)
return nGraph;
nItem=GWEN_XMLNode_FindNextTag(nItem, "item", NULL, NULL);
}
return NULL;
}
void _mkPathForGraph(AQH_MODULE *m, const char *sGraphId, GWEN_BUFFER *buf)
{
AQH_SERVICE *sv;
const char *s;
sv=AQH_ModService_GetService(m);
s=AQH_Service_GetCacheFolder(sv);
GBAA(buf, "%s%s%s", s, GWEN_DIR_SEPARATOR_S, sGraphId);
AQH_ModService_EscapeToBuffer(s, buf);
GBAS(buf, ".png");
}
void _writePage(AQH_MODULE *m, AQH_DATACLIENT *dc, GWEN_XMLNODE *nPage, GWEN_BUFFER *dbuf)
{
const char *sPageId;
GWEN_XMLNODE *nItem;
int layout;
const char *s;
sPageId=GWEN_XMLNode_GetProperty(nPage, "id", NULL);
/* title */
s=GWEN_XMLNode_GetProperty(nPage, "title", NULL);
if (s && *s)
GBAA(dbuf, "<h1>%s</h1>\n", s);
layout=_layoutFromString(GWEN_XMLNode_GetProperty(nPage, "layout", "none"));
if (layout!=MY_LAYOUT_NONE)
GBAS(dbuf, "<table class=\"pageTable\">\n");
if (layout==MY_LAYOUT_HORIZONTAL)
GBAS(dbuf, "<tr>\n");
nItem=GWEN_XMLNode_FindFirstTag(nPage, "item", NULL, NULL);
while(nItem) {
if (layout==MY_LAYOUT_VERTICAL)
GBAS(dbuf, "<tr>\n");
if (layout!=MY_LAYOUT_NONE)
GBAS(dbuf, "<td>");
_writeItem(m, dc, sPageId, nItem, dbuf);
if (layout!=MY_LAYOUT_NONE)
GBAS(dbuf, "</td>");
if (layout==MY_LAYOUT_VERTICAL)
GBAS(dbuf, "</tr>\n");
nItem=GWEN_XMLNode_FindNextTag(nItem, "item", NULL, NULL);
} /* while */
if (layout==MY_LAYOUT_HORIZONTAL)
GBAS(dbuf, "</tr>\n");
if (layout!=MY_LAYOUT_NONE)
GBAS(dbuf, "</table> <!-- pageTable -->\n");
}
void _writeItem(AQH_MODULE *m, AQH_DATACLIENT *dc, const char *sPageId, GWEN_XMLNODE *nItem, GWEN_BUFFER *dbuf)
{
GWEN_XMLNODE *n;
uint32_t perms;
int layout;
perms=AQH_ModService_GetUserPerms(m);
layout=_layoutFromString(GWEN_XMLNode_GetProperty(nItem, "layout", "none"));
if (layout!=MY_LAYOUT_NONE)
GBAS(dbuf, "<table class=\"itemTable\">\n");
if (layout==MY_LAYOUT_HORIZONTAL)
GBAS(dbuf, "<tr>\n");
n=GWEN_XMLNode_GetFirstTag(nItem);
while(n) {
const char *sName;
if (layout==MY_LAYOUT_VERTICAL)
GBAS(dbuf, "<tr>\n");
sName=GWEN_XMLNode_GetData(n);
if (sName && *sName) {
if (strcasecmp(sName, "actor")==0) {
if (perms && AQH_MODDEVICES_PERMS_VALUEWRITE)
_writeActor(dc, sPageId, n, layout, dbuf);
else {
DBG_ERROR(NULL, "No permissions to write values");
}
}
else if (strcasecmp(sName, "graph")==0)
_writeGraph(sPageId, n, layout, dbuf);
else {
DBG_ERROR(NULL, "Ignoring element \"%s\"", sName);
}
}
if (layout==MY_LAYOUT_VERTICAL)
GBAS(dbuf, "</tr>\n");
n=GWEN_XMLNode_GetNextTag(n);
}
if (layout==MY_LAYOUT_HORIZONTAL)
GBAS(dbuf, "</tr>\n");
if (layout!=MY_LAYOUT_NONE)
GBAS(dbuf, "</table> <!-- itemTable -->\n");
}
void _writeActor(AQH_DATACLIENT *dc, const char *sPageId, GWEN_XMLNODE *n, int layout, GWEN_BUFFER *dbuf)
{
const char *sActorId;
const char *sDeviceName;
const char *sValueName;
const char *sLabel;
sActorId=GWEN_XMLNode_GetProperty(n, "id", NULL);
sLabel=GWEN_XMLNode_GetProperty(n, "label", NULL);
sDeviceName=GWEN_XMLNode_GetProperty(n, "device", NULL);
sValueName=GWEN_XMLNode_GetProperty(n, "value", NULL);
if (sActorId && *sActorId && sDeviceName && *sDeviceName && sValueName && *sValueName) {
AQH_VALUE *value;
value=AQH_ModDevices_GetValueForDevice(dc, sDeviceName, sValueName);
if (value) {
uint32_t lastData;
if (layout!=MY_LAYOUT_NONE)
GBAS(dbuf, "<td>\n");
lastData=AQH_ModDevices_ValueGetLastDataAsUint32(dc, value, 0);
GBAS(dbuf,"<form action=\"page.html\" method=\"post\">\n");
GBAA(dbuf, "<input type=\"hidden\" name=\"page\" value=\"%s\">\n", sPageId);
GBAA(dbuf, "<input type=\"hidden\" name=\"actor\" value=\"%s\">\n", sActorId);
DBG_INFO(NULL, "Adding actor");
if (sLabel && *sLabel)
GBAA(dbuf,"<label for=\"%s\">%s</label>", sActorId, sLabel);
if (layout!=MY_LAYOUT_NONE) {
GBAS(dbuf, "</td>\n");
GBAS(dbuf, "<td>\n");
}
switch(AQH_Value_GetModality(value)) {
case AQH_ValueModality_RGBW: _writeRgbwToForm(sActorId, lastData, dbuf); break;
case AQH_ValueModality_OnOff: _writeOnOffToForm(sActorId, lastData, dbuf); break;
case AQH_ValueModality_OnOffAuto: _writeOnOffAutoToForm(sActorId, lastData, dbuf); break;
default: GBAA(dbuf, "%d", lastData); break;
} /* switch */
if (layout!=MY_LAYOUT_NONE) {
GBAS(dbuf, "</td>\n");
GBAS(dbuf, "<td>\n");
}
GBAS(dbuf,"<input type=\"submit\" name=\"action\" value=\"Send\"/>");
GBAS(dbuf, "</form>\n\n");
if (layout!=MY_LAYOUT_NONE)
GBAS(dbuf, "</td>\n");
}
}
}
void _writeGraph(const char *sPageId, GWEN_XMLNODE *n, int layout, GWEN_BUFFER *dbuf)
{
const char *sGraphId;
int w;
int h;
w=GWEN_XMLNode_GetIntProperty(n, "width", AQH_MODDEVICES_GRAPH_WIDTH);
h=GWEN_XMLNode_GetIntProperty(n, "height", AQH_MODDEVICES_GRAPH_HEIGHT);
sGraphId=GWEN_XMLNode_GetProperty(n, "id", NULL);
if (layout!=MY_LAYOUT_NONE)
GBAS(dbuf, "<td colspan=\"3\">\n");
if (sGraphId && *sGraphId)
_addGraphLink(sPageId, sGraphId, w, h, dbuf);
if (layout!=MY_LAYOUT_NONE)
GBAS(dbuf, "</td>\n");
}
void _addGraphLink(const char *sPageId, const char *sGraphId, int w, int h, GWEN_BUFFER *dbuf)
{
GBAS(dbuf, "<img src=\"pgraph.html?page=");
AQH_ModService_EscapeToBuffer(sPageId, dbuf);
GBAS(dbuf, "&graph=");
AQH_ModService_EscapeToBuffer(sGraphId, dbuf);
GBAA(dbuf, "\" alt=\"%s\" width=\"%d\" height=\"%d\"", sGraphId, w, h);
GBAS(dbuf, "/>");
}
void _writeRgbwToForm(const char *sValueName, uint32_t color, GWEN_BUFFER *dbuf)
{
#if 1
DBG_ERROR(NULL, "Color=%08x (%d, %d, %d, %d)",
color,
AQH_ModDevices_RgbwGetR(color),
AQH_ModDevices_RgbwGetG(color),
AQH_ModDevices_RgbwGetB(color),
AQH_ModDevices_RgbwGetW(color));
GBAA(dbuf, "<label for=\"%s_r\">R:</label>", sValueName);
GBAA(dbuf, "<input type=\"number\" min=\"0\" max=\"255\" name=\"%s_r\" id=name=\"%s_r\" value=\"%d\">",
sValueName, sValueName, AQH_ModDevices_RgbwGetR(color));
GBAA(dbuf, "<label for=\"%s_g\">G:</label>", sValueName);
GBAA(dbuf, "<input type=\"number\" min=\"0\" max=\"255\" name=\"%s_g\" id=name=\"%s_g\" value=\"%d\">",
sValueName, sValueName, AQH_ModDevices_RgbwGetG(color));
GBAA(dbuf, "<label for=\"%s_b\">B:</label>", sValueName);
GBAA(dbuf, "<input type=\"number\" min=\"0\" max=\"255\" name=\"%s_b\" id=name=\"%s_b\" value=\"%d\">",
sValueName, sValueName, AQH_ModDevices_RgbwGetB(color));
GBAA(dbuf, "<label for=\"%s_w\">W:</label>", sValueName);
GBAA(dbuf, "<input type=\"number\" min=\"0\" max=\"255\" name=\"%s_w\" id=name=\"%s_w\" value=\"%d\">",
sValueName, sValueName, AQH_ModDevices_RgbwGetW(color));
#else
GBAA(dbuf, "<input type=\"text\" name=\"%s\" id=\"%s\" value=\"#%08x\"/>", sValueName, sValueName, color);
// else
GBAA(dbuf, "<input type=\"color\" name=\"%s\" id=\"%s\" value=\"#%08x\"/>#%08x (#%08x)",
sValueName, sValueName,
AQH_ModDevices_RgbwToHtmlColor(color),
AQH_ModDevices_RgbwToHtmlColor(color),
color);
#endif
}
void _setRgbwData(AQH_DATACLIENT *dc, GWEN_DB_NODE *dbPost, const char *sValueName, const AQH_VALUE *value)
{
const char *sValueSystemName;
uint32_t color;
int rv;
sValueSystemName=AQH_Value_GetNameForSystem(value);
DBG_INFO(NULL, "Set value %s", sValueName);
color=AQH_ModDevices_RgbwFromComponents(_getColorComponent(dbPost, sValueName, "r", 0),
_getColorComponent(dbPost, sValueName, "g", 0),
_getColorComponent(dbPost, sValueName, "b", 0),
_getColorComponent(dbPost, sValueName, "w", 0));
DBG_INFO(NULL, "Send value [#%08x] to %s", color, sValueSystemName);
rv=AQH_DataClient_SetData(dc, value, (double) color);
if (rv<0) {
DBG_INFO(NULL, "Error sending data: %d", rv);
}
}
int _getColorComponent(GWEN_DB_NODE *dbPost, const char *sValueName, const char *sComponent, int defaultValue)
{
GWEN_BUFFER *buf;
const char *sData;
int result;
buf=GWEN_Buffer_new(0, 64, 0, 1);
GBAA(buf, "%s_%s", sValueName, sComponent);
DBG_INFO(NULL, "Read value %s", GWEN_Buffer_GetStart(buf));
sData=GWEN_DB_GetCharValue(dbPost, GWEN_Buffer_GetStart(buf), 0, NULL);
GWEN_Buffer_free(buf);
if (sData) {
if (1==sscanf(sData, "%u", &result))
return result;
}
return defaultValue;
}
void _writeOnOffToForm(const char *sValueName, int intVal, GWEN_BUFFER *dbuf)
{
GBAA(dbuf, "<select name=\"%s\" id=\"%s\">" "<option value=\"unchanged\" >unchanged</option>", sValueName, sValueName);
GBAA(dbuf, "<option value=\"off\" %s>off</option>", (intVal==0)?"selected":"");
GBAA(dbuf, "<option value=\"on\" %s>on</option>", (intVal==1)?"selected":"");
GBAS(dbuf, "</select>");
}
void _setOnOffData(AQH_DATACLIENT *dc, const AQH_VALUE *value, const char *sValue)
{
if (sValue) {
const char *sValueSystemName;
int rv;
sValueSystemName=AQH_Value_GetNameForSystem(value);
if (strcasecmp(sValue, "unchanged")==0) {
DBG_INFO(NULL, "Value %s unchanged", sValueSystemName);
}
else if (strcasecmp(sValue, "on")==0) {
DBG_INFO(NULL, "Send value 1 to %s", sValueSystemName);
rv=AQH_DataClient_SetData(dc, value, 1.0);
if (rv<0) {
DBG_INFO(NULL, "Error sending data: %d", rv);
}
}
else if (strcasecmp(sValue, "off")==0) {
DBG_INFO(NULL, "Send value 0 to %s", sValueSystemName);
rv=AQH_DataClient_SetData(dc, value, 0.0);
if (rv<0) {
DBG_INFO(NULL, "Error sending data: %d", rv);
}
}
else {
}
}
}
void _writeOnOffAutoToForm(const char *sValueName, int intVal, GWEN_BUFFER *dbuf)
{
GBAA(dbuf, "<select name=\"%s\" id=\"%s\" >" "<option value=\"unchanged\" >unchanged</option>", sValueName, sValueName);
GBAA(dbuf, "<option value=\"off\" %s>off</option>", (intVal==0)?"selected":"");
GBAA(dbuf, "<option value=\"on\" %s>on</option>", (intVal==1)?"selected":"");
GBAA(dbuf, "<option value=\"auto\" %s>auto</option>", (intVal==2)?"selected":"");
GBAS(dbuf, "</select>");
}
void _setOnOffAutoData(AQH_DATACLIENT *dc, const AQH_VALUE *value, const char *sValue)
{
if (sValue) {
const char *sValueSystemName;
int rv;
sValueSystemName=AQH_Value_GetNameForSystem(value);
if (strcasecmp(sValue, "unchanged")==0) {
DBG_INFO(NULL, "Value %s unchanged", sValueSystemName);
}
else if (strcasecmp(sValue, "on")==0) {
DBG_INFO(NULL, "Send value 1 to %s", sValueSystemName);
rv=AQH_DataClient_SetData(dc, value, 1.0);
if (rv<0) {
DBG_INFO(NULL, "Error sending data: %d", rv);
}
}
else if (strcasecmp(sValue, "off")==0) {
DBG_INFO(NULL, "Send value 0 to %s", sValueSystemName);
rv=AQH_DataClient_SetData(dc, value, 0.0);
if (rv<0) {
DBG_INFO(NULL, "Error sending data: %d", rv);
}
}
else if (strcasecmp(sValue, "auto")==0) {
DBG_INFO(NULL, "Send value 2 to %s", sValueSystemName);
rv=AQH_DataClient_SetData(dc, value, 2.0);
if (rv<0) {
DBG_INFO(NULL, "Error sending data: %d", rv);
}
}
else {
DBG_INFO(NULL, "Invalid value [%s] for %s", sValue, sValueSystemName);
}
}
}
void _sendPageList(AQH_MODULE *m, GWEN_BUFFER *dbuf)
{
GWEN_STRINGLIST *sl;
GBAS(dbuf, "<h1>Page List</h1>\n");
sl=_listPageFiles(m);
if (sl) {
GWEN_STRINGLISTENTRY *se;
GBAS(dbuf,
"<table class=\"datatable\">\n"
"<thead><tr><th>Page</th><th>Title</th></tr></thead>\n"
"<tbody>\n");
se=GWEN_StringList_FirstEntry(sl);
while(se) {
const char *filename;
filename=GWEN_StringListEntry_Data(se);
if (filename && *filename) {
GWEN_XMLNODE *node;
DBG_ERROR(NULL, "Reading file \"%s\"", filename);
node=_readPageFile(filename);
if (node) {
GWEN_XMLNODE *nPage;
nPage=GWEN_XMLNode_FindFirstTag(node, "page", NULL, NULL);
if (nPage) {
const char *sId;
const char *sTitle;
sId=GWEN_XMLNode_GetProperty(nPage, "id", NULL);
sTitle=GWEN_XMLNode_GetProperty(nPage, "title", sId);
if (sId && *sId)
GBAA(dbuf, "<tr><td><a href=\"page.html?page=%s\">%s</a></td><td>%s</td></tr>\n", sId, sId, sTitle);
}
GWEN_XMLNode_free(node);
}
}
se=GWEN_StringListEntry_Next(se);
}
GBAS(dbuf, "</tbody></table>");
GWEN_StringList_free(sl);
}
else {
GBAS(dbuf, "No pages.");
}
}
GWEN_STRINGLIST *_listPageFiles(AQH_MODULE *m)
{
GWEN_BUFFER *fbuf;
AQH_SERVICE *sv;
GWEN_STRINGLIST *sl;
int rv;
sv=AQH_ModService_GetService(m);
fbuf=GWEN_Buffer_new(0, 256, 0, 1);
GBAA(fbuf, "%s%spages", AQH_Service_GetRuntimeFolder(sv), GWEN_DIR_SEPARATOR_S);
sl=GWEN_StringList_new();
rv=GWEN_Directory_GetMatchingFilesRecursively(GWEN_Buffer_GetStart(fbuf), sl, "*.xml");
if (rv<0) {
DBG_INFO(NULL, "Error reading pages (%d)", rv);
GWEN_StringList_free(sl);
GWEN_Buffer_free(fbuf);
return NULL;
}
if (GWEN_StringList_Count(sl)<1) {
GWEN_StringList_free(sl);
GWEN_Buffer_free(fbuf);
return NULL;
}
GWEN_Buffer_free(fbuf);
return sl;
}
GWEN_XMLNODE *_readPage(AQH_MODULE *m, const char *sPageName)
{
GWEN_BUFFER *fbuf;
AQH_SERVICE *sv;
GWEN_XMLNODE *fileNode;
sv=AQH_ModService_GetService(m);
fbuf=GWEN_Buffer_new(0, 256, 0, 1);
GBAA(fbuf, "%s%spages%s", AQH_Service_GetRuntimeFolder(sv), GWEN_DIR_SEPARATOR_S, GWEN_DIR_SEPARATOR_S);
AQH_ModService_EscapeToBuffer(sPageName, fbuf);
GBAS(fbuf, ".xml");
fileNode=_readPageFile(GWEN_Buffer_GetStart(fbuf));
if (fileNode==NULL) {
DBG_INFO(NULL, "here");
GWEN_Buffer_free(fbuf);
return NULL;
}
GWEN_Buffer_free(fbuf);
return fileNode;
}
GWEN_XMLNODE *_readPageFile(const char *sFilename)
{
GWEN_XMLNODE *fileNode;
int rv;
fileNode=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, sFilename);
rv=GWEN_XML_ReadFile(fileNode, sFilename, GWEN_XML_FLAGS_DEFAULT);
if (rv<0) {
DBG_ERROR(NULL, "Error reading \"%s\": %s (%d)", sFilename?sFilename:"<no name>", strerror(errno), errno);
GWEN_XMLNode_free(fileNode);
return NULL;
}
return fileNode;
}
int _layoutFromString(const char *s)
{
if (s && *s) {
if (strcasecmp(s, "none")==0)
return MY_LAYOUT_NONE;
else if (strcasecmp(s, "horizontal")==0)
return MY_LAYOUT_HORIZONTAL;
else if (strcasecmp(s, "vertical")==0)
return MY_LAYOUT_VERTICAL;
}
return MY_LAYOUT_NONE;
}

View File

@@ -0,0 +1,29 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2025 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_CGI_MDEVICES_PAGE_H
#define AQHOME_CGI_MDEVICES_PAGE_H
#include "aqhome-cgi/modules/devices/mdevices.h"
#include "aqhome/aqhome.h"
#include "aqhome/dataclient/client.h"
#include <aqcgi/request.h>
#include <gwenhywfar/buffer.h>
void AQH_ModDevices_RunPageGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf);
void AQH_ModDevices_RunPagePost(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf);
void AQH_ModDevices_RunPageGraph(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf);
#endif

View File

@@ -86,13 +86,13 @@ void AQH_ModDevices_RunSetData(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *se
pbuf=GWEN_Buffer_new(0, 256, 0, 1); pbuf=GWEN_Buffer_new(0, 256, 0, 1);
if (sValueName && *sValueName) { if (sValueName && *sValueName) {
GBAS(pbuf, "Location: /aqbt/devices/value.html?device="); GBAS(pbuf, "Location: value.html?device=");
GWEN_Text_EscapeToBuffer(sDeviceName, pbuf); GWEN_Text_EscapeToBuffer(sDeviceName, pbuf);
GBAS(pbuf, "&value="); GBAS(pbuf, "&value=");
GWEN_Text_EscapeToBuffer(sValueName, pbuf); GWEN_Text_EscapeToBuffer(sValueName, pbuf);
} }
else { else {
GBAS(pbuf, "Location: /aqbt/devices/values.html?device="); GBAS(pbuf, "Location: values.html?device=");
GWEN_Text_EscapeToBuffer(sDeviceName, pbuf); GWEN_Text_EscapeToBuffer(sDeviceName, pbuf);
} }
AQCGI_Request_AddResponseHeaderData(rq, GWEN_Buffer_GetStart(pbuf)); AQCGI_Request_AddResponseHeaderData(rq, GWEN_Buffer_GetStart(pbuf));

View File

@@ -80,7 +80,7 @@ void AQH_ModDevices_RunSetDevice(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *
GWEN_BUFFER *pbuf; GWEN_BUFFER *pbuf;
pbuf=GWEN_Buffer_new(0, 256, 0, 1); pbuf=GWEN_Buffer_new(0, 256, 0, 1);
GBAS(pbuf, "Location: /aqbt/devices/device.html?device="); GBAS(pbuf, "Location: device.html?device=");
GWEN_Text_EscapeToBuffer(sDeviceName, pbuf); GWEN_Text_EscapeToBuffer(sDeviceName, pbuf);
AQCGI_Request_AddResponseHeaderData(rq, GWEN_Buffer_GetStart(pbuf)); AQCGI_Request_AddResponseHeaderData(rq, GWEN_Buffer_GetStart(pbuf));
GWEN_Buffer_free(pbuf); GWEN_Buffer_free(pbuf);

View File

@@ -26,10 +26,6 @@
#include <gwenhywfar/timestamp.h> #include <gwenhywfar/timestamp.h>
#include <gwenhywfar/text.h> #include <gwenhywfar/text.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
/* ------------------------------------------------------------------------------------------------ /* ------------------------------------------------------------------------------------------------
@@ -42,17 +38,6 @@
enum {
VALUEGRAPH_PERIOD_4H=1,
VALUEGRAPH_PERIOD_1D,
VALUEGRAPH_PERIOD_1W,
VALUEGRAPH_PERIOD_1M,
VALUEGRAPH_PERIOD_6M,
VALUEGRAPH_PERIOD_12M,
};
/* ------------------------------------------------------------------------------------------------ /* ------------------------------------------------------------------------------------------------
* vars * vars
* ------------------------------------------------------------------------------------------------ * ------------------------------------------------------------------------------------------------
@@ -97,12 +82,8 @@ static void _createGraph(AQH_DATACLIENT *dc,
const MY_GRAPH_PARAMS *graphParams, const MY_GRAPH_PARAMS *graphParams,
const char *graphTitle, int precision, const char *curveLabel, const char *graphTitle, int precision, const char *curveLabel,
const char *sImgFile, int imgWidth, int imgHeight, uint64_t numDataPoints); const char *sImgFile, int imgWidth, int imgHeight, uint64_t numDataPoints);
static int _fileIsCurrent(const char *sPath, int seconds);
static AQDG_GRAPH *_mkGraphObjectWithTitle(const char *graphTitle, const MY_GRAPH_PARAMS *graphParams, int precision); static AQDG_GRAPH *_mkGraphObjectWithTitle(const char *graphTitle, const MY_GRAPH_PARAMS *graphParams, int precision);
static void _mkPathForValueAndPeriod(AQH_MODULE *m, const AQH_VALUE *v, const MY_GRAPH_PARAMS *graphParams, GWEN_BUFFER *dbuf); static void _mkPathForValueAndPeriod(AQH_MODULE *m, const AQH_VALUE *v, const MY_GRAPH_PARAMS *graphParams, GWEN_BUFFER *dbuf);
static AQDG_GRAPH_DATAPAIR_LIST *_requestDataPairList(AQH_DATACLIENT *dc, const char *valueName,
uint64_t tsBegin, uint64_t tsEnd, uint64_t num);
static AQDG_GRAPH_DATAPAIR_LIST *_createDataPairListFromDataPoints(const uint64_t *dataPoints, uint64_t numValues);
static const MY_GRAPH_PARAMS *_getParamsByName(const char *s); static const MY_GRAPH_PARAMS *_getParamsByName(const char *s);
@@ -167,11 +148,10 @@ void _runGraphValueWithArgs(AQH_MODULE *m,
value=AQH_ModDevices_GetValueForDevice(dc, sDeviceName, sValueName); value=AQH_ModDevices_GetValueForDevice(dc, sDeviceName, sValueName);
if (value) { if (value) {
GWEN_BUFFER *fbuf; GWEN_BUFFER *fbuf;
int rv;
fbuf=GWEN_Buffer_new(0, 256, 0, 1); fbuf=GWEN_Buffer_new(0, 256, 0, 1);
_mkPathForValueAndPeriod(m, value, graphParams, fbuf); _mkPathForValueAndPeriod(m, value, graphParams, fbuf);
if (!_fileIsCurrent(GWEN_Buffer_GetStart(fbuf), graphParams->acceptedAgeInSeconds)) { if (!AQH_ModService_FileIsCurrent(GWEN_Buffer_GetStart(fbuf), graphParams->acceptedAgeInSeconds)) {
DBG_DEBUG(NULL, "Creating graph"); DBG_DEBUG(NULL, "Creating graph");
_createGraph(dc, _createGraph(dc,
value, value,
@@ -184,23 +164,7 @@ void _runGraphValueWithArgs(AQH_MODULE *m,
100000); 100000);
} }
if (1) { AQH_ModService_RespondWithMimeFile(rq, GWEN_Buffer_GetStart(fbuf), "image/png", dbuf);
GWEN_BUFFER *ibuf;
ibuf=GWEN_Buffer_new(0, 1024, 0, 1);
// return file
rv=GWEN_SyncIo_Helper_ReadFile(GWEN_Buffer_GetStart(fbuf), ibuf);
if (rv<0) {
DBG_ERROR(NULL, "Error reading \"%s\" (%d)", GWEN_Buffer_GetStart(fbuf), rv);
}
else {
GWEN_Buffer_Reset(dbuf);
GWEN_Buffer_AppendBytes(dbuf, GWEN_Buffer_GetStart(ibuf), GWEN_Buffer_GetUsedBytes(ibuf));
AQCGI_Request_AddResponseHeaderData(rq, "Content-type: image/png");
AQCGI_Request_AddFlags(rq, AQH_MODSERVICE_RQFLAGS_RAWFILE);
}
GWEN_Buffer_free(ibuf);
}
GWEN_Buffer_free(fbuf); GWEN_Buffer_free(fbuf);
AQH_Value_free(value); AQH_Value_free(value);
@@ -212,25 +176,6 @@ void _runGraphValueWithArgs(AQH_MODULE *m,
int _fileIsCurrent(const char *sPath, int seconds)
{
struct stat sb;
time_t t1;
if (lstat(sPath, &sb)==-1) {
DBG_ERROR(NULL, "Error on lstat(%s): %s (%d)", sPath, strerror(errno), errno);
return 0;
}
t1=time(0);
if ((t1-sb.st_mtime)<(time_t) seconds) {
DBG_DEBUG(NULL, "File %s is current", sPath);
return 1;
}
return 0;
}
void _createGraph(AQH_DATACLIENT *dc, void _createGraph(AQH_DATACLIENT *dc,
const AQH_VALUE *v, const AQH_VALUE *v,
@@ -252,7 +197,7 @@ void _createGraph(AQH_DATACLIENT *dc,
tsBegin=time(0)-(graphParams->startTimeDiff); tsBegin=time(0)-(graphParams->startTimeDiff);
g=_mkGraphObjectWithTitle(graphTitle, graphParams, precision); g=_mkGraphObjectWithTitle(graphTitle, graphParams, precision);
dpList=_requestDataPairList(dc, sValue, tsBegin, tsEnd, numDataPoints); dpList=AQH_ModDevices_RequestDataPairList(dc, sValue, tsBegin, tsEnd, numDataPoints);
if (dpList) { if (dpList) {
DBG_DEBUG(NULL, "Adding data for %s", sValue); DBG_DEBUG(NULL, "Adding data for %s", sValue);
AQDG_TimeGraph_ModifyDataAndAddCurve(g, curveLabel?curveLabel:sValue, graphParams->modifiers, dpList); AQDG_TimeGraph_ModifyDataAndAddCurve(g, curveLabel?curveLabel:sValue, graphParams->modifiers, dpList);
@@ -309,63 +254,13 @@ void _mkPathForValueAndPeriod(AQH_MODULE *m, const AQH_VALUE *v, const MY_GRAPH_
/* var name */ /* var name */
s=AQH_Value_GetNameForSystem(v); s=AQH_Value_GetNameForSystem(v);
GWEN_Text_EscapeToBuffer(s, dbuf); AQH_ModService_EscapeToBuffer(s, dbuf);
GBAA(dbuf, "-%s.png", graphParams->name); GBAA(dbuf, "-%s.png", graphParams->name);
} }
AQDG_GRAPH_DATAPAIR_LIST *_requestDataPairList(AQH_DATACLIENT *dc, const char *valueName,
uint64_t tsBegin, uint64_t tsEnd, uint64_t num)
{
uint64_t *dataPoints;
uint64_t recvdNum;
dataPoints=malloc(num*sizeof(uint64_t)*2);
recvdNum=AQH_DataClient_GetPeriodData(dc, valueName, dataPoints, num, tsBegin, tsEnd);
if (recvdNum>0) {
AQDG_GRAPH_DATAPAIR_LIST *dpList;
dpList=_createDataPairListFromDataPoints(dataPoints, recvdNum);
free(dataPoints);
return dpList;
}
else {
DBG_ERROR(NULL, "No data received for %s", valueName);
free(dataPoints);
return NULL;
}
}
AQDG_GRAPH_DATAPAIR_LIST *_createDataPairListFromDataPoints(const uint64_t *dataPoints, uint64_t numValues)
{
AQDG_GRAPH_DATAPAIR_LIST *dpList;
uint64_t i;
DBG_DEBUG(NULL, "Got %d datapoints", (int) numValues);
dpList=AQDG_Graph_DataPair_List_new();
for(i=0; i<numValues; i++) {
AQDG_GRAPH_DATAPAIR *dp;
double timestamp;
union {double f; uint64_t i;} u;
timestamp=(double)(*(dataPoints++));
u.i=*(dataPoints++);
dp=AQDG_Graph_DataPair_new();
AQDG_Graph_DataPair_SetValueX(dp, timestamp);
AQDG_Graph_DataPair_SetValueY(dp, u.f);
AQDG_Graph_DataPair_List_Add(dp, dpList);
}
AQDG_Graph_DataPair_List_SortByValueX(dpList, 1);
return dpList;
}
const MY_GRAPH_PARAMS *_getParamsByName(const char *s) const MY_GRAPH_PARAMS *_getParamsByName(const char *s)
{ {
const MY_GRAPH_PARAMS *p; const MY_GRAPH_PARAMS *p;

View File

@@ -0,0 +1,12 @@
<?xml?>
<gwbuild>
<data dist="true" install="$(httpdatadir)/aqhome-cgi/html">
style.css
</data>
<subdirs>
pics
</subdirs>
</gwbuild>

View File

@@ -0,0 +1,17 @@
<?xml?>
<gwbuild>
<data dist="true" install="$(httpdatadir)/aqhome-cgi/html/pics">
cancel.png
document-table.png
edit.png
graph.png
minus.png
ok.png
plus.png
user-add.png
user-edit.png
</data>
</gwbuild>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -97,7 +97,7 @@ AQH_MODULE *_loadSubModule(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *sessio
nbuf=GWEN_Buffer_new(0, 256, 0, 1); nbuf=GWEN_Buffer_new(0, 256, 0, 1);
s=AQH_ModService_GetBaseFolder(m); s=AQH_ModService_GetBaseFolder(m);
GWEN_Buffer_AppendArgs(nbuf, "%s/devices", s?s:"."); GWEN_Buffer_AppendArgs(nbuf, "%s/admin", s?s:".");
AQH_ModAdmin_Extend(mSub, AQH_ModService_GetService(m), GWEN_Buffer_GetStart(nbuf)); AQH_ModAdmin_Extend(mSub, AQH_ModService_GetService(m), GWEN_Buffer_GetStart(nbuf));
AQH_Module_Tree2_AddChild(m, mSub); AQH_Module_Tree2_AddChild(m, mSub);

View File

@@ -15,11 +15,12 @@
</head> </head>
<ul class="mainmenu" > <ul class="mainmenu" >
<li><a href="/aqbt/devices/index.html">Devices</a></li> <li><a href="/aqhome/devices/index.html">Devices</a></li>
<li><a href="/aqbt/admin/index.html">Admin</a></li> <li><a href="/aqhome/devices/page.html">Pages</a></li>
<li><a href="/aqhome/admin/index.html">Admin</a></li>
<li><a href="#news">News</a></li> <li><a href="#news">News</a></li>
<li><a href="#contact">Contact</a></li> <li><a href="#contact">Contact</a></li>
<li style="float:right"><a href="/aqbt/login">Login</a></li> <li style="float:right"><a href="/aqhome/login">Login</a></li>
</ul> </ul>
<body> <body>

View File

@@ -1,63 +0,0 @@
body {
background-color: whitesmoke;
}
table.datatable {
border: thin solid;
border-collapse: collapse;
}
table.datatable th, td {
border: thin solid;
border-collapse: collapse;
padding: 5px;
}
table.datatable tbody tr:nth-child(odd) {
background-color: #ffffff;
}
table.formtable {
border: thin solid;
border-collapse: collapse;
}
table.formtable th, td {
border: thin solid;
border-collapse: collapse;
padding: 5px;
}
ul.mainmenu {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #333333;
}
ul.mainmenu li {
float: left;
}
ul.mainmenu li a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
ul.mainmenu li a:hover {
background-color: #111111;
}

View File

@@ -79,7 +79,8 @@ static int _handleNewClient(AQH_OBJECT *o, AQH_OBJECT *clientEndpoint);
static int _handleClientDown(AQH_OBJECT *o, AQH_OBJECT *clientEndpoint); static int _handleClientDown(AQH_OBJECT *o, AQH_OBJECT *clientEndpoint);
static void _handleMsgsFromClient(AQH_OBJECT *o, AQHOME_SERVER *xo, AQH_OBJECT *ep); static void _handleMsgsFromClient(AQH_OBJECT *o, AQHOME_SERVER *xo, AQH_OBJECT *ep);
static void _handleMsgFromClient(AQH_OBJECT *o, AQH_OBJECT *ep, const AQH_MESSAGE *msg); static void _handleMsgFromClient(AQH_OBJECT *o, AQH_OBJECT *ep, const AQH_MESSAGE *msg);
static void _createValue(AQHOME_SERVER *xo, AQH_OBJECT *epDriver, const char *deviceName, const AQH_VALUE *valueTemplate); static void _createValue(AQHOME_SERVER *xo, AQH_OBJECT *epDriver,
const char *deviceName, const char *valueNameForSystem, const AQH_VALUE *valueTemplate);
static void _updateValue(AQHOME_SERVER *xo, AQH_VALUE *v, const char *deviceName, const AQH_VALUE *valueTemplate); static void _updateValue(AQHOME_SERVER *xo, AQH_VALUE *v, const char *deviceName, const AQH_VALUE *valueTemplate);
static AQH_DEVICE *_getOrCreateDeviceForDriver(AQHOME_SERVER *xo, AQH_OBJECT *epDriver, const char *deviceName); static AQH_DEVICE *_getOrCreateDeviceForDriver(AQHOME_SERVER *xo, AQH_OBJECT *epDriver, const char *deviceName);
static int _createPidFile(const char *pidFilename); static int _createPidFile(const char *pidFilename);
@@ -655,7 +656,7 @@ AQH_VALUE *AqHomeDataServer_GetOrCreateValueForDriverWithTemplate(AQH_OBJECT *o,
v=AQH_Storage_GetValueByNameForSystem(xo->storage, GWEN_Buffer_GetStart(buf)); v=AQH_Storage_GetValueByNameForSystem(xo->storage, GWEN_Buffer_GetStart(buf));
if (v==NULL) { if (v==NULL) {
if (AQH_Endpoint_GetPermissions(epDriver) & AQH_ENDPOINT_PERMS_ADDVALUE) if (AQH_Endpoint_GetPermissions(epDriver) & AQH_ENDPOINT_PERMS_ADDVALUE)
_createValue(xo, epDriver, GWEN_Buffer_GetStart(buf), valueTemplate); _createValue(xo, epDriver, deviceName, GWEN_Buffer_GetStart(buf), valueTemplate);
else { else {
DBG_ERROR(AQH_LOGDOMAIN, "No permissions to create value \"%s\"", GWEN_Buffer_GetStart(buf)); DBG_ERROR(AQH_LOGDOMAIN, "No permissions to create value \"%s\"", GWEN_Buffer_GetStart(buf));
GWEN_Buffer_free(buf); GWEN_Buffer_free(buf);
@@ -672,20 +673,25 @@ AQH_VALUE *AqHomeDataServer_GetOrCreateValueForDriverWithTemplate(AQH_OBJECT *o,
void _createValue(AQHOME_SERVER *xo, AQH_OBJECT *epDriver, const char *deviceName, const AQH_VALUE *valueTemplate) void _createValue(AQHOME_SERVER *xo, AQH_OBJECT *epDriver,
const char *deviceName, const char *valueNameForSystem, const AQH_VALUE *valueTemplate)
{ {
AQH_DEVICE *device; AQH_DEVICE *device;
const char *serviceName; const char *serviceName;
const char *valueName;
AQH_VALUE *v; AQH_VALUE *v;
DBG_INFO(AQH_LOGDOMAIN, "Creating value \"%s\"", deviceName);
serviceName=AQH_Endpoint_GetServiceName(epDriver); serviceName=AQH_Endpoint_GetServiceName(epDriver);
valueName=AQH_Value_GetName(valueTemplate);
device=(deviceName && *deviceName)?_getOrCreateDeviceForDriver(xo, epDriver, deviceName):NULL; device=(deviceName && *deviceName)?_getOrCreateDeviceForDriver(xo, epDriver, deviceName):NULL;
DBG_ERROR(AQH_LOGDOMAIN,
"Creating value: service=\"%s\", device=\"%s\", value \"%s\"",
serviceName?serviceName:"<no service>", deviceName?deviceName:"<no device>", valueName?valueName:"<no value>");
v=AQH_Value_new(); v=AQH_Value_new();
AQH_Value_SetDriver(v, serviceName); AQH_Value_SetDriver(v, serviceName);
AQH_Value_SetName(v, AQH_Value_GetName(valueTemplate)); AQH_Value_SetName(v, AQH_Value_GetName(valueTemplate));
AQH_Value_SetNameForSystem(v, deviceName); AQH_Value_SetNameForSystem(v, valueNameForSystem);
AQH_Value_SetValueUnits(v, AQH_Value_GetValueUnits(valueTemplate)); AQH_Value_SetValueUnits(v, AQH_Value_GetValueUnits(valueTemplate));
AQH_Value_SetValueType(v, AQH_Value_GetValueType(valueTemplate)); AQH_Value_SetValueType(v, AQH_Value_GetValueType(valueTemplate));
AQH_Value_SetModality(v, AQH_Value_GetModality(valueTemplate)); AQH_Value_SetModality(v, AQH_Value_GetModality(valueTemplate));
@@ -755,7 +761,7 @@ AQH_DEVICE *_getOrCreateDeviceForDriver(AQHOME_SERVER *xo, AQH_OBJECT *epDriver,
device=AQH_Storage_GetDeviceByNameForSystem(xo->storage, GWEN_Buffer_GetStart(buf)); device=AQH_Storage_GetDeviceByNameForSystem(xo->storage, GWEN_Buffer_GetStart(buf));
if (device==NULL) { if (device==NULL) {
if (AQH_Endpoint_GetPermissions(epDriver) & AQH_ENDPOINT_PERMS_ADDDEVICE) { if (AQH_Endpoint_GetPermissions(epDriver) & AQH_ENDPOINT_PERMS_ADDDEVICE) {
DBG_INFO(AQH_LOGDOMAIN, "Creating device \"%s\"", GWEN_Buffer_GetStart(buf)); DBG_ERROR(AQH_LOGDOMAIN, "Creating device \"%s\"", GWEN_Buffer_GetStart(buf));
device=AQH_Device_new(); device=AQH_Device_new();
AQH_Device_SetDriver(device, serviceName); AQH_Device_SetDriver(device, serviceName);
AQH_Device_SetName(device, deviceName); AQH_Device_SetName(device, deviceName);

View File

@@ -1 +0,0 @@
test/

View File

@@ -1,107 +0,0 @@
<?xml?>
<gwbuild>
<target type="Program" name="aqhome-storage" install="$(bindir)" >
<includes type="c" >
$(gwenhywfar_cflags)
-I$(topsrcdir)
-I$(topbuilddir)
-I$(builddir)
-I$(srcdir)
</includes>
<includes type="tm2" >
--include=$(builddir)
--include=$(srcdir)
</includes>
<setVar name="local/cflags">$(visibility_cflags)</setVar>
<setVar name="tm2flags" >
</setVar>
<setVar name="local/typefiles" >
</setVar>
<setVar name="local/built_sources" >
</setVar>
<setVar name="local/built_headers_pub">
</setVar>
<setVar name="local/built_headers_priv" >
</setVar>
<headers dist="true" >
aqhomestorage_p.h
aqhomestorage.h
init.h
init_http.h
init_mqtt.h
fini.h
loop.h
loop_http.h
loop_mqtt.h
cleanup.h
u_base.h
u_login.h
u_objects.h
u_objects_p.h
u_rooms.h
u_devices.h
u_mqtttopics.h
u_values.h
u_static.h
u_static_p.h
aqhomehttp.h
aqhomehttp_p.h
</headers>
<sources>
$(local/typefiles)
aqhomestorage.c
init.c
init_http.c
init_mqtt.c
fini.c
loop.c
loop_http.c
loop_mqtt.c
cleanup.c
u_base.c
u_login.c
u_objects.c
u_rooms.c
u_devices.c
u_mqtttopics.c
u_values.c
u_static.c
main.c
aqhomehttp.c
</sources>
<useTargets>
aqhome
</useTargets>
<libraries>
$(gwenhywfar_libs)
</libraries>
<subdirs>
</subdirs>
<extradist>
</extradist>
</target>
</gwbuild>

View File

@@ -1,68 +0,0 @@
TODO
- isolate storage service:
- remove http service from here
- add ipc service
- admin:
- add room/device/MQTT topic/value
- edit room/device/MQTT topic/value
- del room/device/MQTT topic/value
- get received topics
- list rooms/devices/MQTT topics/values
- getValues(valueId, timeFrom, timeUntil)
- addValue(valueId/valueName, timeStamp, value)
- aqhome-tool
- add ipc admin code to connect to ipc service
- create http service as stand-alone app or create PHP code which uses aqhome-tool
- connect to storage service for information/admin
- move http service into own folder
- isolate functions:
- getRoomList
- getDeviceList
- getTopicList
- addRoom/Device/Topic/Value
- delRoom/Device/Topic/Value
- editRoom/Device/Topic/Value
- aqhome-storage->aqhome-data
- aqhome-data
- only list of values
- manages datafiles only
- IPC (later secure ipc):
- get value list
- add value
- del value
- edit value
- add datapoint(valueId, timestamp, data)
- add datapoint(valueName, timestamp, data)
- getData(valueId, timeFrom, timeUntil)
- getLastData(valueId)
- aqhome-tool:
- add ipc code
- aqhome-mqttdata:
- use devices, topics and values from aqhome-storage
- derive valueid and data from mqtt messages
- send data to aqhome-data via ipc (later secure ipc)
- mqtt values:
- aliases (e.g. for doors: open=1.0, closed=0.0, tilted=0.5)
- later services only:
- aqhomed (tty, ipcd)
- aqhome-data (ipcd)
- aqhome-mqtt (mqttc, ipcd)
- aqhome-httpd (maybe; httpd, ipcc)

View File

@@ -1,191 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./aqhomehttp_p.h"
#include "aqhome/http/httpservice.h"
#include "aqhome/http/httpservice_http.h"
#include <gwenhywfar/db.h>
#include <gwenhywfar/buffer.h>
#include <gwenhywfar/text.h>
#include <gwenhywfar/debug.h>
GWEN_INHERIT(AQH_SERVICE, AQHOME_HTTP)
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static void GWENHYWFAR_CB _freeData(void *bp, void *p);
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
void AqHomeHttpService_Extend(AQH_SERVICE *sv)
{
AQHOME_HTTP *xsv;
GWEN_NEW_OBJECT(AQHOME_HTTP, xsv);
GWEN_INHERIT_SETDATA(AQH_SERVICE, AQHOME_HTTP, sv, xsv, _freeData);
xsv->contentTree=AQH_HttpContent_new("root");
xsv->storageMutex=GWEN_Mutex_new();
}
void _freeData(void *bp, void *p)
{
AQHOME_HTTP *xsv;
xsv=(AQHOME_HTTP*) p;
xsv->storage=NULL;
AQH_HttpContent_free(xsv->contentTree);
xsv->contentTree=NULL;
GWEN_Mutex_free(xsv->storageMutex);
GWEN_FREE_OBJECT(xsv);
}
AQH_STORAGE *AqHomeHttpService_GetStorage(const AQH_SERVICE *sv)
{
if (sv) {
AQHOME_HTTP *xsv;
xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQHOME_HTTP, sv);
if (xsv)
return xsv->storage;
}
return NULL;
}
void AqHomeHttpService_SetStorage(AQH_SERVICE *sv, AQH_STORAGE *sto)
{
if (sv) {
AQHOME_HTTP *xsv;
xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQHOME_HTTP, sv);
if (xsv)
xsv->storage=sto;
}
}
AQH_HTTP_CONTENT *AqHomeHttpService_GetContentTree(const AQH_SERVICE *sv)
{
if (sv) {
AQHOME_HTTP *xsv;
xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQHOME_HTTP, sv);
if (xsv)
return xsv->contentTree;
}
return NULL;
}
void AqHomeHttpService_SetContentTree(AQH_SERVICE *sv, AQH_HTTP_CONTENT *c)
{
if (sv) {
AQHOME_HTTP *xsv;
xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQHOME_HTTP, sv);
if (xsv) {
AQH_HttpContent_free(xsv->contentTree);
xsv->contentTree=c;
}
}
}
void AqHomeHttpService_MarkStorageChanged(AQH_SERVICE *sv)
{
if (sv) {
AQHOME_HTTP *xsv;
xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQHOME_HTTP, sv);
if (xsv) {
AQH_Storage_AddRuntimeFlags(xsv->storage, AQH_STORAGE_RTFLAGS_MODIFIED);
}
}
}
int AqHomeHttpService_LockStorage(AQH_SERVICE *sv)
{
if (sv) {
AQHOME_HTTP *xsv;
xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQHOME_HTTP, sv);
if (xsv) {
int rv;
rv=GWEN_Mutex_Lock(xsv->storageMutex);
if (rv<0) {
DBG_ERROR(AQH_LOGDOMAIN, "Error obtaining lock on storage mutex");
return rv;
}
return 0;
}
}
return GWEN_ERROR_GENERIC;
}
int AqHomeHttpService_UnlockStorage(AQH_SERVICE *sv)
{
if (sv) {
AQHOME_HTTP *xsv;
xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQHOME_HTTP, sv);
if (xsv) {
int rv;
rv=GWEN_Mutex_Unlock(xsv->storageMutex);
if (rv<0) {
DBG_ERROR(AQH_LOGDOMAIN, "Error releasing lock on storage mutex");
return rv;
}
return 0;
}
}
return GWEN_ERROR_GENERIC;
}

View File

@@ -1,59 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_HTTP_H
#define AQHOME_HTTP_H
#include "aqhome/service/service.h"
#include "aqhome/data/storage.h"
#include "aqhome/http/content.h"
#include <gwenhywfar/endpoint.h>
#define AQHOME_HTTP_PERMS_LIST_ROOMS 0x00000001
#define AQHOME_HTTP_PERMS_LIST_DEVICES 0x00000002
#define AQHOME_HTTP_PERMS_LIST_VALUES 0x00000004
#define AQHOME_HTTP_PERMS_LIST_TOPICS 0x00000008
#define AQHOME_HTTP_PERMS_ADD_ROOM 0x00000100
#define AQHOME_HTTP_PERMS_ADD_DEVICE 0x00000200
#define AQHOME_HTTP_PERMS_ADD_VALUE 0x00000400
#define AQHOME_HTTP_PERMS_ADD_TOPIC 0x00000800
#define AQHOME_HTTP_PERMS_DEL_ROOM 0x00010000
#define AQHOME_HTTP_PERMS_DEL_DEVICE 0x00020000
#define AQHOME_HTTP_PERMS_DEL_VALUE 0x00040000
#define AQHOME_HTTP_PERMS_DEL_TOPIC 0x00080000
#define AQHOME_HTTP_PERMS_EDIT_ROOM 0x01000000
#define AQHOME_HTTP_PERMS_EDIT_DEVICE 0x02000000
#define AQHOME_HTTP_PERMS_EDIT_VALUE 0x04000000
#define AQHOME_HTTP_PERMS_EDIT_TOPIC 0x08000000
void AqHomeHttpService_Extend(AQH_SERVICE *sv);
AQH_STORAGE *AqHomeHttpService_GetStorage(const AQH_SERVICE *sv);
void AqHomeHttpService_SetStorage(AQH_SERVICE *sv, AQH_STORAGE *sto);
AQH_HTTP_CONTENT *AqHomeHttpService_GetContentTree(const AQH_SERVICE *sv);
void AqHomeHttpService_SetContentTree(AQH_SERVICE *sv, AQH_HTTP_CONTENT *c);
int AqHomeHttpService_LockStorage(AQH_SERVICE *sv);
int AqHomeHttpService_UnlockStorage(AQH_SERVICE *sv);
void AqHomeHttpService_MarkStorageChanged(AQH_SERVICE *sv);
#endif

View File

@@ -1,31 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_HTTP_P_H
#define AQHOME_HTTP_P_H
#include "./aqhomehttp.h"
#include <gwenhywfar/mutex.h>
typedef struct AQHOME_HTTP AQHOME_HTTP;
struct AQHOME_HTTP {
AQH_STORAGE *storage; /* do not release */
GWEN_MUTEX *storageMutex;
AQH_HTTP_CONTENT *contentTree;
};
#endif

View File

@@ -1,126 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./aqhomestorage_p.h"
#include <gwenhywfar/misc.h>
#include <gwenhywfar/debug.h>
AQHOME_STORAGE *AqHomeStorage_new()
{
AQHOME_STORAGE *aqh;
GWEN_NEW_OBJECT(AQHOME_STORAGE, aqh);
aqh->rootEndpoint=GWEN_MsgEndpoint_new("root", 0);
return aqh;
}
void AqHomeStorage_free(AQHOME_STORAGE *aqh)
{
if (aqh) {
AQH_Service_free(aqh->httpService);
aqh->httpService=NULL;
AQH_Storage_free(aqh->storage);
aqh->storage=NULL;
GWEN_MsgEndpoint_free(aqh->rootEndpoint);
aqh->rootEndpoint=NULL;
aqh->ipcdEndpoint=NULL;
aqh->mqttEndpoint=NULL;
aqh->httpdEndpoint=NULL;
GWEN_DB_Group_free(aqh->dbArgs);
aqh->dbArgs=NULL;
free(aqh->pidFile);
GWEN_FREE_OBJECT(aqh);
}
}
GWEN_MSG_ENDPOINT *AqHomeStorage_GetRootEndpoint(const AQHOME_STORAGE *aqh)
{
return aqh?(aqh->rootEndpoint):NULL;
}
GWEN_MSG_ENDPOINT *AqHomeStorage_GetIpcdEndpoint(const AQHOME_STORAGE *aqh)
{
return aqh?(aqh->ipcdEndpoint):NULL;
}
GWEN_MSG_ENDPOINT *AqHomeStorage_GetMqttEndpoint(const AQHOME_STORAGE *aqh)
{
return aqh?(aqh->mqttEndpoint):NULL;
}
GWEN_MSG_ENDPOINT *AqHomeStorage_GetHttpdEndpoint(const AQHOME_STORAGE *aqh)
{
return aqh?(aqh->httpdEndpoint):NULL;
}
GWEN_DB_NODE *AqHomeStorage_GetDbArgs(const AQHOME_STORAGE *aqh)
{
return aqh?(aqh->dbArgs):NULL;
}
AQH_STORAGE *AqHomeStorage_GetStorage(const AQHOME_STORAGE *aqh)
{
return aqh?(aqh->storage):NULL;
}
const char *AqHomeStorage_GetPidFile(const AQHOME_STORAGE *aqh)
{
return aqh?aqh->pidFile:NULL;
}
void AqHomeStorage_SetPidFile(AQHOME_STORAGE *aqh, const char *s)
{
if (aqh) {
free(aqh->pidFile);
aqh->pidFile=s?strdup(s):NULL;
}
}
int AqHomeStorage_GetTimeout(const AQHOME_STORAGE *aqh)
{
return aqh?aqh->timeout:0;
}

View File

@@ -1,42 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_H
#define AQHOME_STORAGE_H
#include "aqhome/data/storage.h"
#include "aqhome/service/session.h"
#include "aqhome/http/httpservice.h"
#include <gwenhywfar/endpoint.h>
typedef struct AQHOME_STORAGE AQHOME_STORAGE;
AQHOME_STORAGE *AqHomeStorage_new();
void AqHomeStorage_free(AQHOME_STORAGE *aqh);
GWEN_MSG_ENDPOINT *AqHomeStorage_GetRootEndpoint(const AQHOME_STORAGE *aqh);
GWEN_MSG_ENDPOINT *AqHomeStorage_GetIpcdEndpoint(const AQHOME_STORAGE *aqh);
GWEN_MSG_ENDPOINT *AqHomeStorage_GetMqttEndpoint(const AQHOME_STORAGE *aqh);
GWEN_MSG_ENDPOINT *AqHomeStorage_GetHttpdEndpoint(const AQHOME_STORAGE *aqh);
GWEN_DB_NODE *AqHomeStorage_GetDbArgs(const AQHOME_STORAGE *aqh);
AQH_STORAGE *AqHomeStorage_GetStorage(const AQHOME_STORAGE *aqh);
const char *AqHomeStorage_GetPidFile(const AQHOME_STORAGE *aqh);
void AqHomeStorage_SetPidFile(AQHOME_STORAGE *aqh, const char *s);
int AqHomeStorage_GetTimeout(const AQHOME_STORAGE *aqh);
#endif

View File

@@ -1,59 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_P_H
#define AQHOME_STORAGE_P_H
#include "./aqhomestorage.h"
/* default values */
#define AQHOME_STORAGE_DEFAULT_PIDFILE "/var/run/aqhome-storage.pid"
#define AQHOME_STORAGE_DEFAULT_IPC_PORT 45455
#define AQHOME_STORAGE_DEFAULT_HTTP_PORT 45456
#define AQHOME_STORAGE_DEFAULT_MQTT_CLIENTID "AQHOMESTORAGE"
#define AQHOME_STORAGE_DEFAULT_MQTT_KEEPALIVE 600
#define AQHOME_STORAGE_DEFAULT_MQTT_PORT 1883
#define AQHOME_STORAGE_DEFAULT_CONFIGDIR "/var/lib/aqhomestorage/config"
#define AQHOME_STORAGE_DEFAULT_HTTP_SOURCEDIR "/var/lib/aqhomestorage/html"
#define AQHOME_STORAGE_DEFAULT_DATADIR "/var/lib/aqhomestorage/data"
#define AQHOME_STORAGE_DEFAULT_STATEFILE "/var/lib/aqhomestorage/config/statefile"
#define AQHOME_STORAGE_DEFAULT_MAXSESSIONAGE (30*60)
#define AQHOME_STORAGE_SITEHEADER "site-header.html"
#define AQHOME_STORAGE_SITEFOOTER "site-footer.html"
#define AQHOME_STORAGE_STATIC_RELFOLDER "static"
struct AQHOME_STORAGE {
GWEN_MSG_ENDPOINT *rootEndpoint;
GWEN_MSG_ENDPOINT *ipcdEndpoint; /* don't release, will be released by freeing rootEndpoint! */
GWEN_MSG_ENDPOINT *mqttEndpoint; /* don't release, will be released by freeing rootEndpoint! */
GWEN_MSG_ENDPOINT *httpdEndpoint; /* don't release, will be released by freeing rootEndpoint! */
GWEN_DB_NODE *dbArgs;
AQH_SERVICE *httpService;
AQH_STORAGE *storage;
char *pidFile;
int maxSessionAgeInSeconds;
int timeout; /* timeout for run e.g. inside valgrind */
};
#endif

View File

@@ -1,61 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./cleanup.h"
#include "./aqhomehttp.h"
#include "./aqhomestorage_p.h"
#include "aqhome/http/httpservice_conf.h"
#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/args.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/endpoint.h>
/* ------------------------------------------------------------------------------------------------
* defines
* ------------------------------------------------------------------------------------------------
*/
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
void AqHomeStorage_Cleanup(AQHOME_STORAGE *aqh)
{
int rv;
rv=AQH_HttpService_CleanupSessions(aqh->httpService, aqh->maxSessionAgeInSeconds);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
}
}

View File

@@ -1,25 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_CLEANUP_H
#define AQHOME_STORAGE_CLEANUP_H
#include "./aqhomestorage.h"
void AqHomeStorage_Cleanup(AQHOME_STORAGE *aqh);
#endif

View File

@@ -1,83 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./fini.h"
#include "./aqhomestorage_p.h"
#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/args.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/endpoint_tcpd.h>
#include <unistd.h>
/* ------------------------------------------------------------------------------------------------
* defines
* ------------------------------------------------------------------------------------------------
*/
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static void _disconnectTree(GWEN_MSG_ENDPOINT *ep);
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
void AqHomeStorage_Fini(AQHOME_STORAGE *aqh)
{
if (aqh) {
if (aqh->rootEndpoint) {
_disconnectTree(aqh->rootEndpoint);
GWEN_MsgEndpoint_Disconnect(aqh->rootEndpoint);
}
GWEN_MsgEndpoint_free(aqh->rootEndpoint);
aqh->rootEndpoint=NULL;
aqh->ipcdEndpoint=NULL;
aqh->httpdEndpoint=NULL;
aqh->mqttEndpoint=NULL;
if (aqh->pidFile)
remove(aqh->pidFile);
}
}
void _disconnectTree(GWEN_MSG_ENDPOINT *ep)
{
GWEN_MSG_ENDPOINT *epChild;
epChild=GWEN_MsgEndpoint_Tree2_GetFirstChild(ep);
while(epChild) {
_disconnectTree(epChild);
epChild=GWEN_MsgEndpoint_Tree2_GetNext(epChild);
} /* while */
GWEN_MsgEndpoint_Disconnect(ep);
}

View File

@@ -1,23 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_FINI_H
#define AQHOME_STORAGE_FINI_H
#include "./aqhomestorage.h"
void AqHomeStorage_Fini(AQHOME_STORAGE *aqh);
#endif

View File

@@ -1,5 +0,0 @@
</body>
</html>

View File

@@ -1,16 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<!-- copyright (c) 2023 by martin -->
<meta name="generator" content="FTE 1.1" />
<meta name="revised" content="martin,2023-07-23" />
<meta name="keywords" content="" />
<meta name="description" content="" />
<meta name="author" content="martin" />
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />
<title>AqHome Storage Service</title>
</head>
<body>

View File

@@ -1,506 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./init.h"
#include "./init_http.h"
#include "./init_mqtt.h"
#include "./aqhomestorage_p.h"
#include "./aqhomehttp.h"
#include "aqhome/msg/endpoint_tty.h"
#include "aqhome/ipc/endpoint_ipc.h"
#include "aqhome/mqtt/endpoint_mqttc.h"
#include "aqhome/http/endpoint_http.h"
#include "aqhome/http/httpservice_conf.h"
#include "aqhome/http/httpservice_http.h"
#include "aqhome/http/httpservice.h"
#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/args.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/endpoint_tcpd.h>
#include <gwenhywfar/endpoint_msgio.h>
#include <gwenhywfar/directory.h>
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <time.h>
/* ------------------------------------------------------------------------------------------------
* defines
* ------------------------------------------------------------------------------------------------
*/
//#define I18N(msg) msg
#define I18S(msg) msg
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static int _setupFolders(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs);
static int _setupStorage(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs);
static void _setupIpc(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs);
static GWEN_MSG_ENDPOINT *_acceptIpcFn(GWEN_MSG_ENDPOINT *ep, GWEN_SOCKET *sk, const GWEN_INETADDRESS *addr, void *data);
static int _readArgs(int argc, char **argv, GWEN_DB_NODE *dbArgs);
static int _createPidFile(const char *pidFilename);
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
int AqHomeStorage_Init(AQHOME_STORAGE *aqh, int argc, char **argv)
{
GWEN_DB_NODE *dbArgs;
int rv;
const char *s;
dbArgs=GWEN_DB_Group_new("args");
rv=_readArgs(argc, argv, dbArgs);
if (rv<0) {
DBG_ERROR(NULL, "Error reading args (%d)", rv);
return rv;
}
aqh->dbArgs=dbArgs;
aqh->maxSessionAgeInSeconds=GWEN_DB_GetIntValue(dbArgs, "maxSessionAge", 0, AQHOME_STORAGE_DEFAULT_MAXSESSIONAGE);
aqh->timeout=GWEN_DB_GetIntValue(dbArgs, "timeout", 0, 0);
s=GWEN_DB_GetCharValue(dbArgs, "pidfile", 0, AQHOME_STORAGE_DEFAULT_PIDFILE);
if (s && *s) {
AqHomeStorage_SetPidFile(aqh, s);
rv=_createPidFile(s);
if (rv<0) {
DBG_ERROR(NULL, "Error creating PID file (%d)", rv);
return rv;
}
}
rv=_setupFolders(aqh, dbArgs);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
rv=_setupStorage(aqh, dbArgs);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
_setupIpc(aqh, dbArgs);
rv=AqHomeStorage_SetupMqtt(aqh, dbArgs);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
rv=AqHomeStorage_SetupHttp(aqh, dbArgs);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
return 0;
}
int _setupFolders(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs)
{
const char *s;
GWEN_BUFFER *nameBuf;
int pos;
int rv;
s=GWEN_DB_GetCharValue(dbArgs, "cfgdir", 0, AQHOME_STORAGE_DEFAULT_CONFIGDIR);
if (!(s && *s)) {
DBG_ERROR(NULL, "Missing configuration folder");
return GWEN_ERROR_GENERIC;
}
nameBuf=GWEN_Buffer_new(0, 256, 0, 1);
GWEN_Buffer_AppendString(nameBuf, s);
pos=GWEN_Buffer_GetPos(nameBuf);
rv=GWEN_Directory_GetPath(GWEN_Buffer_GetStart(nameBuf), GWEN_PATH_FLAGS_CHECKROOT);
if (rv<0) {
DBG_ERROR(NULL, "Error accessing configuration folder \"%s\"", GWEN_Buffer_GetStart(nameBuf));
GWEN_Buffer_free(nameBuf);
return GWEN_ERROR_GENERIC;
}
GWEN_Buffer_Crop(nameBuf, 0, pos);
GWEN_Buffer_AppendString(nameBuf, GWEN_DIR_SEPARATOR_S "modules");
rv=GWEN_Directory_GetPath(GWEN_Buffer_GetStart(nameBuf), GWEN_PATH_FLAGS_CHECKROOT);
if (rv<0) {
DBG_ERROR(NULL, "Error accessing modules folder \"%s\"", GWEN_Buffer_GetStart(nameBuf));
GWEN_Buffer_free(nameBuf);
return GWEN_ERROR_GENERIC;
}
GWEN_Buffer_Crop(nameBuf, 0, pos);
GWEN_Buffer_AppendString(nameBuf, GWEN_DIR_SEPARATOR_S "users");
rv=GWEN_Directory_GetPath(GWEN_Buffer_GetStart(nameBuf), GWEN_PATH_FLAGS_CHECKROOT);
if (rv<0) {
DBG_ERROR(NULL, "Error accessing configuration folder \"%s\"", GWEN_Buffer_GetStart(nameBuf));
GWEN_Buffer_free(nameBuf);
return GWEN_ERROR_GENERIC;
}
GWEN_Buffer_free(nameBuf);
return 0;
}
int _setupStorage(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs)
{
const char *dataFolder;
const char *stateFile;
dataFolder=GWEN_DB_GetCharValue(dbArgs, "dataFolder", 0, AQHOME_STORAGE_DEFAULT_DATADIR);
stateFile=GWEN_DB_GetCharValue(dbArgs, "stateFile", 0, NULL);
if (stateFile && *stateFile) {
AQH_STORAGE *sto;
int rv;
sto=AQH_Storage_new();
AQH_Storage_SetStateFile(sto, stateFile);
AQH_Storage_SetDataFileFolder(sto, (dataFolder && *dataFolder)?dataFolder:NULL);
rv=AQH_Storage_Init(sto);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
AQH_Storage_free(sto);
return rv;
}
aqh->storage=sto;
}
else {
DBG_ERROR(NULL, "No state file given");
return GWEN_ERROR_GENERIC;
}
return 0;
}
void _setupIpc(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs)
{
const char *tcpAddress;
int tcpPort;
tcpAddress=GWEN_DB_GetCharValue(dbArgs, "tcpAddress", 0, NULL);
tcpPort=GWEN_DB_GetIntValue(dbArgs, "tcpPort", 0, AQHOME_STORAGE_DEFAULT_IPC_PORT);
if (tcpAddress && *tcpAddress && tcpPort) {
GWEN_MSG_ENDPOINT *ep;
ep=GWEN_TcpdEndpoint_new(tcpAddress, tcpPort, NULL, 0);
GWEN_TcpdEndpoint_SetAcceptFn(ep, _acceptIpcFn, aqh);
GWEN_MsgEndpoint_Tree2_AddChild(aqh->rootEndpoint, ep);
aqh->ipcdEndpoint=ep;
}
}
GWEN_MSG_ENDPOINT *_acceptIpcFn(GWEN_MSG_ENDPOINT *ep,
GWEN_SOCKET *sk,
const GWEN_INETADDRESS *addr,
GWEN_UNUSED void *data)
{
/* AQHOME_STORAGE *aqh;
*
* aqh=(AQHOME_STORAGE*) data;
*/
DBG_INFO(NULL, "Incoming IPC connection");
return AQH_IpcEndpoint_CreateIpcTcpServiceForSocket(sk, NULL, 0);
}
int _createPidFile(const char *pidFilename)
{
FILE *f;
int pidfd;
if (remove(pidFilename)==0) {
DBG_ERROR(0, "Old PID file existed, removed. (Unclean shutdown?)");
}
#ifdef HAVE_SYS_STAT_H
pidfd = open(pidFilename, O_EXCL|O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
if (pidfd < 0) {
DBG_ERROR(NULL, "Could not create PID file \"%s\" (%s), aborting.", pidFilename, strerror(errno));
return GWEN_ERROR_IO;
}
f = fdopen(pidfd, "w");
#else /* HAVE_STAT_H */
f=fopen(pidFilename,"w+");
#endif /* HAVE_STAT_H */
/* write pid */
#ifdef HAVE_GETPID
fprintf(f,"%d\n",getpid());
#else
fprintf(f,"-1\n");
#endif
if (fclose(f)) {
DBG_ERROR(0, "Could not close PID file \"%s\" (%s), aborting.", pidFilename, strerror(errno));
return GWEN_ERROR_IO;
}
return 0;
}
int _readArgs(int argc, char **argv, GWEN_DB_NODE *dbArgs)
{
int rv;
const GWEN_ARGS args[]= {
{
GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
GWEN_ArgsType_Char, /* type */
"cfgdir", /* name */
0, /* minnum */
1, /* maxnum */
"D", /* short option */
"cfgdir", /* long option */
I18S("Specify the configuration folder"),
I18S("Specify the configuration folder")
},
{
GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
GWEN_ArgsType_Char, /* type */
"tcpAddress", /* name */
0, /* minnum */
1, /* maxnum */
"t", /* short option */
"tcpaddress", /* long option */
I18S("Specify the TCP address to listen on (disabled if missing)"),
I18S("Specify the TCP address to listen on (disabled if missing)")
},
{
GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
GWEN_ArgsType_Int, /* type */
"tcpPort", /* name */
0, /* minnum */
1, /* maxnum */
"P", /* short option */
"tcpport", /* long option */
I18S("Specify the TCP port to listen on"),
I18S("Specify the TCP port to listen on")
},
{
GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
GWEN_ArgsType_Char, /* type */
"mqttAddress", /* name */
0, /* minnum */
1, /* maxnum */
"ma", /* short option */
"mqttaddress", /* long option */
I18S("Specify the address of the MQTT server to connect to (disabled if missing)"),
I18S("Specify the address of the MQTT server to connect to (disabled if missing)")
},
{
GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
GWEN_ArgsType_Int, /* type */
"mqttPort", /* name */
0, /* minnum */
1, /* maxnum */
"mp", /* short option */
"mqttport", /* long option */
I18S("Specify the port of the MQTT server (default: 1883)"),
I18S("Specify the port of the MQTT server (default: 1883)")
},
{
GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
GWEN_ArgsType_Char, /* type */
"mqttClientId", /* name */
0, /* minnum */
1, /* maxnum */
NULL, /* short option */
"mqttclientid", /* long option */
I18S("Specify client id for the MQTT server (default: \"AQHOMESTORAGE\")"),
I18S("Specify client id for the MQTT server (default: \"AQHOMESTORAGE\")")
},
{
GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
GWEN_ArgsType_Int, /* type */
"mqttKeepAlive", /* name */
0, /* minnum */
1, /* maxnum */
"mk", /* short option */
"mqttkeepalive", /* long option */
I18S("Specify keepalive time in seconds (defaults: 600)"),
I18S("Specify keepalive time in seconds (defaults: 600)")
},
{
GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
GWEN_ArgsType_Char, /* type */
"httpAddress", /* name */
0, /* minnum */
1, /* maxnum */
"ha", /* short option */
"httpaddress", /* long option */
I18S("Specify the address to bind the http service to (disabled if missing)"),
I18S("Specify the address to bind the http service to (disabled if missing)")
},
{
GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
GWEN_ArgsType_Int, /* type */
"httpPort", /* name */
0, /* minnum */
1, /* maxnum */
"hp", /* short option */
"httpport", /* long option */
I18S("Specify the port to listen on for HTTP connections"),
I18S("Specify the port to listen on for HTTP connections")
},
{
GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
GWEN_ArgsType_Int, /* type */
"maxSessionAge", /* name */
0, /* minnum */
1, /* maxnum */
NULL, /* short option */
"maxsessionage", /* long option */
I18S("Specify maximum session age in seconds (default: 30mins)"),
I18S("Specify maximum session age in seconds (default: 30mins)")
},
{
GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
GWEN_ArgsType_Char, /* type */
"sourcefolder", /* name */
0, /* minnum */
1, /* maxnum */
NULL, /* short option */
"sourcefolder", /* long option */
I18S("Folder where static HTML source files are stored"),
I18S("Folder where static HTML source files are stored")
},
{
GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
GWEN_ArgsType_Char, /* type */
"datafolder", /* name */
0, /* minnum */
1, /* maxnum */
NULL, /* short option */
"datafolder", /* long option */
I18S("Folder where data files are stored"),
I18S("Folder where data files are stored")
},
{
GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
GWEN_ArgsType_Char, /* type */
"statefile", /* name */
0, /* minnum */
1, /* maxnum */
"S", /* short option */
"statefile", /* long option */
I18S("File where rooms, devices and values etc. are stored"),
I18S("File where rooms, devices and values etc. are stored")
},
{
GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
GWEN_ArgsType_Char, /* type */
"pidfile", /* name */
0, /* minnum */
1, /* maxnum */
"p", /* short option */
"pidfile", /* long option */
I18S("Specify the PID file"),
I18S("Specify the PID file")
},
{
GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
GWEN_ArgsType_Int, /* type */
"timeout", /* name */
0, /* minnum */
1, /* maxnum */
"T", /* short option */
"timeout", /* long option */
I18S("Specify timeout in second (default: no timeout)"),
I18S("Specify timeout in second (default: no timeout)")
},
{
GWEN_ARGS_FLAGS_HELP | GWEN_ARGS_FLAGS_LAST, /* flags */
GWEN_ArgsType_Int, /* type */
"help", /* name */
0, /* minnum */
0, /* maxnum */
"h", /* short option */
"help",
I18S("Show this help screen."),
I18S("Show this help screen.")
}
};
rv=GWEN_Args_Check(argc, argv, 1, 0, args, dbArgs);
if (rv==GWEN_ARGS_RESULT_ERROR) {
fprintf(stderr, "ERROR: Could not parse arguments main\n");
return GWEN_ERROR_INVALID;
}
else if (rv==GWEN_ARGS_RESULT_HELP) {
GWEN_BUFFER *ubuf;
ubuf=GWEN_Buffer_new(0, 1024, 0, 1);
GWEN_Buffer_AppendArgs(ubuf,
I18N("This is version %s.\nUsage: %s [OPTIONS]\n\nOptions:\n"),
AQHOME_VERSION_STRING,
argv[0]);
if (GWEN_Args_Usage(args, ubuf, GWEN_ArgsOutType_Txt)) {
fprintf(stderr, "ERROR: Could not create help string\n");
return 1;
}
GWEN_Buffer_AppendString(ubuf, "\n");
fprintf(stdout, "%s\n", GWEN_Buffer_GetStart(ubuf));
GWEN_Buffer_free(ubuf);
return GWEN_ERROR_CLOSE;
}
return 0;
}

View File

@@ -1,23 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_INIT_H
#define AQHOME_STORAGE_INIT_H
#include "./aqhomestorage.h"
int AqHomeStorage_Init(AQHOME_STORAGE *aqh, int argc, char **argv);
#endif

View File

@@ -1,336 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./init_http.h"
#include "./aqhomestorage_p.h"
#include "./aqhomehttp.h"
#include "./u_login.h"
#include "./u_rooms.h"
#include "./u_devices.h"
#include "./u_mqtttopics.h"
#include "./u_values.h"
#include "./u_static.h"
#include "aqhome/msg/endpoint_tty.h"
#include "aqhome/ipc/endpoint_ipc.h"
#include "aqhome/mqtt/endpoint_mqttc.h"
#include "aqhome/http/endpoint_http.h"
#include "aqhome/http/httpservice_conf.h"
#include "aqhome/http/httpservice_http.h"
#include "aqhome/http/httpservice.h"
#include "aqhome/http/content_files.h"
#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/args.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/endpoint_tcpd.h>
#include <gwenhywfar/endpoint_msgio.h>
#include <gwenhywfar/directory.h>
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <time.h>
/* ------------------------------------------------------------------------------------------------
* defines
* ------------------------------------------------------------------------------------------------
*/
//#define I18N(msg) msg
#define I18S(msg) msg
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static void _setupEndpoint(AQHOME_STORAGE *aqh, const char *tcpAddress, int tcpPort);
static int _setupHttpService(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs);
static int _createContentRoot(AQHOME_STORAGE *aqh);
static GWEN_MSG_ENDPOINT *_acceptHttpFn(GWEN_MSG_ENDPOINT *ep, GWEN_SOCKET *sk, const GWEN_INETADDRESS *addr, void *data);
static int _createUrlHandler_login(AQHOME_STORAGE *aqh);
static int _createUrlHandler_rooms(AQHOME_STORAGE *aqh);
static int _createUrlHandler_devices(AQHOME_STORAGE *aqh);
static int _createUrlHandler_topics(AQHOME_STORAGE *aqh);
static int _createUrlHandler_values(AQHOME_STORAGE *aqh);
static int _createUrlHandler_static(AQHOME_STORAGE *aqh);
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
int AqHomeStorage_SetupHttp(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs)
{
const char *tcpAddress;
int tcpPort;
tcpAddress=GWEN_DB_GetCharValue(dbArgs, "httpAddress", 0, NULL);
tcpPort=GWEN_DB_GetIntValue(dbArgs, "httpPort", 0, AQHOME_STORAGE_DEFAULT_HTTP_PORT);
if (tcpAddress && *tcpAddress && tcpPort) {
int rv;
_setupEndpoint(aqh, tcpAddress, tcpPort);
rv=_setupHttpService(aqh, dbArgs);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
rv=_createContentRoot(aqh);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
rv=_createUrlHandler_login(aqh);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
rv=_createUrlHandler_rooms(aqh);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
rv=_createUrlHandler_devices(aqh);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
rv=_createUrlHandler_topics(aqh);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
rv=_createUrlHandler_values(aqh);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
rv=_createUrlHandler_static(aqh);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
}
return 0;
}
void _setupEndpoint(AQHOME_STORAGE *aqh, const char *tcpAddress, int tcpPort)
{
GWEN_MSG_ENDPOINT *ep;
ep=GWEN_TcpdEndpoint_new(tcpAddress, tcpPort, NULL, 0);
GWEN_TcpdEndpoint_SetAcceptFn(ep, _acceptHttpFn, aqh);
GWEN_MsgEndpoint_Tree2_AddChild(aqh->rootEndpoint, ep);
aqh->httpdEndpoint=ep;
}
int _setupHttpService(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs)
{
const char *configFolder;
const char *sourceFolder;
int rv;
configFolder=GWEN_DB_GetCharValue(dbArgs, "cfgdir", 0, AQHOME_STORAGE_DEFAULT_CONFIGDIR);
sourceFolder=GWEN_DB_GetCharValue(dbArgs, "sourceFolder", 0, AQHOME_STORAGE_DEFAULT_HTTP_SOURCEDIR);
aqh->httpService=AQH_HttpService_new(configFolder, sourceFolder);
AqHomeHttpService_Extend(aqh->httpService);
AqHomeHttpService_SetStorage(aqh->httpService, aqh->storage);
rv=AQH_HttpService_LoadConfig(aqh->httpService);
if (rv<0) {
DBG_ERROR(NULL, "Error loading config for HTTP service (%d)", rv);
return GWEN_ERROR_GENERIC;
}
AQH_HttpService_LoadAllSessions(aqh->httpService);
return 0;
}
GWEN_MSG_ENDPOINT *_acceptHttpFn(GWEN_MSG_ENDPOINT *ep,
GWEN_SOCKET *sk,
const GWEN_INETADDRESS *addr,
GWEN_UNUSED void *data)
{
GWEN_MSG_ENDPOINT *epIncoming;
/* AQHOME_STORAGE *aqh;
*
* aqh=(AQHOME_STORAGE*) data;
*/
DBG_INFO(NULL, "Incoming HTTP connection");
epIncoming=GWEN_MsgEndpoint_new("http", 0);
GWEN_MsgEndpoint_SetSocket(epIncoming, sk);
GWEN_MsgIoEndpoint_Extend(epIncoming);
AQH_HttpEndpoint_Extend(epIncoming, AQH_ENDPOINT_HTTP_FLAGS_PASSIVE);
return epIncoming;
}
int _createContentRoot(AQHOME_STORAGE *aqh)
{
const char *sourceFolder;
GWEN_BUFFER *nbuf1;
GWEN_BUFFER *nbuf2;
AQH_HTTP_CONTENT *c;
sourceFolder=AQH_HttpService_GetSourceFolder(aqh->httpService);
nbuf1=GWEN_Buffer_new(0, 256, 0, 1);
nbuf2=GWEN_Buffer_new(0, 256, 0, 1);
GWEN_Buffer_AppendString(nbuf1, sourceFolder);
GWEN_Buffer_AppendString(nbuf1, GWEN_DIR_SEPARATOR_S AQHOME_STORAGE_SITEHEADER);
GWEN_Buffer_AppendString(nbuf2, sourceFolder);
GWEN_Buffer_AppendString(nbuf2, GWEN_DIR_SEPARATOR_S AQHOME_STORAGE_SITEFOOTER);
c=AQH_HttpContentFiles_new("root", GWEN_Buffer_GetStart(nbuf1), GWEN_Buffer_GetStart(nbuf2));
if (c==NULL) {
DBG_INFO(NULL,
"Unable to create content for root (header=[%s], footer=[%s])",
GWEN_Buffer_GetStart(nbuf1), GWEN_Buffer_GetStart(nbuf2));
GWEN_Buffer_free(nbuf2);
GWEN_Buffer_free(nbuf1);
return GWEN_ERROR_GENERIC;
}
AqHomeHttpService_SetContentTree(aqh->httpService, c);
GWEN_Buffer_free(nbuf2);
GWEN_Buffer_free(nbuf1);
return 0;
}
int _createUrlHandler_login(AQHOME_STORAGE *aqh)
{
AQH_HTTP_URLHANDLER *uh;
uh=AQH_LoginHttpUrlHandler_new(aqh->httpService);
AQH_HttpUrlHandler_SetContentProvider(uh, AqHomeHttpService_GetContentTree(aqh->httpService));
AQH_HttpUrlHandler_AddUrlPattern(uh, "/login");
AQH_HttpService_AddUrlHandler(aqh->httpService, uh);
return 0;
}
int _createUrlHandler_rooms(AQHOME_STORAGE *aqh)
{
AQH_HTTP_URLHANDLER *uh;
uh=AQH_RoomsHttpUrlHandler_new(aqh->httpService);
AQH_HttpUrlHandler_SetContentProvider(uh, AqHomeHttpService_GetContentTree(aqh->httpService));
AQH_HttpUrlHandler_AddUrlPattern(uh, "/rooms/*");
AQH_HttpService_AddUrlHandler(aqh->httpService, uh);
return 0;
}
int _createUrlHandler_devices(AQHOME_STORAGE *aqh)
{
AQH_HTTP_URLHANDLER *uh;
uh=AQH_DevicesHttpUrlHandler_new(aqh->httpService);
AQH_HttpUrlHandler_SetContentProvider(uh, AqHomeHttpService_GetContentTree(aqh->httpService));
AQH_HttpUrlHandler_AddUrlPattern(uh, "/devices/*");
AQH_HttpService_AddUrlHandler(aqh->httpService, uh);
return 0;
}
int _createUrlHandler_topics(AQHOME_STORAGE *aqh)
{
AQH_HTTP_URLHANDLER *uh;
uh=AQH_MqttTopicsHttpUrlHandler_new(aqh->httpService);
AQH_HttpUrlHandler_SetContentProvider(uh, AqHomeHttpService_GetContentTree(aqh->httpService));
AQH_HttpUrlHandler_AddUrlPattern(uh, "/mqtttopics/*");
AQH_HttpService_AddUrlHandler(aqh->httpService, uh);
return 0;
}
int _createUrlHandler_values(AQHOME_STORAGE *aqh)
{
AQH_HTTP_URLHANDLER *uh;
uh=AQH_ValuesHttpUrlHandler_new(aqh->httpService);
AQH_HttpUrlHandler_SetContentProvider(uh, AqHomeHttpService_GetContentTree(aqh->httpService));
AQH_HttpUrlHandler_AddUrlPattern(uh, "/values/*");
AQH_HttpService_AddUrlHandler(aqh->httpService, uh);
return 0;
}
int _createUrlHandler_static(AQHOME_STORAGE *aqh)
{
AQH_HTTP_URLHANDLER *uh;
GWEN_BUFFER *nameBuf;
const char *sourceFolder;
sourceFolder=AQH_HttpService_GetSourceFolder(aqh->httpService);
nameBuf=GWEN_Buffer_new(0, 256, 0, 1);
GWEN_Buffer_AppendString(nameBuf, sourceFolder);
GWEN_Buffer_AppendString(nameBuf, GWEN_DIR_SEPARATOR_S AQHOME_STORAGE_STATIC_RELFOLDER GWEN_DIR_SEPARATOR_S "pics");
uh=AQH_StaticHttpUrlHandler_new(aqh->httpService, GWEN_Buffer_GetStart(nameBuf));
GWEN_Buffer_free(nameBuf);
AQH_HttpUrlHandler_AddUrlPattern(uh, "/pics/*");
AQH_HttpService_AddUrlHandler(aqh->httpService, uh);
return 0;
}

View File

@@ -1,23 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_INIT_HTTP_H
#define AQHOME_STORAGE_INIT_HTTP_H
#include "./aqhomestorage.h"
int AqHomeStorage_SetupHttp(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs);
#endif

View File

@@ -1,212 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./init_mqtt.h"
#include "./aqhomestorage_p.h"
#include "./aqhomehttp.h"
#include "./u_login.h"
#include "./u_rooms.h"
#include "./u_devices.h"
#include "./u_mqtttopics.h"
#include "./u_values.h"
#include "./u_static.h"
#include "aqhome/msg/endpoint_tty.h"
#include "aqhome/ipc/endpoint_ipc.h"
#include "aqhome/mqtt/endpoint_mqttc.h"
#include <aqhome/mqtt/msg_mqtt_subscribe.h>
#include <aqhome/mqtt/msg_mqtt_suback.h>
#include "aqhome/http/endpoint_http.h"
#include "aqhome/http/httpservice_conf.h"
#include "aqhome/http/httpservice_http.h"
#include "aqhome/http/httpservice.h"
#include "aqhome/http/content_files.h"
#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/args.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/endpoint_tcpd.h>
#include <gwenhywfar/endpoint_msgio.h>
#include <gwenhywfar/directory.h>
#include <gwenhywfar/text.h>
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <time.h>
/* ------------------------------------------------------------------------------------------------
* defines
* ------------------------------------------------------------------------------------------------
*/
//#define I18N(msg) msg
#define I18S(msg) msg
#define AQHOME_STORAGE_DEFAULT_CMDTIMEOUT 10000
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static int _mqttConnect(GWEN_MSG_ENDPOINT *epTcp);
static int _subscribe(AQHOME_STORAGE *aqh, const char *topicFilter);
static GWEN_MSG *_awaitPacket(GWEN_MSG_ENDPOINT *epTcp, uint8_t expectedPacketType, int timeoutInSeconds);
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
int AqHomeStorage_SetupMqtt(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs)
{
const char *mqttAddress;
int mqttPort;
const char *mqttClientId;
int mqttKeepAlive;
mqttAddress=GWEN_DB_GetCharValue(dbArgs, "mqttAddress", 0, NULL);
mqttPort=GWEN_DB_GetIntValue(dbArgs, "mqttPort", 0, AQHOME_STORAGE_DEFAULT_MQTT_PORT);
mqttClientId=GWEN_DB_GetCharValue(dbArgs, "mqttClientId", 0, AQHOME_STORAGE_DEFAULT_MQTT_CLIENTID);
mqttKeepAlive=GWEN_DB_GetIntValue(dbArgs, "mqttKeepAlive", 0, AQHOME_STORAGE_DEFAULT_MQTT_KEEPALIVE);
if (mqttAddress && *mqttAddress && mqttPort) {
GWEN_MSG_ENDPOINT *ep;
int rv;
ep=AQH_MqttClientEndpoint_new(mqttClientId, mqttAddress, mqttPort, NULL, 0);
AQH_MqttClientEndpoint_SetKeepAliveTime(ep, mqttKeepAlive);
GWEN_MsgEndpoint_Tree2_AddChild(aqh->rootEndpoint, ep);
aqh->mqttEndpoint=ep;
rv=_mqttConnect(ep);
if (rv<0) {
DBG_ERROR(NULL, "Error connecting to MQTT server %s:%d (%d)", mqttAddress, mqttPort, rv);
return rv;
}
rv=_subscribe(aqh, "#");
if (rv<0) {
DBG_ERROR(NULL, "Error subscribingconnecting to MQTT server %s:%d (%d)", mqttAddress, mqttPort, rv);
return rv;
}
}
return 0;
}
int _mqttConnect(GWEN_MSG_ENDPOINT *epTcp)
{
if (GWEN_MsgEndpoint_GetState(epTcp)==GWEN_MSG_ENDPOINT_STATE_UNCONNECTED) {
int rv;
rv=AQH_MqttClientEndpoint_StartConnect(epTcp);
if (rv<0 && rv!=GWEN_ERROR_IN_PROGRESS) {
DBG_ERROR(NULL, "Error starting to connect (%d)", rv);
return rv;
}
}
while(GWEN_MsgEndpoint_GetState(epTcp)!=GWEN_MSG_ENDPOINT_STATE_CONNECTED) {
DBG_DEBUG(NULL, "Next loop");
GWEN_MsgEndpoint_IoLoop(epTcp, 2000); /* 2000 ms */
}
return 0;
}
int _subscribe(AQHOME_STORAGE *aqh, const char *topicFilter)
{
uint16_t pckId;
GWEN_MSG *msgOut;
GWEN_MSG *msgIn;
DBG_INFO(NULL, "Sending SUBSCRIBE %s", topicFilter);
pckId=AQH_MqttClientEndpoint_GetNextPacketId(aqh->mqttEndpoint);
msgOut=GWEN_SubscribeMqttMsg_new(AQH_MQTTMSG_MSGTYPE_SUBSCRIBE, pckId, topicFilter, 0);
if (msgOut==NULL) {
DBG_ERROR(NULL, "Error creating message");
return GWEN_ERROR_INTERNAL;
}
GWEN_MsgEndpoint_AddSendMessage(aqh->mqttEndpoint, msgOut);
DBG_INFO(NULL, "Waiting for response");
msgIn=_awaitPacket(aqh->mqttEndpoint, AQH_MQTTMSG_MSGTYPE_SUBACK, AQHOME_STORAGE_DEFAULT_CMDTIMEOUT);
if (msgIn) {
GWEN_BUFFER *buf;
buf=GWEN_Buffer_new(0, 256, 0, 1);
AQH_SubAckMqttMsg_DumpToBuffer(msgIn, buf, "received");
DBG_INFO(NULL, "%s", GWEN_Buffer_GetStart(buf));
GWEN_Buffer_free(buf);
GWEN_Msg_free(msgIn);
}
return 0;
}
GWEN_MSG *_awaitPacket(GWEN_MSG_ENDPOINT *epTcp, uint8_t expectedPacketType, int timeoutInSeconds)
{
time_t startTime;
startTime=time(NULL);
for (;;) {
GWEN_MSG *msg;
time_t now;
GWEN_MsgEndpoint_IoLoop(epTcp, 2000); /* 2000 ms */
msg=GWEN_MsgEndpoint_TakeFirstReceivedMessage(epTcp);
if (msg) {
if ((AQH_MqttMsg_GetMsgTypeAndFlags(msg) & 0xf0)==(expectedPacketType & 0xf0)) {
return msg;
}
else {
DBG_ERROR(NULL, "Received this message:");
GWEN_Text_DumpString((const char*) GWEN_Msg_GetConstBuffer(msg), GWEN_Msg_GetBytesInBuffer(msg), 2);
}
GWEN_Msg_free(msg);
}
now=time(NULL);
if (now-startTime>timeoutInSeconds) {
DBG_INFO(NULL, "Timeout");
break;
}
}
return NULL;
}

View File

@@ -1,25 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_INIT_MQTT_H
#define AQHOME_STORAGE_INIT_MQTT_H
#include "./aqhomestorage.h"
int AqHomeStorage_SetupMqtt(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs);
#endif

View File

@@ -1,150 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./loop.h"
#include "./loop_http.h"
#include "./loop_mqtt.h"
#include "./aqhomehttp.h"
#include "./aqhomestorage_p.h"
#include "aqhome/http/httpservice_conf.h"
#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/args.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/endpoint.h>
/* ------------------------------------------------------------------------------------------------
* defines
* ------------------------------------------------------------------------------------------------
*/
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static int _writeModifiedSessions(AQHOME_STORAGE *aqh);
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
void AqHomeStorage_Loop(AQHOME_STORAGE *aqh, int timeoutInMsecs)
{
if (aqh) {
GWEN_MsgEndpoint_ChildrenIoLoop(aqh->rootEndpoint, timeoutInMsecs);
AqHomeStorage_ReadAndHandleHttpMessages(aqh);
AqHomeStorage_ReadAndHandleMqttMessages(aqh);
// AqHomeStorage_ReadAndHandleIpcMessages(aqh);
}
}
int AqHomeStorage_WriteStorageIfChanged(AQHOME_STORAGE *aqh)
{
if (AQH_Storage_GetRuntimeFlags(aqh->storage) & AQH_STORAGE_RTFLAGS_MODIFIED) {
int rv;
DBG_INFO(NULL, "Storage modified, writing statefile");
rv=AqHomeHttpService_LockStorage(aqh->httpService);
if (rv<0) {
DBG_INFO(NULL, "Error locking storage (%d)", rv);
return rv;
}
rv=AQH_Storage_WriteState(aqh->storage);
if (rv<0) {
DBG_INFO(NULL, "Error writing state file (%d)", rv);
AqHomeHttpService_UnlockStorage(aqh->httpService);
return rv;
}
rv=AqHomeHttpService_UnlockStorage(aqh->httpService);
if (rv<0) {
DBG_INFO(NULL, "Error unlocking storage (%d)", rv);
return rv;
}
}
return 0;
}
int AqHomeStorage_WriteServiceIfChanged(AQHOME_STORAGE *aqh)
{
int rv;
rv=_writeModifiedSessions(aqh);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
return 0;
}
int _writeModifiedSessions(AQHOME_STORAGE *aqh)
{
AQH_SESSION_LIST *sessionList;
int rv;
rv=AQH_HttpService_LockSessions(aqh->httpService);
if (rv<0) {
DBG_INFO(NULL, "Error locking sessions (%d)", rv);
return rv;
}
sessionList=AQH_Service_GetSessionList(aqh->httpService);
if (sessionList) {
AQH_SESSION *session;
session=AQH_Session_List_First(sessionList);
while(session) {
if (AQH_Session_GetRuntimeFlags(session) & AQH_SESSION_RTFLAGS_MODIFIED) {
DBG_INFO(NULL, "Session \"%s\" modified, writing", AQH_Session_GetUid(session));
rv=AQH_HttpService_SaveSession(aqh->httpService, session);
if (rv<0) {
DBG_INFO(NULL, "Error writing session \"%s\" (%d)", AQH_Session_GetUid(session), rv);
}
else {
DBG_DEBUG(NULL, "Session \"%s\" written", AQH_Session_GetUid(session));
AQH_Session_SubRuntimeFlags(session, AQH_SESSION_RTFLAGS_MODIFIED);
}
}
session=AQH_Session_List_Next(session);
}
}
rv=AQH_HttpService_UnlockSessions(aqh->httpService);
if (rv<0) {
DBG_INFO(NULL, "Error unlocking sessions (%d)", rv);
return rv;
}
return 0;
}

View File

@@ -1,27 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_LOOP_H
#define AQHOME_STORAGE_LOOP_H
#include "./aqhomestorage.h"
void AqHomeStorage_Loop(AQHOME_STORAGE *aqh, int timeoutInMsecs);
int AqHomeStorage_WriteStorageIfChanged(AQHOME_STORAGE *aqh);
int AqHomeStorage_WriteServiceIfChanged(AQHOME_STORAGE *aqh);
#endif

View File

@@ -1,117 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./loop_http.h"
#include "./aqhomestorage_p.h"
#include "aqhome/msg/endpoint_tty.h"
#include "aqhome/ipc/endpoint_ipc.h"
#include "aqhome/mqtt/endpoint_mqttc.h"
#include "aqhome/http/endpoint_http.h"
#include "aqhome/http/httpservice_http.h"
#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/text.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/endpoint_tcpd.h>
#include <gwenhywfar/endpoint_msgio.h>
#include <gwenhywfar/syncio.h>
#include <gwenhywfar/url.h>
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <time.h>
/* ------------------------------------------------------------------------------------------------
* defines
* ------------------------------------------------------------------------------------------------
*/
//#define I18N(msg) msg
#define I18S(msg) msg
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static void _handleHttpEndpoint(AQHOME_STORAGE *aqh, GWEN_MSG_ENDPOINT *ep);
static void _handleHttpMsg(AQHOME_STORAGE *aqh, GWEN_MSG_ENDPOINT *ep, const GWEN_MSG *msgReceived);
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
void AqHomeStorage_ReadAndHandleHttpMessages(AQHOME_STORAGE *aqh)
{
if (aqh->httpdEndpoint) {
GWEN_MSG_ENDPOINT *ep;
ep=GWEN_MsgEndpoint_Tree2_GetFirstChild(aqh->httpdEndpoint);
while(ep) {
_handleHttpEndpoint(aqh, ep);
ep=GWEN_MsgEndpoint_Tree2_GetNext(ep);
}
}
}
void _handleHttpEndpoint(AQHOME_STORAGE *aqh, GWEN_MSG_ENDPOINT *ep)
{
GWEN_MSG *msg;
while( (msg=GWEN_MsgEndpoint_TakeFirstReceivedMessage(ep)) ) {
_handleHttpMsg(aqh, ep, msg);
GWEN_Msg_free(msg);
}
}
void _handleHttpMsg(AQHOME_STORAGE *aqh, GWEN_MSG_ENDPOINT *ep, const GWEN_MSG *msgReceived)
{
GWEN_MSG *msgResponse;
msgResponse=AQH_HttpService_HandleHttpRequest(aqh->httpService, ep, msgReceived);
if (msgResponse) {
DBG_INFO(NULL, "Sending response message");
GWEN_MsgEndpoint_AddSendMessage(ep, msgResponse);
}
else {
DBG_INFO(NULL, "No response for message");
}
}

View File

@@ -1,110 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./loop_mqtt.h"
#include "./aqhomestorage_p.h"
#include "aqhome/mqtt/msg_mqtt_publish.h"
#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/debug.h>
/* ------------------------------------------------------------------------------------------------
* defines
* ------------------------------------------------------------------------------------------------
*/
//#define I18N(msg) msg
#define I18S(msg) msg
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static void _handlePublishMsg(AQHOME_STORAGE *aqh, GWEN_MSG *msg);
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
void AqHomeStorage_ReadAndHandleMqttMessages(AQHOME_STORAGE *aqh)
{
GWEN_MSG *msg;
while( (msg=GWEN_MsgEndpoint_TakeFirstReceivedMessage(aqh->mqttEndpoint)) ) {
if ((AQH_MqttMsg_GetMsgTypeAndFlags(msg) & 0xf0)==(AQH_MQTTMSG_MSGTYPE_PUBLISH & 0xf0)) {
_handlePublishMsg(aqh, msg);
}
else if ((AQH_MqttMsg_GetMsgTypeAndFlags(msg) & 0xf0)==(AQH_MQTTMSG_MSGTYPE_PINGRESP & 0xf0)) {
DBG_INFO(AQH_LOGDOMAIN, "PING response received");
}
else {
DBG_INFO(NULL, "Received unexpected MQTT message %02x", AQH_MqttMsg_GetMsgTypeAndFlags(msg));
}
GWEN_Msg_free(msg);
}
}
int AqHomeStorage_MqttPing(AQHOME_STORAGE *aqh)
{
GWEN_MSG *msgOut;
DBG_INFO(AQH_LOGDOMAIN, "Sending PING");
msgOut=GWEN_MqttMsg_new(AQH_MQTTMSG_MSGTYPE_PINGREQ, 0, NULL);
if (msgOut==NULL) {
DBG_ERROR(NULL, "Error creating message");
return GWEN_ERROR_INTERNAL;
}
GWEN_MsgEndpoint_AddSendMessage(aqh->mqttEndpoint, msgOut);
return 0;
}
void _handlePublishMsg(AQHOME_STORAGE *aqh, GWEN_MSG *msg)
{
char *topic;
char *value;
topic=AQH_PublishMqttMsg_ExtractTopic(msg);
value=AQH_PublishMqttMsg_ExtractValue(msg);
if (topic && value)
AQH_Storage_HandleMqttPublish(aqh->storage, topic, value);
else {
DBG_ERROR(NULL, "Either topic or value missing in PUBLISH msg");
}
free(value);
free(topic);
}

View File

@@ -1,23 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_MQTT_H
#define AQHOME_STORAGE_MQTT_H
#include "./aqhomestorage.h"
void AqHomeStorage_ReadAndHandleMqttMessages(AQHOME_STORAGE *aqh);
int AqHomeStorage_MqttPing(AQHOME_STORAGE *aqh);
#endif

View File

@@ -1,269 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <aqhome/api.h>
#include <aqhome/aqhome.h>
#include "./aqhomestorage.h"
#include "./init.h"
#include "./fini.h"
#include "./loop.h"
#include "./loop_mqtt.h"
#include "./cleanup.h"
#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/logger.h>
#include <gwenhywfar/cgui.h>
#include <gwenhywfar/debug.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
//#define CLEANUP_INTERVAL_IN_SECS (5*60)
//#define WRITE_INTERVAL_IN_SECS (5*60)
#define CLEANUP_INTERVAL_IN_SECS (60)
#define WRITE_INTERVAL_IN_SECS (60)
#define PING_INTERVAL 120
/* ------------------------------------------------------------------------------------------------
* defines
* ------------------------------------------------------------------------------------------------
*/
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
#ifdef HAVE_SIGNAL_H
static int _setSignalHandlers(void);
static int _setupSigAction(struct sigaction *sa, int sig);
static void _signalHandler(int s);
#endif
static void _runService(AQHOME_STORAGE *aqh);
static void _writeCurrentState(AQHOME_STORAGE *aqh);
/* ------------------------------------------------------------------------------------------------
* static vars
* ------------------------------------------------------------------------------------------------
*/
#ifdef HAVE_SIGNAL_H
static struct sigaction saINT,saTERM, saHUP, saTSTP, saCONT;
#endif
static int stopService=0;
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
int main(int argc, char **argv)
{
int rv;
AQHOME_STORAGE *aqh;
GWEN_GUI *gui;
rv=GWEN_Init();
if (rv) {
fprintf(stderr, "ERROR: Unable to init Gwen.\n");
return 2;
}
GWEN_Logger_Open(0, "aqhome-storage", 0, GWEN_LoggerType_Console, GWEN_LoggerFacility_User);
//GWEN_Logger_SetLevel(0, GWEN_LoggerLevel_Warning);
GWEN_Logger_SetLevel(0, GWEN_LoggerLevel_Info);
rv=_setSignalHandlers();
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
rv=AQH_Init();
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return 2;
}
gui=GWEN_Gui_CGui_new();
GWEN_Gui_SetGui(gui);
aqh=AqHomeStorage_new();
rv=AqHomeStorage_Init(aqh, argc, argv);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return 2;
}
_runService(aqh);
AqHomeStorage_Fini(aqh);
AqHomeStorage_free(aqh);
GWEN_Gui_SetGui(NULL);
GWEN_Gui_free(gui);
return 0;
}
void _runService(AQHOME_STORAGE *aqh)
{
time_t timeStart;
time_t timeLastCleanup;
time_t timeLastWrite;
time_t timeLastPing;
int timeout;
timeout=AqHomeStorage_GetTimeout(aqh);
timeStart=time(NULL);
timeLastCleanup=time(NULL);
timeLastWrite=time(NULL);
timeLastPing=time(NULL);
while(!stopService) {
time_t now;
DBG_DEBUG(NULL, "Next loop");
AqHomeStorage_Loop(aqh, 2000);
now=time(NULL);
if (((int)difftime(now, timeLastCleanup))>CLEANUP_INTERVAL_IN_SECS) {
DBG_INFO(NULL, "Cleanup time");
AqHomeStorage_Cleanup(aqh);
timeLastCleanup=now;
}
if (((int)difftime(now, timeLastWrite))>WRITE_INTERVAL_IN_SECS) {
DBG_INFO(NULL, "Write time");
_writeCurrentState(aqh);
timeLastWrite=now;
}
if (timeout && ((int)difftime(now, timeLastPing))>timeout) {
DBG_INFO(NULL, "Sending ping");
AqHomeStorage_MqttPing(aqh);
timeLastPing=now;
}
if (timeout && ((int)difftime(now, timeStart))>timeout) {
DBG_INFO(NULL, "Timeout");
_writeCurrentState(aqh);
break;
}
} /* while */
}
void _writeCurrentState(AQHOME_STORAGE *aqh)
{
int rv;
rv=AqHomeStorage_WriteStorageIfChanged(aqh);
if (rv<0) {
DBG_ERROR(NULL, "ATTENTION: Could not write storage statefile (%d)", rv);
}
rv=AqHomeStorage_WriteServiceIfChanged(aqh);
if (rv<0) {
DBG_ERROR(NULL, "ATTENTION: Could not write current config (%d)", rv);
}
}
int _setSignalHandlers(void)
{
#ifdef HAVE_SIGNAL_H
int rv;
rv=_setupSigAction(&saINT, SIGINT);
if (rv)
return rv;
rv=_setupSigAction(&saTERM, SIGTERM);
if (rv)
return rv;
rv=_setupSigAction(&saHUP, SIGHUP);
if (rv)
return rv;
# ifdef SIGTSTP
rv=_setupSigAction(&saTSTP, SIGTSTP);
if (rv)
return rv;
# endif
# ifdef SIGCONT
rv=_setupSigAction(&saCONT, SIGCONT);
if (rv)
return rv;
# endif
#endif
return 0;
}
int _setupSigAction(struct sigaction *sa, int sig)
{
sa->sa_handler=_signalHandler;
sigemptyset(&sa->sa_mask);
sa->sa_flags=0;
if (sigaction(sig, sa, 0)) {
DBG_ERROR(NULL, "Could not setup signal handler for signal %d", sig);
return GWEN_ERROR_IO;
}
return 0;
}
void _signalHandler(int s)
{
switch(s) {
case SIGINT:
case SIGTERM:
case SIGHUP:
DBG_WARN(0, "Received signal %d, stopping service in next loop.",s);
stopService=1;
break;
default:
DBG_WARN(0, "Unknown signal %d",s);
break;
}
}

View File

@@ -1,71 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./u_base.h"
#include "./aqhomehttp.h"
#include "aqhome/http/httpservice_http.h"
#include <gwenhywfar/debug.h>
#include <gwenhywfar/i18n.h>
/* ------------------------------------------------------------------------------------------------
* defines
* ------------------------------------------------------------------------------------------------
*/
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
GWEN_MSG *AQH_BaseHttpUrlHandler_CreateResponseForErrorCode(AQH_HTTP_URLHANDLER *uh,
AQH_HTTP_REQUEST *rq,
int rv,
AQH_HTTP_URLHANDLER_WRITEPAGE_CB cb,
GWEN_DB_NODE *db)
{
if (rv<0) {
switch(rv) {
case GWEN_ERROR_INVALID:
return AQH_HttpUrlHandler_CreatePageMessage(uh, rq, "red", I18N("Missing fields"), 1, db, cb);
case GWEN_ERROR_FOUND:
return AQH_HttpUrlHandler_CreatePageMessage(uh, rq, "red", I18N("Object already exists"), 1, db, cb);
case GWEN_ERROR_NOT_FOUND:
return AQH_HttpUrlHandler_CreatePageMessage(uh, rq, "red", I18N("Object not found"), 1, db, cb);
case GWEN_ERROR_IO:
case GWEN_ERROR_INTERNAL:
default:
return AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh),
500, "Internal error",
AQH_HttpRequest_GetProtocol(rq), NULL);
}
}
return NULL;
}

View File

@@ -1,27 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_U_BASE_H
#define AQHOME_STORAGE_U_BASE_H
#include "aqhome/http/urlhandler.h"
GWEN_MSG *AQH_BaseHttpUrlHandler_CreateResponseForErrorCode(AQH_HTTP_URLHANDLER *uh,
AQH_HTTP_REQUEST *rq,
int rv,
AQH_HTTP_URLHANDLER_WRITEPAGE_CB cb,
GWEN_DB_NODE *db);
#endif

View File

@@ -1,351 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./u_devices.h"
#include "./u_objects.h"
#include "./aqhomehttp.h"
#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/i18n.h>
/* ------------------------------------------------------------------------------------------------
* defines
* ------------------------------------------------------------------------------------------------
*/
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static int _addOrEditObject(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *db, int id);
static GWEN_DB_NODE *_findObjectByIdAndReturnAsDb(AQH_HTTP_URLHANDLER *uh, int id);
static int _writeAddPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf);
static int _writeEditPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf);
static void _listObjectsIntoBuffer(AQH_HTTP_URLHANDLER *uh, GWEN_BUFFER *pageBuf);
static void _writeEditingTable(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf);
static void _setFromObject(AQH_DEVICE *device, const AQH_DEVICE *srcDevice);
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
AQH_HTTP_URLHANDLER *AQH_DevicesHttpUrlHandler_new(AQH_SERVICE *sv)
{
AQH_HTTP_URLHANDLER *uh;
uh=AQH_ObjectsHttpUrlHandler_new(sv,
AQHOME_HTTP_PERMS_LIST_DEVICES,
AQHOME_HTTP_PERMS_ADD_DEVICE,
AQHOME_HTTP_PERMS_DEL_DEVICE,
AQHOME_HTTP_PERMS_EDIT_DEVICE,
"/devices/list");
AQH_ObjectsHttpUrlHandler_SetAddOrEditObjectFn(uh, _addOrEditObject);
AQH_ObjectsHttpUrlHandler_SetFindObjectByIdAndReturnAsDbFn(uh, _findObjectByIdAndReturnAsDb);
AQH_ObjectsHttpUrlHandler_SetWriteAddPageFn(uh, _writeAddPage);
AQH_ObjectsHttpUrlHandler_SetWriteEditPageFn(uh, _writeEditPage);
AQH_ObjectsHttpUrlHandler_SetListObjectsIntoBufferFn(uh, _listObjectsIntoBuffer);
return uh;
}
int _addOrEditObject(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *db, int id)
{
AQH_SERVICE *sv;
AQH_STORAGE *sto;
AQH_DEVICE *newDevice;
const char *deviceName;
int rv;
sv=AQH_HttpUrlHandler_GetHttpService(uh);
sto=AqHomeHttpService_GetStorage(sv);
newDevice=AQH_Device_fromDb(db);
deviceName=AQH_Device_GetName(newDevice);
if (!(deviceName && *deviceName)) {
DBG_INFO(NULL, "Missing device name");
AQH_Device_free(newDevice);
return GWEN_ERROR_INVALID;
}
rv=AqHomeHttpService_LockStorage(sv);
if (rv<0) {
DBG_ERROR(NULL, "Error locking storage");
AQH_Device_free(newDevice);
return GWEN_ERROR_IO;
}
if (id>0) {
AQH_DEVICE *device;
DBG_INFO(NULL, "Edit existing device");
device=AQH_Storage_GetDeviceById(sto, id);
if (device==NULL) {
AqHomeHttpService_UnlockStorage(sv);
DBG_ERROR(NULL, "Device %d not found", id);
AQH_Device_free(newDevice);
return GWEN_ERROR_NOT_FOUND;
}
AQH_Device_SetId(device, id);
_setFromObject(device, newDevice);
AQH_Storage_AddRuntimeFlags(sto, AQH_STORAGE_RTFLAGS_MODIFIED);
}
else {
AQH_DEVICE *device;
DBG_INFO(NULL, "Adding new device");
device=AQH_Storage_GetDeviceByName(sto, deviceName);
if (device) {
AqHomeHttpService_UnlockStorage(sv);
DBG_ERROR(NULL, "Device %s exists", deviceName);
AQH_Device_free(newDevice);
return GWEN_ERROR_FOUND;
}
device=AQH_Device_new();
AQH_Device_SetId(device, 0);
_setFromObject(device, newDevice);
AQH_Storage_AddDevice(sto, device);
AQH_Storage_AddRuntimeFlags(sto, AQH_STORAGE_RTFLAGS_MODIFIED);
}
AQH_Device_free(newDevice);
rv=AqHomeHttpService_UnlockStorage(sv);
if (rv<0) {
DBG_ERROR(NULL, "Error unlocking storage");
return GWEN_ERROR_IO;
}
return 0;
}
void _setFromObject(AQH_DEVICE *device, const AQH_DEVICE *srcDevice)
{
AQH_Device_SetRoomId(device, AQH_Device_GetRoomId(srcDevice));
AQH_Device_SetName(device, AQH_Device_GetName(srcDevice));
AQH_Device_SetDeviceType(device, AQH_Device_GetDeviceType(srcDevice));
AQH_Device_SetLocation(device, AQH_Device_GetLocation(srcDevice));
AQH_Device_SetDescription(device, AQH_Device_GetDescription(srcDevice));
}
GWEN_DB_NODE *_findObjectByIdAndReturnAsDb(AQH_HTTP_URLHANDLER *uh, int id)
{
AQH_SERVICE *sv;
AQH_STORAGE *sto;
const AQH_DEVICE *device;
sv=AQH_HttpUrlHandler_GetHttpService(uh);
sto=AqHomeHttpService_GetStorage(sv);
device=AQH_Storage_GetDeviceById(sto, id);
if (device) {
GWEN_DB_NODE *db;
db=GWEN_DB_Group_new("device");
AQH_Device_toDb(device, db);
return db;
}
return NULL;
}
int _writeAddPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf)
{
GWEN_Buffer_AppendArgs(pageBuf,
"<h2>%s</h2><br>\n"
"<form action=\"/devices/add\" method=\"post\" enctype=\"application/x-www-form-urlencoded\">",
I18N("Add Device"));
_writeEditingTable(uh, dbValues, pageBuf);
GWEN_Buffer_AppendArgs(pageBuf, "<input type=\"submit\" value=\"%s\"></form>", I18N("Submit"));
return 0;
}
int _writeEditPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf)
{
GWEN_Buffer_AppendArgs(pageBuf,
"<h2>%s</h2><br>\n"
"<form action=\"/devices/edit/%lu\" method=\"post\" enctype=\"application/x-www-form-urlencoded\">",
I18N("Edit Device"),
(long unsigned int)(dbValues?GWEN_DB_GetIntValue(dbValues, "id", 0, 0):0));
_writeEditingTable(uh, dbValues, pageBuf);
GWEN_Buffer_AppendArgs(pageBuf, "<input type=\"submit\" value=\"%s\"></form>", I18N("Submit"));
return 0;
}
void _writeEditingTable(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf)
{
AQH_SERVICE *sv;
AQH_STORAGE *sto;
const AQH_ROOM_LIST *roomList;
unsigned long int selectedRoomId=0;
char numbuf[16];
sv=AQH_HttpUrlHandler_GetHttpService(uh);
sto=AqHomeHttpService_GetStorage(sv);
roomList=AQH_Storage_GetRoomList(sto);
selectedRoomId=(unsigned long int)(dbValues?GWEN_DB_GetIntValue(dbValues, "roomId", 0, 0):0);
snprintf(numbuf, sizeof(numbuf)-1, "%lu", selectedRoomId);
numbuf[sizeof(numbuf)-1]=0;
GWEN_Buffer_AppendArgs(pageBuf,
" <table>"
" <tr>"
" <td><label for=\"name\">%s: </label></td>"
" <td><input type=\"text\" name=\"name\" value=\"%s\" required></td>"
" </tr>",
I18N("Name"),
dbValues?GWEN_DB_GetCharValue(dbValues, "name", 0, ""):"");
GWEN_Buffer_AppendArgs(pageBuf,
"<tr><td><label for=\"roomId\">%s: </label></td>"
"<td><select id=\"roomId\" name=\"roomId\">",
I18N("Room"));
GWEN_Buffer_AppendArgs(pageBuf, "<option value=\"0\">%s</option>", I18N("-- select room --"));
if (roomList) {
const AQH_ROOM *r;
r=AQH_Room_List_First(roomList);
while(r) {
const char *roomName;
roomName=AQH_Room_GetName(r);
if (roomName && *roomName) {
snprintf(numbuf, sizeof(numbuf)-1, "%lu", (unsigned long int) AQH_Room_GetId(r));
numbuf[sizeof(numbuf)-1]=0;
if (selectedRoomId && AQH_Room_GetId(r)==selectedRoomId)
GWEN_Buffer_AppendArgs(pageBuf, "<option value=\"%s\" selected>%s</option>", numbuf, roomName);
else
GWEN_Buffer_AppendArgs(pageBuf, "<option value=\"%s\">%s</option>", numbuf, roomName);
}
r=AQH_Room_List_Next(r);
}
}
GWEN_Buffer_AppendString(pageBuf, "</select></td></tr>");
GWEN_Buffer_AppendArgs(pageBuf,
" <tr>"
" <td><label for=\"deviceType\">%s: </label></td>"
" <td><input type=\"text\" name=\"deviceType\" value=\"%s\"></td>"
" </tr>",
I18N("Type"),
dbValues?GWEN_DB_GetCharValue(dbValues, "deviceType", 0, ""):"");
GWEN_Buffer_AppendArgs(pageBuf,
" <tr>"
" <td><label for=\"location\">%s: </label></td>"
" <td><input type=\"text\" name=\"location\" value=\"%s\"></td>"
" </tr>",
I18N("Location"),
dbValues?GWEN_DB_GetCharValue(dbValues, "location", 0, ""):"");
GWEN_Buffer_AppendArgs(pageBuf,
" <tr>"
" <td><label for=\"description\">%s: </label></td>"
" <td><input type=\"text\" name=\"description\" value=\"%s\"></td>"
" </tr>"
" </table>",
I18N("Description"),
dbValues?GWEN_DB_GetCharValue(dbValues, "description", 0, ""):"");
}
void _listObjectsIntoBuffer(AQH_HTTP_URLHANDLER *uh, GWEN_BUFFER *pageBuf)
{
AQH_SERVICE *sv;
AQH_STORAGE *sto;
const AQH_DEVICE_LIST *rl;
sv=AQH_HttpUrlHandler_GetHttpService(uh);
sto=AqHomeHttpService_GetStorage(sv);
GWEN_Buffer_AppendArgs(pageBuf,
"<h2>%s</h2>"
"<table class=\"dataTable\">"
"<thead>"
" <tr><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th></th></tr>"
"</thead>",
I18N("Devices"),
I18N("Name"),
I18N("Room"),
I18N("Type"),
I18N("Location"),
I18N("Description"));
GWEN_Buffer_AppendString(pageBuf, "<tbody>");
rl=AQH_Storage_GetDeviceList(sto);
if (rl) {
const AQH_DEVICE *device;
device=AQH_Device_List_First(rl);
while(device) {
long unsigned int id;
int roomId;
const char *name;
const char *roomName=NULL;
const char *devType;
const char *descr;
const char *location;
const AQH_ROOM *r=NULL;
id=(long unsigned int) AQH_Device_GetId(device);
roomId=(long unsigned int) AQH_Device_GetRoomId(device);
if (roomId>0)
r=AQH_Storage_GetRoomById(sto, roomId);
if (r)
roomName=AQH_Room_GetName(r);
name=AQH_Device_GetName(device);
devType=AQH_Device_GetDeviceType(device);
descr=AQH_Device_GetDescription(device);
location=AQH_Device_GetLocation(device);
GWEN_Buffer_AppendArgs(pageBuf,
"<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td>"
"<td><a href=\"/devices/edit/%lu\">"
"<IMG src=\"/pics/edit.png\" width=32 height=32 align=left border=0></a></td>"
"</tr>",
name?name:"",
roomName?roomName:"",
devType?devType:"",
location?location:"",
descr?descr:"", id);
device=AQH_Device_List_Next(device);
}
}
GWEN_Buffer_AppendString(pageBuf, "</tbody>");
GWEN_Buffer_AppendArgs(pageBuf, "</table><a href=\"/devices/add\">%s</a><br>", I18N("Add Device"));
}

View File

@@ -1,23 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_U_DEVICES_H
#define AQHOME_STORAGE_U_DEVICES_H
#include "aqhome/http/urlhandler.h"
AQH_HTTP_URLHANDLER *AQH_DevicesHttpUrlHandler_new(AQH_SERVICE *sv);
#endif

View File

@@ -1,394 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./u_login.h"
#include "aqhome/http/httpservice.h"
#include "aqhome/http/httpservice_http.h"
#include "aqhome/http/httpservice_conf.h"
#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/args.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/endpoint.h>
#include <gwenhywfar/mdigest.h>
#include <gwenhywfar/text.h>
#include <gwenhywfar/timestamp.h>
static int _handleUrl(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq);
static int _handleGet(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq);
static int _handlePost(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq);
static int _loginUser(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq);
static int _verifyPass(AQH_USER *u, const char *userName, const char *password);
static AQH_SESSION *_generateSessionForUser(AQH_SERVICE *sv, AQH_USER *u);
static GWEN_BUFFER *_generateSessionUid(void);
static void _headerSetCookie(GWEN_DB_NODE *db, uint32_t flags, const char *cookieName, const char *cookieValue);
static GWEN_MSG *_createLoginResponseMsg(AQH_SERVICE *sv, const char *protocol, const char *sessionId, const char *newPage);
AQH_HTTP_URLHANDLER *AQH_LoginHttpUrlHandler_new(AQH_SERVICE *sv)
{
AQH_HTTP_URLHANDLER *uh;
uh=AQH_HttpUrlHandler_new(sv);
AQH_HttpUrlHandler_SetHandleFn(uh, _handleUrl);
return uh;
}
int _handleUrl(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq)
{
const char *protocol;
const char *cmd;
GWEN_MSG *msgOut=NULL;
protocol=AQH_HttpRequest_GetProtocol(rq);
cmd=AQH_HttpRequest_GetCommand(rq);
if (cmd && *cmd) {
int rv;
if (strcasecmp(cmd, "GET")==0) {
rv=_handleGet(uh, rq);
if (rv<0) {
DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
return rv;
}
}
else if (strcasecmp(cmd, "POST")==0) {
rv=_handlePost(uh, rq);
if (rv<0) {
DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
return rv;
}
}
else {
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 405, "Method not allowed", protocol, NULL);
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
}
return 0;
}
else {
DBG_ERROR(AQH_LOGDOMAIN, "No command in request");
return GWEN_ERROR_INVALID;
}
}
int _handleGet(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq)
{
GWEN_BUFFER *pageBuf;
int rv;
GWEN_MSG *msgOut=NULL;
const char *protocol;
protocol=AQH_HttpRequest_GetProtocol(rq);
pageBuf=GWEN_Buffer_new(0, 256, 0, 1);
rv=AQH_HttpUrlHandler_AddContentHeaders(uh, AQH_HTTP_CONTENT_MODE_DESKTOP, pageBuf);
if (rv<0) {
DBG_ERROR(AQH_LOGDOMAIN, "Error adding headers");
GWEN_Buffer_free(pageBuf);
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 500, "Internal Error", protocol, NULL);
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
return 0;
}
rv=AQH_HttpService_AddFile(AQH_HttpUrlHandler_GetHttpService(uh), NULL, "login.html", pageBuf);
if (rv<0) {
DBG_ERROR(AQH_LOGDOMAIN, "Error reading file \"login.html\"");
GWEN_Buffer_free(pageBuf);
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 500, "Internal Error", protocol, NULL);
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
return 0;
}
rv=AQH_HttpUrlHandler_AddContentFooters(uh, AQH_HTTP_CONTENT_MODE_DESKTOP, pageBuf);
if (rv<0) {
DBG_ERROR(AQH_LOGDOMAIN, "Error adding footers");
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 500, "Internal Error", protocol, NULL);
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
GWEN_Buffer_free(pageBuf);
return rv;
}
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 200, "OK", protocol, GWEN_Buffer_GetStart(pageBuf));
GWEN_Buffer_free(pageBuf);
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
return 0;
}
int _handlePost(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq)
{
GWEN_DB_NODE *db;
int rv;
DBG_ERROR(NULL, "Login POST:");
db=AQH_HttpRequest_GetDbPostBody(rq);
GWEN_DB_Dump(db, 2);
rv=_loginUser(uh, rq);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
return 0;
}
int _loginUser(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq)
{
AQH_SERVICE *sv;
GWEN_MSG *msgOut=NULL;
const char *protocol;
GWEN_DB_NODE *db;
const char *userName;
const char *password;
AQH_USER *u;
AQH_SESSION *session;
int i;
int rv;
sv=AQH_HttpUrlHandler_GetHttpService(uh);
db=AQH_HttpRequest_GetDbPostBody(rq);
protocol=AQH_HttpRequest_GetProtocol(rq);
userName=GWEN_DB_GetCharValue(db, "username", 0, NULL);
password=GWEN_DB_GetCharValue(db, "password", 0, NULL);
if (!(userName && *userName && password && *password)) {
msgOut=AQH_HttpService_CreateResponseMsg(sv, 401, "Unauthorized", protocol, NULL);
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
return 0;
}
u=AQH_HttpService_GetUser(sv, userName);
if (u==NULL) {
DBG_INFO(NULL, "User \"%s\" not found", userName);
/* TODO: sleep */
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 401, "Unauthorized", protocol, NULL);
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
return 0;
}
i=AQH_User_GetState(u);
if (i==AQH_UserState_Suspended) {
DBG_INFO(NULL, "User \"%s\" suspended", userName);
/* TODO: sleep */
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 401, "Unauthorized", protocol, NULL);
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
return 0;
}
rv=_verifyPass(u, userName, password);
if (rv<0) {
if (rv==GWEN_ERROR_VERIFY)
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 401, "Unauthorized", protocol, "error");
else
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 500, "Internal Error", protocol, NULL);
/* TODO: sleep */
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
return 0;
}
session=_generateSessionForUser(sv, u);
if (session==NULL) {
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 500, "Internal Error", protocol, NULL);
/* TODO: sleep */
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
return 0;
}
DBG_ERROR(NULL, "Session generated");
AQH_User_SetTimestampLastLogin(u, AQH_Session_GetTimestampCreation(session));
rv=AQH_HttpService_WriteUser(sv, u);
if (rv<0) {
DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 500, "Internal Error", protocol, NULL);
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
return 0;
}
msgOut=_createLoginResponseMsg(sv, protocol, AQH_Session_GetUid(session), "/rooms/list");
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
return 0;
}
int _verifyPass(AQH_USER *u, const char *userName, const char *password)
{
GWEN_MDIGEST *md;
int rv;
const char *storedPasswordHash;
char buffer[70];
storedPasswordHash=AQH_User_GetHashedPassword(u);
if (!(storedPasswordHash && *storedPasswordHash)) {
DBG_ERROR(NULL, "No password hash stored with user \"%s\"", userName);
return GWEN_ERROR_INTERNAL;
}
md=GWEN_MDigest_Sha256_new();
rv=GWEN_MDigest_Begin(md);
if (rv<0) {
DBG_ERROR(NULL, "Error digesting given password [begin] (%d)", rv);
GWEN_MDigest_free(md);
return rv;
}
rv=GWEN_MDigest_Update(md, (const uint8_t*) password, strlen(password));
if (rv<0) {
DBG_ERROR(NULL, "Error digesting given password [update] (%d)", rv);
GWEN_MDigest_free(md);
return rv;
}
rv=GWEN_MDigest_End(md);
if (rv<0) {
DBG_ERROR(NULL, "Error digesting given password [end] (%d)", rv);
GWEN_MDigest_free(md);
return rv;
}
DBG_ERROR(NULL, "Digest needs %d bytes", (GWEN_MDigest_GetDigestSize(md)*2)+1);
if (NULL==GWEN_Text_ToHex((const char*) GWEN_MDigest_GetDigestPtr(md), GWEN_MDigest_GetDigestSize(md), buffer, sizeof(buffer)-1)) {
DBG_ERROR(NULL, "Buffer too small (need %d)", (GWEN_MDigest_GetDigestSize(md)*2)+1);
GWEN_MDigest_free(md);
return GWEN_ERROR_INTERNAL;
}
GWEN_MDigest_free(md);
if (strcasecmp(buffer, storedPasswordHash)!=0) {
DBG_ERROR(NULL, "Bad password for user \"%s\"", userName);
return GWEN_ERROR_VERIFY;
}
DBG_ERROR(NULL, "Valid password for user \"%s\"", userName);
return 0;
}
AQH_SESSION *_generateSessionForUser(AQH_SERVICE *sv, AQH_USER *u)
{
AQH_SESSION *session;
GWEN_BUFFER *buf;
GWEN_TIMESTAMP *ts;
int rv;
buf=_generateSessionUid();
if (buf==NULL) {
DBG_INFO(NULL, "here");
return NULL;
}
DBG_INFO(NULL, "New session id: [%s]", GWEN_Buffer_GetStart(buf));
session=AQH_Session_new();
AQH_Session_SetUid(session, GWEN_Buffer_GetStart(buf));
GWEN_Buffer_free(buf);
ts=GWEN_Timestamp_NowInLocalTime();
AQH_Session_SetTimestampCreation(session, ts);
AQH_Session_SetTimestampLastAccess(session, ts);
GWEN_Timestamp_free(ts);
AQH_Session_SetUserAlias(session, AQH_User_GetAlias(u));
AQH_Session_SetUser(session, u);
rv=AQH_HttpService_AddSession(sv, session);
if (rv<0) {
DBG_INFO(NULL, "Error adding session \"%s\" (%d)", AQH_Session_GetUid(session), rv);
AQH_Session_free(session);
return NULL;
}
return session;
}
GWEN_BUFFER *_generateSessionUid(void)
{
uint8_t binbuf[8];
GWEN_BUFFER *buf;
int rv;
rv=GWEN_SyncIo_Helper_PartiallyReadFile("/dev/urandom", binbuf, sizeof(binbuf));
if (rv<sizeof(binbuf)) {
DBG_ERROR(NULL, "Error reading from /dev/urandom: %d", rv);
return NULL;
}
buf=GWEN_Buffer_new(0, 20, 0, 1);
rv=GWEN_Text_ToHexBuffer((const char*) binbuf, rv, buf, 0, 0, 0);
if (rv<0) {
DBG_ERROR(NULL, "Error converting random bytes to hex (%d)", rv);
GWEN_Buffer_free(buf);
return NULL;
}
return buf;
}
GWEN_MSG *_createLoginResponseMsg(AQH_SERVICE *sv, const char *protocol, const char *sessionId, const char *newPage)
{
GWEN_BUFFER *buf;
GWEN_MSG *msg;
GWEN_DB_NODE *db;
buf=GWEN_Buffer_new(0, 256, 0, 1);
AQH_HttpService_AddStatusLine(sv, 303, "OK, redirecting to content", protocol, buf);
db=GWEN_DB_Group_new("header");
GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Connection", "Keep-Alive");
GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Content-Type", "text/html; charset=utf-8");
_headerSetCookie(db, GWEN_DB_FLAGS_OVERWRITE_VARS, AQH_HTTP_REQUEST_SESSIONCOOKIE, sessionId);
GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Location", newPage);
GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Content-Length", 0);
AQH_HttpService_AddHeader(sv, db, buf);
GWEN_DB_Group_free(db);
msg=GWEN_Msg_fromBytes((const uint8_t*)GWEN_Buffer_GetStart(buf), GWEN_Buffer_GetUsedBytes(buf));
GWEN_Buffer_free(buf);
return msg;
}
void _headerSetCookie(GWEN_DB_NODE *db, uint32_t flags, const char *cookieName, const char *cookieValue)
{
if (cookieName && cookieValue) {
GWEN_BUFFER *dbuf;
dbuf=GWEN_Buffer_new(0, 32, 0, 1);
GWEN_Buffer_AppendArgs(dbuf, "%s=%s", cookieName, cookieValue);
GWEN_DB_SetCharValue(db, flags, "Set-Cookie", GWEN_Buffer_GetStart(dbuf));
GWEN_Buffer_free(dbuf);
}
}

View File

@@ -1,24 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_U_LOGIN_H
#define AQHOME_STORAGE_U_LOGIN_H
#include "aqhome/http/urlhandler.h"
#include <gwenhywfar/endpoint.h>
AQH_HTTP_URLHANDLER *AQH_LoginHttpUrlHandler_new(AQH_SERVICE *sv);
#endif

View File

@@ -1,379 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./u_mqtttopics.h"
#include "./u_objects.h"
#include "./aqhomehttp.h"
#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/i18n.h>
/* ------------------------------------------------------------------------------------------------
* defines
* ------------------------------------------------------------------------------------------------
*/
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static int _addOrEditObject(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *db, int id);
static GWEN_DB_NODE *_findObjectByIdAndReturnAsDb(AQH_HTTP_URLHANDLER *uh, int id);
static int _writeAddPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf);
static int _writeEditPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf);
static void _listObjectsIntoBuffer(AQH_HTTP_URLHANDLER *uh, GWEN_BUFFER *pageBuf);
static void _writeEditingTable(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf);
static void _setFromObject(AQH_MQTT_TOPIC *mqttTopic, const AQH_MQTT_TOPIC *srcMqttTopic);
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
AQH_HTTP_URLHANDLER *AQH_MqttTopicsHttpUrlHandler_new(AQH_SERVICE *sv)
{
AQH_HTTP_URLHANDLER *uh;
uh=AQH_ObjectsHttpUrlHandler_new(sv,
AQHOME_HTTP_PERMS_LIST_TOPICS,
AQHOME_HTTP_PERMS_ADD_TOPIC,
AQHOME_HTTP_PERMS_DEL_TOPIC,
AQHOME_HTTP_PERMS_EDIT_TOPIC,
"/mqtttopics/list");
AQH_ObjectsHttpUrlHandler_SetAddOrEditObjectFn(uh, _addOrEditObject);
AQH_ObjectsHttpUrlHandler_SetFindObjectByIdAndReturnAsDbFn(uh, _findObjectByIdAndReturnAsDb);
AQH_ObjectsHttpUrlHandler_SetWriteAddPageFn(uh, _writeAddPage);
AQH_ObjectsHttpUrlHandler_SetWriteEditPageFn(uh, _writeEditPage);
AQH_ObjectsHttpUrlHandler_SetListObjectsIntoBufferFn(uh, _listObjectsIntoBuffer);
return uh;
}
int _addOrEditObject(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *db, int id)
{
AQH_SERVICE *sv;
AQH_STORAGE *sto;
AQH_MQTT_TOPIC *newMqttTopic;
const char *topic;
int rv;
sv=AQH_HttpUrlHandler_GetHttpService(uh);
sto=AqHomeHttpService_GetStorage(sv);
newMqttTopic=AQH_MqttTopic_fromDb(db);
topic=AQH_MqttTopic_GetTopic(newMqttTopic);
if (!(topic && *topic)) {
DBG_INFO(NULL, "Missing mqttTopic topic");
AQH_MqttTopic_free(newMqttTopic);
return GWEN_ERROR_INVALID;
}
rv=AqHomeHttpService_LockStorage(sv);
if (rv<0) {
DBG_ERROR(NULL, "Error locking storage");
AQH_MqttTopic_free(newMqttTopic);
return GWEN_ERROR_IO;
}
if (id>0) {
AQH_MQTT_TOPIC *mqttTopic;
DBG_INFO(NULL, "Edit existing mqttTopic");
mqttTopic=AQH_Storage_GetMqttTopicById(sto, id);
if (mqttTopic==NULL) {
AqHomeHttpService_UnlockStorage(sv);
DBG_ERROR(NULL, "MqttTopic %d not found", id);
AQH_MqttTopic_free(newMqttTopic);
return GWEN_ERROR_NOT_FOUND;
}
AQH_MqttTopic_SetId(mqttTopic, id);
_setFromObject(mqttTopic, newMqttTopic);
AQH_Storage_AddRuntimeFlags(sto, AQH_STORAGE_RTFLAGS_MODIFIED);
}
else {
AQH_MQTT_TOPIC *mqttTopic;
DBG_INFO(NULL, "Adding new mqttTopic");
mqttTopic=AQH_Storage_GetMqttTopicByTopic(sto, topic);
if (mqttTopic) {
AqHomeHttpService_UnlockStorage(sv);
DBG_ERROR(NULL, "MqttTopic %s exists", topic);
AQH_MqttTopic_free(newMqttTopic);
return GWEN_ERROR_FOUND;
}
mqttTopic=AQH_MqttTopic_new();
AQH_MqttTopic_SetId(mqttTopic, 0);
_setFromObject(mqttTopic, newMqttTopic);
AQH_Storage_AddMqttTopic(sto, mqttTopic);
AQH_Storage_AddRuntimeFlags(sto, AQH_STORAGE_RTFLAGS_MODIFIED);
}
AQH_MqttTopic_free(newMqttTopic);
rv=AqHomeHttpService_UnlockStorage(sv);
if (rv<0) {
DBG_ERROR(NULL, "Error unlocking storage");
return GWEN_ERROR_IO;
}
return 0;
}
void _setFromObject(AQH_MQTT_TOPIC *mqttTopic, const AQH_MQTT_TOPIC *srcMqttTopic)
{
AQH_MqttTopic_SetDeviceId(mqttTopic, AQH_MqttTopic_GetDeviceId(srcMqttTopic));
AQH_MqttTopic_SetTopic(mqttTopic, AQH_MqttTopic_GetTopic(srcMqttTopic));
AQH_MqttTopic_SetDataType(mqttTopic, AQH_MqttTopic_GetDataType(srcMqttTopic));
}
GWEN_DB_NODE *_findObjectByIdAndReturnAsDb(AQH_HTTP_URLHANDLER *uh, int id)
{
AQH_SERVICE *sv;
AQH_STORAGE *sto;
const AQH_MQTT_TOPIC *mqttTopic;
sv=AQH_HttpUrlHandler_GetHttpService(uh);
sto=AqHomeHttpService_GetStorage(sv);
mqttTopic=AQH_Storage_GetMqttTopicById(sto, id);
if (mqttTopic) {
GWEN_DB_NODE *db;
db=GWEN_DB_Group_new("mqttTopic");
AQH_MqttTopic_toDb(mqttTopic, db);
return db;
}
return NULL;
}
int _writeAddPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf)
{
GWEN_Buffer_AppendArgs(pageBuf,
"<h2>%s</h2><br>\n"
"<form action=\"/mqtttopics/add\" method=\"post\" enctype=\"application/x-www-form-urlencoded\">",
I18N("Add MQTT Topic"));
_writeEditingTable(uh, dbValues, pageBuf);
GWEN_Buffer_AppendArgs(pageBuf, "<input type=\"submit\" value=\"%s\"></form>", I18N("Submit"));
return 0;
}
int _writeEditPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf)
{
GWEN_Buffer_AppendArgs(pageBuf,
"<h2>%s</h2><br>\n"
"<form action=\"/mqtttopics/edit/%lu\" method=\"post\" enctype=\"application/x-www-form-urlencoded\">",
I18N("Edit MQTT Topic"),
(long unsigned int)(dbValues?GWEN_DB_GetIntValue(dbValues, "id", 0, 0):0));
_writeEditingTable(uh, dbValues, pageBuf);
GWEN_Buffer_AppendArgs(pageBuf, "<input type=\"submit\" value=\"%s\"></form>", I18N("Submit"));
return 0;
}
void _writeEditingTable(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf)
{
AQH_SERVICE *sv;
AQH_STORAGE *sto;
const GWEN_STRINGLIST *seenTopicsList;
const AQH_DEVICE_LIST *deviceList;
unsigned long int selectedDeviceId=0;
int dataType;
char numbuf[16];
sv=AQH_HttpUrlHandler_GetHttpService(uh);
sto=AqHomeHttpService_GetStorage(sv);
deviceList=AQH_Storage_GetDeviceList(sto);
selectedDeviceId=(unsigned long int)(dbValues?GWEN_DB_GetIntValue(dbValues, "deviceId", 0, 0):0);
snprintf(numbuf, sizeof(numbuf)-1, "%lu", selectedDeviceId);
numbuf[sizeof(numbuf)-1]=0;
dataType=dbValues?GWEN_DB_GetIntValue(dbValues, "dataType", 0, AQH_MqttTopicType_Num):0;
/* topic */
GWEN_Buffer_AppendArgs(pageBuf,
"<table>"
"<tr>"
" <td><label for=\"topic\">%s: </label></td>"
" <td>"
" <input list=\"seenTopics\" type=\"text\" name=\"topic\" id=\"topic\" value=\"%s\" size=\"64\" required>\n"
" <datalist id=\"seenTopics\">",
I18N("MQTT Topic"),
dbValues?GWEN_DB_GetCharValue(dbValues, "topic", 0, ""):"");
seenTopicsList=AQH_Storage_GetRecvdTopicList(sto);
if (seenTopicsList && GWEN_StringList_Count(seenTopicsList)) {
GWEN_STRINGLISTENTRY *se;
se=GWEN_StringList_FirstEntry(seenTopicsList);
while(se) {
const char *s=GWEN_StringListEntry_Data(se);
if (s && *s) {
DBG_INFO(NULL, "Adding MQTT string %s", s);
GWEN_Buffer_AppendArgs(pageBuf, "<option value=\"%s\">\n", s);
}
se=GWEN_StringListEntry_Next(se);
}
}
else {
}
GWEN_Buffer_AppendString(pageBuf, "</datalist></td></tr>");
/* device */
GWEN_Buffer_AppendArgs(pageBuf,
"<tr><td><label for=\"deviceId\">%s: </label></td>"
"<td><select id=\"deviceId\" name=\"deviceId\">",
I18N("Device"));
GWEN_Buffer_AppendArgs(pageBuf, "<option value=\"0\">%s</option>\n", I18N("-- select device --"));
if (deviceList) {
const AQH_DEVICE *device;
device=AQH_Device_List_First(deviceList);
while(device) {
const char *deviceName;
deviceName=AQH_Device_GetName(device);
if (deviceName && *deviceName) {
snprintf(numbuf, sizeof(numbuf)-1, "%lu", (unsigned long int) AQH_Device_GetId(device));
numbuf[sizeof(numbuf)-1]=0;
if (selectedDeviceId && AQH_Device_GetId(device)==selectedDeviceId)
GWEN_Buffer_AppendArgs(pageBuf, "<option value=\"%s\" selected>%s</option>\n", numbuf, deviceName);
else
GWEN_Buffer_AppendArgs(pageBuf, "<option value=\"%s\">%s</option>\n", numbuf, deviceName);
}
device=AQH_Device_List_Next(device);
}
}
GWEN_Buffer_AppendString(pageBuf, "</select></td></tr>\n");
/* data type */
GWEN_Buffer_AppendArgs(pageBuf,
"<tr><td><label for=\"dataType\">%s: </label></td>"
"<td><select id=\"dataType\" name=\"dataType\">",
I18N("Data Type"));
if (dataType==AQH_MqttTopicType_Num)
GWEN_Buffer_AppendArgs(pageBuf, "<option value=\"%d\" selected>%s</option>", AQH_MqttTopicType_Num, I18N("numeric"));
else
GWEN_Buffer_AppendArgs(pageBuf, "<option value=\"%d\" >%s</option>", AQH_MqttTopicType_Num, I18N("numeric"));
if (dataType==AQH_MqttTopicType_Json)
GWEN_Buffer_AppendArgs(pageBuf, "<option value=\"%d\" selected>%s</option>", AQH_MqttTopicType_Json, I18N("JSON"));
else
GWEN_Buffer_AppendArgs(pageBuf, "<option value=\"%d\" >%s</option>", AQH_MqttTopicType_Json, I18N("JSON"));
GWEN_Buffer_AppendString(pageBuf, "</select></td></tr>\n");
if (seenTopicsList && GWEN_StringList_Count(seenTopicsList)) {
GWEN_STRINGLISTENTRY *se;
GWEN_Buffer_AppendArgs(pageBuf, "<tr><td valign=\"top\"><label for=\"receivedTopicsList\">%s: </label></td>", I18N("Received Topics"));
GWEN_Buffer_AppendString(pageBuf, "<td><output name=\"receivedTopicsList\" id=\"receivedTopicsList\">");
se=GWEN_StringList_FirstEntry(seenTopicsList);
while(se) {
const char *s=GWEN_StringListEntry_Data(se);
if (s && *s)
GWEN_Buffer_AppendArgs(pageBuf, "%s<br>\n", s);
se=GWEN_StringListEntry_Next(se);
}
GWEN_Buffer_AppendString(pageBuf, "</output></td></tr>");
}
GWEN_Buffer_AppendString(pageBuf, "</tbody></table>\n");
}
void _listObjectsIntoBuffer(AQH_HTTP_URLHANDLER *uh, GWEN_BUFFER *pageBuf)
{
AQH_SERVICE *sv;
AQH_STORAGE *sto;
const AQH_MQTT_TOPIC_LIST *rl;
sv=AQH_HttpUrlHandler_GetHttpService(uh);
sto=AqHomeHttpService_GetStorage(sv);
GWEN_Buffer_AppendArgs(pageBuf,
"<h2>%s</h2>"
"<table class=\"dataTable\">"
"<thead>"
" <tr><th>%s</th><th>%s</th><th>%s</th><th></th></tr>"
"</thead>",
I18N("Mqtt Topics"),
I18N("Topic"),
I18N("Device"),
I18N("Datatype"));
GWEN_Buffer_AppendString(pageBuf, "<tbody>");
rl=AQH_Storage_GetMqttTopicList(sto);
if (rl) {
const AQH_MQTT_TOPIC *mqttTopic;
mqttTopic=AQH_MqttTopic_List_First(rl);
while(mqttTopic) {
long unsigned int id;
int deviceId;
const char *topic;
int dataType;
const char *deviceName=NULL;
const AQH_DEVICE *device=NULL;
id=(long unsigned int) AQH_MqttTopic_GetId(mqttTopic);
deviceId=(long unsigned int) AQH_MqttTopic_GetDeviceId(mqttTopic);
if (deviceId>0)
device=AQH_Storage_GetDeviceById(sto, deviceId);
if (device)
deviceName=AQH_Device_GetName(device);
topic=AQH_MqttTopic_GetTopic(mqttTopic);
dataType=AQH_MqttTopic_GetDataType(mqttTopic);
GWEN_Buffer_AppendArgs(pageBuf,
"<tr><td>%s</td><td>%s</td><td>%s</td>"
"<td><a href=\"/mqtttopics/edit/%lu\">"
"<IMG src=\"/pics/edit.png\" width=32 height=32 align=left border=0></a></td>"
"</tr>",
topic?topic:"",
deviceName?deviceName:"",
AQH_MqttTopicType_toString(dataType),
id);
mqttTopic=AQH_MqttTopic_List_Next(mqttTopic);
}
}
GWEN_Buffer_AppendString(pageBuf, "</tbody>");
GWEN_Buffer_AppendArgs(pageBuf, "</table><a href=\"/mqtttopics/add\">%s</a><br>", I18N("Add MqttTopic"));
}

View File

@@ -1,23 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_U_MQTTTOPICS_H
#define AQHOME_STORAGE_U_MQTTTOPICS_H
#include "aqhome/http/urlhandler.h"
AQH_HTTP_URLHANDLER *AQH_MqttTopicsHttpUrlHandler_new(AQH_SERVICE *sv);
#endif

View File

@@ -1,458 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./u_objects_p.h"
#include "./u_base.h"
#include "./aqhomehttp.h"
#include "aqhome/http/httpservice_http.h"
#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/i18n.h>
GWEN_INHERIT(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS);
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static void GWENHYWFAR_CB _freeData(void *bp, void *p);
static int _handleUrl(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq);
static GWEN_MSG *_handleGet(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq);
static GWEN_MSG *_handlePost(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq);
static void _handleGetList(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_BUFFER *pageBuf);
static void _handleGetAdd(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_BUFFER *pageBuf);
static void _handleGetEdit(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, int id, GWEN_BUFFER *pageBuf);
static GWEN_MSG *_handlePostAdd(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq);
static GWEN_MSG *_handlePostEdit(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, int id);
static int _writeAddPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf);
static int _writeEditPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf);
static GWEN_MSG *_addOrEditAndCreateResponse(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, int id, AQH_HTTP_URLHANDLER_WRITEPAGE_CB cb);
static int _addOrEditObject(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *db, int id);
static GWEN_DB_NODE *_findObjectByIdAndReturnAsDb(AQH_HTTP_URLHANDLER *uh, int id);
static void _listObjectsIntoBuffer(AQH_HTTP_URLHANDLER *uh, GWEN_BUFFER *pageBuf);
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
AQH_HTTP_URLHANDLER *AQH_ObjectsHttpUrlHandler_new(AQH_SERVICE *sv,
uint32_t neededPermsList,
uint32_t neededPermsAdd,
uint32_t neededPermsDel,
uint32_t neededPermsEdit,
const char *urlForObjectList)
{
AQH_HTTP_URLHANDLER *uh;
AQH_URLHANDLER_OBJECTS *xuh;
uh=AQH_HttpUrlHandler_new(sv);
GWEN_NEW_OBJECT(AQH_URLHANDLER_OBJECTS, xuh);
GWEN_INHERIT_SETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh, xuh, _freeData);
AQH_HttpUrlHandler_SetHandleFn(uh, _handleUrl);
xuh->neededPermsList=neededPermsList;
xuh->neededPermsAdd=neededPermsAdd;
xuh->neededPermsDel=neededPermsDel;
xuh->neededPermsEdit=neededPermsEdit;
xuh->urlForObjectList=urlForObjectList?strdup(urlForObjectList):NULL;
return uh;
}
void _freeData(void *bp, void *p)
{
AQH_URLHANDLER_OBJECTS *xuh;
xuh=(AQH_URLHANDLER_OBJECTS*)p;
free(xuh->urlForObjectList);
GWEN_FREE_OBJECT(xuh);
}
void AQH_ObjectsHttpUrlHandler_SetAddOrEditObjectFn(AQH_HTTP_URLHANDLER *uh, AQH_OBJECTSHTTPURLHANDLER_ADDOREDITOBJECT_FN fn)
{
AQH_URLHANDLER_OBJECTS *xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh);
if (xuh) {
xuh->addOrEditObjectFn=fn;
}
}
void AQH_ObjectsHttpUrlHandler_SetFindObjectByIdAndReturnAsDbFn(AQH_HTTP_URLHANDLER *uh,
AQH_OBJECTSHTTPURLHANDLER_FINDOBJECTBYIDANDRETURNASDB_FN fn)
{
AQH_URLHANDLER_OBJECTS *xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh);
if (xuh) {
xuh->findObjectByIdAndReturnAsDbFn=fn;
}
}
void AQH_ObjectsHttpUrlHandler_SetWriteAddPageFn(AQH_HTTP_URLHANDLER *uh, AQH_OBJECTSHTTPURLHANDLER_WRITEADDPAGE_FN fn)
{
AQH_URLHANDLER_OBJECTS *xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh);
if (xuh) {
xuh->writeAddPageFn=fn;
}
}
void AQH_ObjectsHttpUrlHandler_SetWriteEditPageFn(AQH_HTTP_URLHANDLER *uh, AQH_OBJECTSHTTPURLHANDLER_WRITEEDITPAGE_FN fn)
{
AQH_URLHANDLER_OBJECTS *xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh);
if (xuh) {
xuh->writeEditPageFn=fn;
}
}
void AQH_ObjectsHttpUrlHandler_SetListObjectsIntoBufferFn(AQH_HTTP_URLHANDLER *uh,
AQH_OBJECTSHTTPURLHANDLER_LISTOBJECTSINTOBUFFER_FN fn)
{
AQH_URLHANDLER_OBJECTS *xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh);
if (xuh) {
xuh->listObjectsIntoBufferFn=fn;
}
}
int _handleUrl(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq)
{
if (GWEN_INHERIT_ISOFTYPE(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh)) {
const char *protocol;
const char *cmd;
GWEN_MSG *msgOut;
AQH_HttpService_SetupModuleAndPerms(AQH_HttpUrlHandler_GetHttpService(uh), rq, "aqhome");
protocol=AQH_HttpRequest_GetProtocol(rq);
AQH_HttpRequest_SetupUrlPathMembers(rq);
if (AQH_HttpRequest_GetUrlPathMembers(rq)==NULL) {
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 500, "Internal Error", protocol, NULL);
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
return 0;
}
cmd=AQH_HttpRequest_GetCommand(rq);
if (cmd && *cmd) {
if (strcasecmp(cmd, "GET")==0)
msgOut=_handleGet(uh, rq);
else if (strcasecmp(cmd, "POST")==0)
msgOut=_handlePost(uh, rq);
else {
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 405, "Method not allowed", protocol, NULL);
}
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
return 0;
}
else {
DBG_ERROR(AQH_LOGDOMAIN, "No command in request");
return GWEN_ERROR_INVALID;
}
}
else {
DBG_ERROR(NULL, "Not an AQH_URLHANDLER_OBJECTS object");
return GWEN_ERROR_INTERNAL;
}
}
GWEN_MSG *_handleGet(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq)
{
GWEN_BUFFER *pageBuf;
int rv;
GWEN_MSG *msgOut=NULL;
const char *protocol;
const GWEN_STRINGLIST *sl;
const char *s;
protocol=AQH_HttpRequest_GetProtocol(rq);
pageBuf=GWEN_Buffer_new(0, 2048, 0, 1);
/* header */
rv=AQH_HttpUrlHandler_AddContentHeaders(uh, AQH_HTTP_CONTENT_MODE_DESKTOP, pageBuf);
if (rv<0) {
DBG_ERROR(AQH_LOGDOMAIN, "Error adding headers");
GWEN_Buffer_free(pageBuf);
return AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 500, "Internal Error", protocol, NULL);
}
/* middle part (header - middle - footer) */
sl=AQH_HttpRequest_GetUrlPathMembers(rq);
s=GWEN_StringList_StringAt(sl, 1);
if (!(s && *s))
s="list";
if (strcasecmp(s, "list")==0)
_handleGetList(uh, rq, pageBuf);
else if (strcasecmp(s, "add")==0)
_handleGetAdd(uh, rq, pageBuf);
else if (strcasecmp(s, "edit")==0)
_handleGetEdit(uh, rq, GWEN_StringList_StringAsIntAt(sl, 2, 0), pageBuf);
else {
DBG_ERROR(NULL, "Invalid url (2nd member is [%s])", s);
GWEN_Buffer_free(pageBuf);
return AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 404, "Not found", protocol, NULL);
}
/* footer */
rv=AQH_HttpUrlHandler_AddContentFooters(uh, AQH_HTTP_CONTENT_MODE_DESKTOP, pageBuf);
if (rv<0) {
DBG_ERROR(AQH_LOGDOMAIN, "Error adding footers");
GWEN_Buffer_free(pageBuf);
return AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 500, "Internal Error", protocol, NULL);
}
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 200, "OK", protocol, GWEN_Buffer_GetStart(pageBuf));
GWEN_Buffer_free(pageBuf);
return msgOut;
}
GWEN_MSG *_handlePost(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq)
{
GWEN_DB_NODE *db;
const GWEN_STRINGLIST *sl;
const char *protocol;
const char *s;
DBG_ERROR(NULL, "POST:");
db=AQH_HttpRequest_GetDbPostBody(rq);
GWEN_DB_Dump(db, 2);
protocol=AQH_HttpRequest_GetProtocol(rq);
sl=AQH_HttpRequest_GetUrlPathMembers(rq);
s=GWEN_StringList_StringAt(sl, 1);
if (s && *s) {
if (strcasecmp(s, "add")==0)
return _handlePostAdd(uh, rq);
else if (strcasecmp(s, "edit")==0)
return _handlePostEdit(uh, rq, GWEN_StringList_StringAsIntAt(sl, 2, 0));
else {
DBG_ERROR(NULL, "Invalid url (2nd member is [%s])", s);
return AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 404, "Not found", protocol, NULL);
}
}
else {
DBG_ERROR(NULL, "Invalid url (2nd member missing)");
return AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 404, "Not found", protocol, NULL);
}
}
void _handleGetList(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_BUFFER *pageBuf)
{
AQH_URLHANDLER_OBJECTS *xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh);
uint32_t perms;
DBG_ERROR(NULL, "LIST");
perms=AQH_HttpRequest_GetModulePerms(rq);
if (perms & xuh->neededPermsList) {
_listObjectsIntoBuffer(uh, pageBuf);
}
else {
GWEN_Buffer_AppendArgs(pageBuf, "<p>%s</p>", I18N("No permissions to see list of objects."));
}
}
void _handleGetAdd(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_BUFFER *pageBuf)
{
AQH_URLHANDLER_OBJECTS *xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh);
uint32_t perms;
DBG_ERROR(NULL, "ADD");
perms=AQH_HttpRequest_GetModulePerms(rq);
if (perms & xuh->neededPermsAdd) {
_writeAddPage(uh, rq, NULL, pageBuf);
}
else {
DBG_INFO(NULL, "No permissions to add.");
GWEN_Buffer_AppendArgs(pageBuf, "<p><font color=\"red\">%s</font></p>", I18N("No permissions to add an object."));
}
}
void _handleGetEdit(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, int id, GWEN_BUFFER *pageBuf)
{
AQH_URLHANDLER_OBJECTS *xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh);
uint32_t perms;
DBG_ERROR(NULL, "EDIT");
perms=AQH_HttpRequest_GetModulePerms(rq);
if (perms & xuh->neededPermsEdit) {
if (id>0) {
GWEN_DB_NODE *db;
db=_findObjectByIdAndReturnAsDb(uh, id);
if (db) {
_writeEditPage(uh, rq, db, pageBuf);
GWEN_DB_Group_free(db);
}
else {
DBG_ERROR(NULL, "Object %d not found", id);
GWEN_Buffer_AppendArgs(pageBuf, "<p><font color=\"red\">%s</font></p>", I18N("Object not found."));
}
}
else {
DBG_ERROR(NULL, "Missing object id");
GWEN_Buffer_AppendArgs(pageBuf, "<p><font color=\"red\">%s</font></p>", I18N("Missing or invalid object id."));
}
}
else {
DBG_INFO(NULL, "No permissions to edit.");
GWEN_Buffer_AppendArgs(pageBuf, "<p>%s</p>", I18N("No permissions to edit."));
}
}
GWEN_MSG *_handlePostAdd(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq)
{
AQH_URLHANDLER_OBJECTS *xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh);
uint32_t perms;
perms=AQH_HttpRequest_GetModulePerms(rq);
if (perms & xuh->neededPermsAdd)
return _addOrEditAndCreateResponse(uh, rq, 0, _writeAddPage);
else {
DBG_INFO(NULL, "No perms to add object");
return AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 403, "Forbidden",
AQH_HttpRequest_GetProtocol(rq), NULL);
}
}
GWEN_MSG *_handlePostEdit(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, int id)
{
AQH_URLHANDLER_OBJECTS *xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh);
uint32_t perms;
perms=AQH_HttpRequest_GetModulePerms(rq);
if (perms & xuh->neededPermsEdit) {
return _addOrEditAndCreateResponse(uh, rq, id, _writeEditPage);
}
else {
DBG_INFO(NULL, "No perms to edit");
return AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 403, "Forbidden",
AQH_HttpRequest_GetProtocol(rq), NULL);
}
}
GWEN_MSG *_addOrEditAndCreateResponse(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, int id, AQH_HTTP_URLHANDLER_WRITEPAGE_CB cb)
{
AQH_URLHANDLER_OBJECTS *xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh);
AQH_SERVICE *sv;
GWEN_DB_NODE *db;
int rv;
sv=AQH_HttpUrlHandler_GetHttpService(uh);
db=AQH_HttpRequest_GetDbPostBody(rq);
rv=_addOrEditObject(uh, db, id);
if (rv<0)
return AQH_BaseHttpUrlHandler_CreateResponseForErrorCode(uh, rq, rv, cb, db);
return AQH_HttpService_CreateRedirectingResponseMsg(sv, AQH_HttpRequest_GetProtocol(rq), xuh->urlForObjectList);
}
int _addOrEditObject(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *db, int id)
{
AQH_URLHANDLER_OBJECTS *xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh);
if (xuh->addOrEditObjectFn)
return xuh->addOrEditObjectFn(uh, db, id);
return GWEN_ERROR_NOT_IMPLEMENTED;
}
GWEN_DB_NODE *_findObjectByIdAndReturnAsDb(AQH_HTTP_URLHANDLER *uh, int id)
{
AQH_URLHANDLER_OBJECTS *xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh);
if (xuh->findObjectByIdAndReturnAsDbFn)
return xuh->findObjectByIdAndReturnAsDbFn(uh, id);
return NULL;
}
int _writeAddPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf)
{
AQH_URLHANDLER_OBJECTS *xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh);
if (xuh->writeAddPageFn)
return xuh->writeAddPageFn(uh, rq, dbValues, pageBuf);
return GWEN_ERROR_NOT_IMPLEMENTED;
}
int _writeEditPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf)
{
AQH_URLHANDLER_OBJECTS *xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh);
if (xuh->writeEditPageFn)
return xuh->writeEditPageFn(uh, rq, dbValues, pageBuf);
return GWEN_ERROR_NOT_IMPLEMENTED;
}
void _listObjectsIntoBuffer(AQH_HTTP_URLHANDLER *uh, GWEN_BUFFER *pageBuf)
{
AQH_URLHANDLER_OBJECTS *xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_OBJECTS, uh);
if (xuh->listObjectsIntoBufferFn)
xuh->listObjectsIntoBufferFn(uh, pageBuf);
}

View File

@@ -1,45 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_U_OBJECTS_H
#define AQHOME_STORAGE_U_OBJECTS_H
#include "aqhome/http/urlhandler.h"
AQH_HTTP_URLHANDLER *AQH_ObjectsHttpUrlHandler_new(AQH_SERVICE *sv,
uint32_t neededPermsList,
uint32_t neededPermsAdd,
uint32_t neededPermsDel,
uint32_t neededPermsEdit,
const char *urlForObjectList);
typedef int (*AQH_OBJECTSHTTPURLHANDLER_ADDOREDITOBJECT_FN)(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *db, int id);
typedef GWEN_DB_NODE* (*AQH_OBJECTSHTTPURLHANDLER_FINDOBJECTBYIDANDRETURNASDB_FN)(AQH_HTTP_URLHANDLER *uh, int id);
typedef int (*AQH_OBJECTSHTTPURLHANDLER_WRITEADDPAGE_FN)(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq,
GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf);
typedef int (*AQH_OBJECTSHTTPURLHANDLER_WRITEEDITPAGE_FN)(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq,
GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf);
typedef void (*AQH_OBJECTSHTTPURLHANDLER_LISTOBJECTSINTOBUFFER_FN)(AQH_HTTP_URLHANDLER *uh, GWEN_BUFFER *pageBuf);
void AQH_ObjectsHttpUrlHandler_SetAddOrEditObjectFn(AQH_HTTP_URLHANDLER *uh, AQH_OBJECTSHTTPURLHANDLER_ADDOREDITOBJECT_FN fn);
void AQH_ObjectsHttpUrlHandler_SetFindObjectByIdAndReturnAsDbFn(AQH_HTTP_URLHANDLER *uh,
AQH_OBJECTSHTTPURLHANDLER_FINDOBJECTBYIDANDRETURNASDB_FN fn);
void AQH_ObjectsHttpUrlHandler_SetWriteAddPageFn(AQH_HTTP_URLHANDLER *uh, AQH_OBJECTSHTTPURLHANDLER_WRITEADDPAGE_FN fn);
void AQH_ObjectsHttpUrlHandler_SetWriteEditPageFn(AQH_HTTP_URLHANDLER *uh, AQH_OBJECTSHTTPURLHANDLER_WRITEEDITPAGE_FN fn);
void AQH_ObjectsHttpUrlHandler_SetListObjectsIntoBufferFn(AQH_HTTP_URLHANDLER *uh,
AQH_OBJECTSHTTPURLHANDLER_LISTOBJECTSINTOBUFFER_FN fn);
#endif

View File

@@ -1,37 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_U_OBJECTS_P_H
#define AQHOME_STORAGE_U_OBJECTS_P_H
#include "./u_objects.h"
#include <gwenhywfar/stringlist.h>
typedef struct AQH_URLHANDLER_OBJECTS AQH_URLHANDLER_OBJECTS;
struct AQH_URLHANDLER_OBJECTS {
uint32_t neededPermsList;
uint32_t neededPermsAdd;
uint32_t neededPermsDel;
uint32_t neededPermsEdit; /* e.g. AQHOME_HTTP_PERMS_EDIT_ROOM */
char *urlForObjectList;
AQH_OBJECTSHTTPURLHANDLER_ADDOREDITOBJECT_FN addOrEditObjectFn;
AQH_OBJECTSHTTPURLHANDLER_FINDOBJECTBYIDANDRETURNASDB_FN findObjectByIdAndReturnAsDbFn;
AQH_OBJECTSHTTPURLHANDLER_WRITEADDPAGE_FN writeAddPageFn;
AQH_OBJECTSHTTPURLHANDLER_WRITEEDITPAGE_FN writeEditPageFn;
AQH_OBJECTSHTTPURLHANDLER_LISTOBJECTSINTOBUFFER_FN listObjectsIntoBufferFn;
};
#endif

View File

@@ -1,272 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./u_rooms.h"
#include "./u_objects.h"
#include "./aqhomehttp.h"
#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/i18n.h>
/* ------------------------------------------------------------------------------------------------
* defines
* ------------------------------------------------------------------------------------------------
*/
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static int _addOrEditObject(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *db, int id);
static GWEN_DB_NODE *_findObjectByIdAndReturnAsDb(AQH_HTTP_URLHANDLER *uh, int id);
static int _writeAddPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf);
static int _writeEditPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf);
static void _listObjectsIntoBuffer(AQH_HTTP_URLHANDLER *uh, GWEN_BUFFER *pageBuf);
static void _writeEditingTable(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf);
static void _setFromObject(AQH_ROOM *r, const AQH_ROOM *srcRoom);
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
AQH_HTTP_URLHANDLER *AQH_RoomsHttpUrlHandler_new(AQH_SERVICE *sv)
{
AQH_HTTP_URLHANDLER *uh;
uh=AQH_ObjectsHttpUrlHandler_new(sv,
AQHOME_HTTP_PERMS_LIST_ROOMS,
AQHOME_HTTP_PERMS_ADD_ROOM,
AQHOME_HTTP_PERMS_DEL_ROOM,
AQHOME_HTTP_PERMS_EDIT_ROOM,
"/rooms/list");
AQH_ObjectsHttpUrlHandler_SetAddOrEditObjectFn(uh, _addOrEditObject);
AQH_ObjectsHttpUrlHandler_SetFindObjectByIdAndReturnAsDbFn(uh, _findObjectByIdAndReturnAsDb);
AQH_ObjectsHttpUrlHandler_SetWriteAddPageFn(uh, _writeAddPage);
AQH_ObjectsHttpUrlHandler_SetWriteEditPageFn(uh, _writeEditPage);
AQH_ObjectsHttpUrlHandler_SetListObjectsIntoBufferFn(uh, _listObjectsIntoBuffer);
return uh;
}
int _addOrEditObject(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *db, int id)
{
AQH_SERVICE *sv;
AQH_STORAGE *sto;
AQH_ROOM *newRoom;
const char *roomName;
int rv;
sv=AQH_HttpUrlHandler_GetHttpService(uh);
sto=AqHomeHttpService_GetStorage(sv);
newRoom=AQH_Room_fromDb(db);
roomName=AQH_Room_GetName(newRoom);
if (!(roomName && *roomName)) {
DBG_INFO(NULL, "Missing room name");
AQH_Room_free(newRoom);
return GWEN_ERROR_INVALID;
}
rv=AqHomeHttpService_LockStorage(sv);
if (rv<0) {
DBG_ERROR(NULL, "Error locking storage");
AQH_Room_free(newRoom);
return GWEN_ERROR_IO;
}
if (id>0) {
AQH_ROOM *r;
DBG_INFO(NULL, "Edit existing room");
r=AQH_Storage_GetRoomById(sto, id);
if (r==NULL) {
AqHomeHttpService_UnlockStorage(sv);
DBG_ERROR(NULL, "Room %d not found", id);
AQH_Room_free(newRoom);
return GWEN_ERROR_NOT_FOUND;
}
AQH_Room_SetId(r, id);
_setFromObject(r, newRoom);
AQH_Storage_AddRuntimeFlags(sto, AQH_STORAGE_RTFLAGS_MODIFIED);
}
else {
AQH_ROOM *r;
DBG_INFO(NULL, "Adding new room");
r=AQH_Storage_GetRoomByName(sto, roomName);
if (r) {
AqHomeHttpService_UnlockStorage(sv);
DBG_ERROR(NULL, "Room %s exists", roomName);
AQH_Room_free(newRoom);
return GWEN_ERROR_FOUND;
}
r=AQH_Room_new();
AQH_Room_SetId(r, 0);
_setFromObject(r, newRoom);
AQH_Storage_AddRoom(sto, r);
AQH_Storage_AddRuntimeFlags(sto, AQH_STORAGE_RTFLAGS_MODIFIED);
}
AQH_Room_free(newRoom);
rv=AqHomeHttpService_UnlockStorage(sv);
if (rv<0) {
DBG_ERROR(NULL, "Error unlocking storage");
return GWEN_ERROR_IO;
}
return 0;
}
void _setFromObject(AQH_ROOM *r, const AQH_ROOM *srcRoom)
{
AQH_Room_SetName(r, AQH_Room_GetName(srcRoom));
AQH_Room_SetDescription(r, AQH_Room_GetDescription(srcRoom));
}
GWEN_DB_NODE *_findObjectByIdAndReturnAsDb(AQH_HTTP_URLHANDLER *uh, int id)
{
AQH_SERVICE *sv;
AQH_STORAGE *sto;
const AQH_ROOM *r;
sv=AQH_HttpUrlHandler_GetHttpService(uh);
sto=AqHomeHttpService_GetStorage(sv);
r=AQH_Storage_GetRoomById(sto, id);
if (r) {
GWEN_DB_NODE *db;
db=GWEN_DB_Group_new("room");
AQH_Room_toDb(r, db);
return db;
}
return NULL;
}
int _writeAddPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf)
{
GWEN_Buffer_AppendArgs(pageBuf,
"<h2>%s</h2><br>\n"
"<form action=\"/rooms/add\" method=\"post\" enctype=\"application/x-www-form-urlencoded\">",
I18N("Add Room"));
_writeEditingTable(uh, dbValues, pageBuf);
GWEN_Buffer_AppendArgs(pageBuf, "<input type=\"submit\" value=\"%s\"></form>", I18N("Submit"));
return 0;
}
int _writeEditPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf)
{
GWEN_Buffer_AppendArgs(pageBuf,
"<h2>%s</h2><br>\n"
"<form action=\"/rooms/edit/%lu\" method=\"post\" enctype=\"application/x-www-form-urlencoded\">",
I18N("Edit Room"),
(long unsigned int)(dbValues?GWEN_DB_GetIntValue(dbValues, "id", 0, 0):0));
_writeEditingTable(uh, dbValues, pageBuf);
GWEN_Buffer_AppendArgs(pageBuf, "<input type=\"submit\" value=\"%s\"></form>", I18N("Submit"));
return 0;
}
void _writeEditingTable(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf)
{
GWEN_Buffer_AppendArgs(pageBuf,
" <table>"
" <tr>"
" <td><label for=\"name\">%s: </label></td>"
" <td><input type=\"text\" name=\"name\" value=\"%s\" required></td>"
" </tr>"
" <tr>"
" <td><label for=\"description\">%s: </label></td>"
" <td><input type=\"text\" name=\"description\" value=\"%s\" ></td>"
" </tr>"
" </table>",
I18N("Name"),
dbValues?GWEN_DB_GetCharValue(dbValues, "name", 0, ""):"",
I18N("Description"),
dbValues?GWEN_DB_GetCharValue(dbValues, "description", 0, ""):"");
}
void _listObjectsIntoBuffer(AQH_HTTP_URLHANDLER *uh, GWEN_BUFFER *pageBuf)
{
AQH_SERVICE *sv;
AQH_STORAGE *sto;
const AQH_ROOM_LIST *rl;
sv=AQH_HttpUrlHandler_GetHttpService(uh);
sto=AqHomeHttpService_GetStorage(sv);
GWEN_Buffer_AppendArgs(pageBuf,
"<h2>%s</h2>"
"<table class=\"dataTable\">"
"<thead>"
" <tr><th>%s</th><th>%s</th><th></th></tr>"
"</thead>",
I18N("Rooms"),
I18N("Name"),
I18N("Description"));
GWEN_Buffer_AppendString(pageBuf, "<tbody>");
rl=AQH_Storage_GetRoomList(sto);
if (rl) {
const AQH_ROOM *r;
r=AQH_Room_List_First(rl);
while(r) {
long unsigned int id;
const char *name;
const char *descr;
id=(long unsigned int) AQH_Room_GetId(r);
name=AQH_Room_GetName(r);
descr=AQH_Room_GetDescription(r);
GWEN_Buffer_AppendArgs(pageBuf,
"<tr><td>%s</td><td>%s</td>"
"<td><a href=\"/rooms/edit/%lu\">"
"<IMG src=\"/pics/edit.png\" width=32 height=32 align=left border=0></a></td>"
"</tr>",
name?name:"", descr?descr:"", id);
r=AQH_Room_List_Next(r);
}
}
GWEN_Buffer_AppendString(pageBuf, "</tbody>");
GWEN_Buffer_AppendArgs(pageBuf, "</table><a href=\"/rooms/add\">%s</a><br>", I18N("Add Room"));
}

View File

@@ -1,24 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_U_ROOMS_H
#define AQHOME_STORAGE_U_ROOMS_H
#include "aqhome/http/urlhandler.h"
#include <gwenhywfar/endpoint.h>
AQH_HTTP_URLHANDLER *AQH_RoomsHttpUrlHandler_new(AQH_SERVICE *sv);
#endif

View File

@@ -1,291 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./u_static_p.h"
#include "./aqhomehttp.h"
#include "aqhome/http/httpservice.h"
#include "aqhome/http/httpservice_http.h"
#include "aqhome/http/httpservice_conf.h"
#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/i18n.h>
GWEN_INHERIT(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_STATIC);
/* ------------------------------------------------------------------------------------------------
* defines
* ------------------------------------------------------------------------------------------------
*/
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static void GWENHYWFAR_CB _freeData(void *bp, void *p);
static int _handleUrl(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq);
static GWEN_MSG *_handleGet(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq);
static void _readFileList(AQH_HTTP_URLHANDLER *uh);
static GWEN_BUFFER *_readFileIntoBuffer(AQH_HTTP_URLHANDLER *uh, const char *requestedFilename);
static const char *_getContentTypeFromFilename(const char *filename);
static GWEN_MSG *_createFileResponseMsg(AQH_HTTP_URLHANDLER *uh,
const char *protocol,
const char *contentType,
const uint8_t *ptr, uint32_t len);
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
AQH_HTTP_URLHANDLER *AQH_StaticHttpUrlHandler_new(AQH_SERVICE *sv, const char *folder)
{
AQH_HTTP_URLHANDLER *uh;
AQH_URLHANDLER_STATIC *xuh;
uh=AQH_HttpUrlHandler_new(sv);
GWEN_NEW_OBJECT(AQH_URLHANDLER_STATIC, xuh);
GWEN_INHERIT_SETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_STATIC, uh, xuh, _freeData);
AQH_HttpUrlHandler_SetFolder(uh, folder);
AQH_HttpUrlHandler_SetHandleFn(uh, _handleUrl);
_readFileList(uh);
return uh;
}
void _freeData(void *bp, void *p)
{
AQH_URLHANDLER_STATIC *xuh;
xuh=(AQH_URLHANDLER_STATIC*)p;
GWEN_StringList_free(xuh->fileList);
GWEN_FREE_OBJECT(xuh);
}
int _handleUrl(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq)
{
const char *protocol;
const char *cmd;
GWEN_MSG *msgOut;
AQH_HttpService_SetupModuleAndPerms(AQH_HttpUrlHandler_GetHttpService(uh), rq, "aqhome");
AQH_HttpRequest_SetupUrlPathMembers(rq);
protocol=AQH_HttpRequest_GetProtocol(rq);
cmd=AQH_HttpRequest_GetCommand(rq);
if (cmd && *cmd) {
if (strcasecmp(cmd, "GET")==0)
msgOut=_handleGet(uh, rq);
else {
msgOut=AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 405, "Method not allowed", protocol, NULL);
}
AQH_HttpRequest_SetResponseMsg(rq, msgOut);
return 0;
}
else {
DBG_ERROR(AQH_LOGDOMAIN, "No command in request");
return GWEN_ERROR_INVALID;
}
}
GWEN_MSG *_handleGet(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq)
{
AQH_URLHANDLER_STATIC *xuh;
AQH_SERVICE *sv;
const char *protocol;
const GWEN_STRINGLIST *sl;
xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_STATIC, uh);
if (xuh==NULL) {
DBG_ERROR(NULL, "Not a AQH_URLHANDLER_STATIC object");
return NULL;
}
sv=AQH_HttpUrlHandler_GetHttpService(uh);
protocol=AQH_HttpRequest_GetProtocol(rq);
/* handle middle part (header - middle - footer) */
sl=AQH_HttpRequest_GetUrlPathMembers(rq);
if (sl) {
const char *s;
s=GWEN_StringList_StringAt(sl, 1);
if (!(s && *s)) {
DBG_ERROR(NULL, "Invalid url (2nd member is [%s])", s);
return AQH_HttpService_CreateResponseMsg(sv, 404, "Not found", protocol, NULL);
}
else {
GWEN_BUFFER *fileBuf;
const char *contentType;
GWEN_MSG *msgOut;
contentType=_getContentTypeFromFilename(s);
DBG_INFO(NULL, "Reading file \"%s\" (%s)", s, contentType);
fileBuf=_readFileIntoBuffer(uh, s);
if (fileBuf==NULL) {
DBG_INFO(NULL, "here");
return AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 404, "Not found", protocol, NULL);
}
msgOut=_createFileResponseMsg(uh, protocol, contentType,
(const uint8_t*) GWEN_Buffer_GetStart(fileBuf), GWEN_Buffer_GetUsedBytes(fileBuf));
GWEN_Buffer_free(fileBuf);
return msgOut;
}
}
else {
DBG_ERROR(NULL, "No list of url members");
return AQH_HttpService_CreateResponseMsg(AQH_HttpUrlHandler_GetHttpService(uh), 500, "Internal Error", protocol, NULL);
}
}
void _readFileList(AQH_HTTP_URLHANDLER *uh)
{
AQH_URLHANDLER_STATIC *xuh;
const char *folder;
xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_STATIC, uh);
GWEN_StringList_free(xuh->fileList);
xuh->fileList=NULL;
folder=AQH_HttpUrlHandler_GetFolder(uh);
if (folder && *folder) {
GWEN_STRINGLIST *fileList;
fileList=AQH_HttpService_GetFolderFileList(folder, NULL, 1);
if (fileList) {
GWEN_StringList_RemoveString(fileList, ".");
GWEN_StringList_RemoveString(fileList, "..");
GWEN_StringList_free(xuh->fileList);
xuh->fileList=fileList;
}
else {
DBG_INFO(NULL, "No file list (empty folder?)");
}
}
}
GWEN_BUFFER *_readFileIntoBuffer(AQH_HTTP_URLHANDLER *uh, const char *requestedFilename)
{
AQH_URLHANDLER_STATIC *xuh;
const char *folder;
xuh=GWEN_INHERIT_GETDATA(AQH_HTTP_URLHANDLER, AQH_URLHANDLER_STATIC, uh);
folder=AQH_HttpUrlHandler_GetFolder(uh);
if (!(xuh->fileList && GWEN_StringList_Count(xuh->fileList)>0)) {
DBG_INFO(NULL, "Reading file list for folder \"%s\"", folder);
_readFileList(uh);
}
if (!(xuh->fileList && GWEN_StringList_Count(xuh->fileList)>0)) {
DBG_INFO(NULL, "No file list (empty or missing/bad folder? Permissions?");
return NULL;
}
if (GWEN_StringList_HasString(xuh->fileList, requestedFilename)) {
GWEN_BUFFER *nameBuf;
GWEN_BUFFER *fileBuf;
int rv;
fileBuf=GWEN_Buffer_new(0, 1024, 0, 1);
nameBuf=GWEN_Buffer_new(0, 256, 0, 1);
GWEN_Buffer_AppendString(nameBuf, folder);
GWEN_Buffer_AppendString(nameBuf, GWEN_DIR_SEPARATOR_S);
GWEN_Buffer_AppendString(nameBuf, requestedFilename);
DBG_INFO(NULL, "Reading file \"%s\"", GWEN_Buffer_GetStart(nameBuf));
rv=GWEN_SyncIo_Helper_ReadFile(GWEN_Buffer_GetStart(nameBuf), fileBuf);
if (rv<0) {
DBG_ERROR(NULL, "Error reading file \"%s\" (%d)", GWEN_Buffer_GetStart(nameBuf), rv);
GWEN_Buffer_free(nameBuf);
GWEN_Buffer_free(fileBuf);
return NULL;
}
GWEN_Buffer_free(nameBuf);
return fileBuf;
}
else {
DBG_INFO(NULL, "File \"%s\" not found in folder \"%s\"", requestedFilename, folder);
return NULL;
}
}
GWEN_MSG *_createFileResponseMsg(AQH_HTTP_URLHANDLER *uh,
const char *protocol,
const char *contentType,
const uint8_t *ptr, uint32_t len)
{
GWEN_BUFFER *buf;
GWEN_MSG *msg;
GWEN_DB_NODE *db;
buf=GWEN_Buffer_new(0, len+256, 0, 1);
AQH_HttpService_AddStatusLine(AQH_HttpUrlHandler_GetHttpService(uh), 200, "OK", protocol, buf);
db=GWEN_DB_Group_new("header");
GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Connection", "Keep-Alive");
GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Content-Type", contentType);
GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Content-Length", len);
AQH_HttpService_AddHeader(AQH_HttpUrlHandler_GetHttpService(uh), db, buf);
GWEN_DB_Group_free(db);
GWEN_Buffer_AppendBytes(buf, (const char *) ptr, len);
msg=GWEN_Msg_fromBytes((const uint8_t*)GWEN_Buffer_GetStart(buf), GWEN_Buffer_GetUsedBytes(buf));
GWEN_Buffer_free(buf);
return msg;
}
const char *_getContentTypeFromFilename(const char *filename)
{
if (filename) {
const char *s;
s=strrchr(filename, '.');
if (s) {
if (strcasecmp(s, ".png")==0)
return "image/png";
else if (strcasecmp(s, ".jpg")==0)
return "image/jpg";
}
}
return "application/x-binary";
}

View File

@@ -1,24 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_U_STATIC_H
#define AQHOME_STORAGE_U_STATIC_H
#include "aqhome/http/urlhandler.h"
#include <gwenhywfar/endpoint.h>
AQH_HTTP_URLHANDLER *AQH_StaticHttpUrlHandler_new(AQH_SERVICE *sv, const char *folder);
#endif

View File

@@ -1,27 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_U_STATIC_P_H
#define AQHOME_STORAGE_U_STATIC_P_H
#include "./u_static.h"
#include <gwenhywfar/stringlist.h>
typedef struct AQH_URLHANDLER_STATIC AQH_URLHANDLER_STATIC;
struct AQH_URLHANDLER_STATIC {
GWEN_STRINGLIST *fileList;
};
#endif

View File

@@ -1,340 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./u_values.h"
#include "./u_objects.h"
#include "./aqhomehttp.h"
#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/i18n.h>
/* ------------------------------------------------------------------------------------------------
* defines
* ------------------------------------------------------------------------------------------------
*/
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static int _addOrEditObject(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *db, int id);
static GWEN_DB_NODE *_findObjectByIdAndReturnAsDb(AQH_HTTP_URLHANDLER *uh, int id);
static int _writeAddPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf);
static int _writeEditPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf);
static void _listObjectsIntoBuffer(AQH_HTTP_URLHANDLER *uh, GWEN_BUFFER *pageBuf);
static void _writeEditingTable(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf);
static void _setFromObject(AQH_VALUE *value, const AQH_VALUE *srcValue);
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
AQH_HTTP_URLHANDLER *AQH_ValuesHttpUrlHandler_new(AQH_SERVICE *sv)
{
AQH_HTTP_URLHANDLER *uh;
uh=AQH_ObjectsHttpUrlHandler_new(sv,
AQHOME_HTTP_PERMS_LIST_VALUES,
AQHOME_HTTP_PERMS_ADD_VALUE,
AQHOME_HTTP_PERMS_DEL_VALUE,
AQHOME_HTTP_PERMS_EDIT_VALUE,
"/values/list");
AQH_ObjectsHttpUrlHandler_SetAddOrEditObjectFn(uh, _addOrEditObject);
AQH_ObjectsHttpUrlHandler_SetFindObjectByIdAndReturnAsDbFn(uh, _findObjectByIdAndReturnAsDb);
AQH_ObjectsHttpUrlHandler_SetWriteAddPageFn(uh, _writeAddPage);
AQH_ObjectsHttpUrlHandler_SetWriteEditPageFn(uh, _writeEditPage);
AQH_ObjectsHttpUrlHandler_SetListObjectsIntoBufferFn(uh, _listObjectsIntoBuffer);
return uh;
}
int _addOrEditObject(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *db, int id)
{
AQH_SERVICE *sv;
AQH_STORAGE *sto;
AQH_VALUE *newValue;
const char *valueName;
int rv;
sv=AQH_HttpUrlHandler_GetHttpService(uh);
sto=AqHomeHttpService_GetStorage(sv);
newValue=AQH_Value_fromDb(db);
valueName=AQH_Value_GetNameForSystem(newValue);
if (!(valueName && *valueName)) {
DBG_INFO(NULL, "Missing value name");
AQH_Value_free(newValue);
return GWEN_ERROR_INVALID;
}
rv=AqHomeHttpService_LockStorage(sv);
if (rv<0) {
DBG_ERROR(NULL, "Error locking storage");
AQH_Value_free(newValue);
return GWEN_ERROR_IO;
}
if (id>0) {
AQH_VALUE *value;
DBG_INFO(NULL, "Edit existing value");
value=AQH_Storage_GetValueById(sto, id);
if (value==NULL) {
AqHomeHttpService_UnlockStorage(sv);
DBG_ERROR(NULL, "Value %d not found", id);
AQH_Value_free(newValue);
return GWEN_ERROR_NOT_FOUND;
}
AQH_Value_SetId(value, id);
_setFromObject(value, newValue);
AQH_Storage_AddRuntimeFlags(sto, AQH_STORAGE_RTFLAGS_MODIFIED);
}
else {
AQH_VALUE *value;
DBG_INFO(NULL, "Adding new value");
value=AQH_Storage_GetValueByName(sto, valueName);
if (value) {
AqHomeHttpService_UnlockStorage(sv);
DBG_ERROR(NULL, "Value %s exists", valueName);
AQH_Value_free(newValue);
return GWEN_ERROR_FOUND;
}
value=AQH_Value_new();
AQH_Value_SetId(value, 0);
_setFromObject(value, newValue);
AQH_Storage_AddValue(sto, value);
AQH_Storage_AddRuntimeFlags(sto, AQH_STORAGE_RTFLAGS_MODIFIED);
}
AQH_Value_free(newValue);
rv=AqHomeHttpService_UnlockStorage(sv);
if (rv<0) {
DBG_ERROR(NULL, "Error unlocking storage");
return GWEN_ERROR_IO;
}
return 0;
}
void _setFromObject(AQH_VALUE *value, const AQH_VALUE *srcValue)
{
AQH_Value_SetName(value, AQH_Value_GetName(srcValue));
AQH_Value_SetTopicId(value, AQH_Value_GetTopicId(srcValue));
AQH_Value_SetValueType(value, AQH_Value_GetValueType(srcValue));
AQH_Value_SetValueUnits(value, AQH_Value_GetValueUnits(srcValue));
AQH_Value_SetDataPath(value, AQH_Value_GetDataPath(srcValue));
}
GWEN_DB_NODE *_findObjectByIdAndReturnAsDb(AQH_HTTP_URLHANDLER *uh, int id)
{
AQH_SERVICE *sv;
AQH_STORAGE *sto;
const AQH_VALUE *value;
sv=AQH_HttpUrlHandler_GetHttpService(uh);
sto=AqHomeHttpService_GetStorage(sv);
value=AQH_Storage_GetValueById(sto, id);
if (value) {
GWEN_DB_NODE *db;
db=GWEN_DB_Group_new("value");
AQH_Value_toDb(value, db);
return db;
}
return NULL;
}
int _writeAddPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf)
{
GWEN_Buffer_AppendArgs(pageBuf,
"<h2>%s</h2><br>\n"
"<form action=\"/values/add\" method=\"post\" enctype=\"application/x-www-form-urlencoded\">",
I18N("Add Value"));
_writeEditingTable(uh, dbValues, pageBuf);
GWEN_Buffer_AppendArgs(pageBuf, "<input type=\"submit\" value=\"%s\"></form>", I18N("Submit"));
return 0;
}
int _writeEditPage(AQH_HTTP_URLHANDLER *uh, AQH_HTTP_REQUEST *rq, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf)
{
GWEN_Buffer_AppendArgs(pageBuf,
"<h2>%s</h2><br>\n"
"<form action=\"/values/edit/%lu\" method=\"post\" enctype=\"application/x-www-form-urlencoded\">",
I18N("Edit Value"),
(long unsigned int)(dbValues?GWEN_DB_GetIntValue(dbValues, "id", 0, 0):0));
_writeEditingTable(uh, dbValues, pageBuf);
GWEN_Buffer_AppendArgs(pageBuf, "<input type=\"submit\" value=\"%s\"></form>", I18N("Submit"));
return 0;
}
void _writeEditingTable(AQH_HTTP_URLHANDLER *uh, GWEN_DB_NODE *dbValues, GWEN_BUFFER *pageBuf)
{
AQH_SERVICE *sv;
AQH_STORAGE *sto;
const AQH_MQTT_TOPIC_LIST *topicList;
unsigned long int selectedTopicId=0;
char numbuf[16];
sv=AQH_HttpUrlHandler_GetHttpService(uh);
sto=AqHomeHttpService_GetStorage(sv);
topicList=AQH_Storage_GetMqttTopicList(sto);
selectedTopicId=(unsigned long int)(dbValues?GWEN_DB_GetIntValue(dbValues, "TopicId", 0, 0):0);
snprintf(numbuf, sizeof(numbuf)-1, "%lu", selectedTopicId);
numbuf[sizeof(numbuf)-1]=0;
GWEN_Buffer_AppendArgs(pageBuf,
" <table>"
" <tr>"
" <td><label for=\"name\">%s: </label></td>"
" <td><input type=\"text\" name=\"name\" value=\"%s\" required></td>"
" </tr>",
I18N("Name"),
dbValues?GWEN_DB_GetCharValue(dbValues, "name", 0, ""):"");
GWEN_Buffer_AppendArgs(pageBuf,
"<tr><td><label for=\"topicId\">%s: </label></td>"
"<td><select id=\"topicId\" name=\"topicId\">",
I18N("Topic"));
GWEN_Buffer_AppendArgs(pageBuf, "<option value=\"0\">%s</option>", I18N("-- select topic --"));
if (topicList) {
const AQH_MQTT_TOPIC *topic;
topic=AQH_MqttTopic_List_First(topicList);
while(topic) {
const char *topicName;
topicName=AQH_MqttTopic_GetTopic(topic);
if (topicName && *topicName) {
snprintf(numbuf, sizeof(numbuf)-1, "%lu", (unsigned long int) AQH_MqttTopic_GetId(topic));
numbuf[sizeof(numbuf)-1]=0;
if (selectedTopicId && AQH_MqttTopic_GetId(topic)==selectedTopicId)
GWEN_Buffer_AppendArgs(pageBuf, "<option value=\"%s\" selected>%s</option>", numbuf, topicName);
else
GWEN_Buffer_AppendArgs(pageBuf, "<option value=\"%s\">%s</option>", numbuf, topicName);
}
topic=AQH_MqttTopic_List_Next(topic);
}
}
GWEN_Buffer_AppendString(pageBuf, "</select></td></tr>");
GWEN_Buffer_AppendArgs(pageBuf,
" <tr>"
" <td><label for=\"valueUnits\">%s: </label></td>"
" <td><input type=\"text\" name=\"valueUnits\" value=\"%s\"></td>"
" </tr>",
I18N("Units"),
dbValues?GWEN_DB_GetCharValue(dbValues, "valueUnits", 0, ""):"");
GWEN_Buffer_AppendArgs(pageBuf,
" <tr>"
" <td><label for=\"dataPath\">%s: </label></td>"
" <td><input type=\"text\" name=\"dataPath\" value=\"%s\"></td>"
" </tr>"
" </table>",
I18N("Data Path (JSON)"),
dbValues?GWEN_DB_GetCharValue(dbValues, "dataPath", 0, ""):"");
}
void _listObjectsIntoBuffer(AQH_HTTP_URLHANDLER *uh, GWEN_BUFFER *pageBuf)
{
AQH_SERVICE *sv;
AQH_STORAGE *sto;
const AQH_VALUE_LIST *rl;
sv=AQH_HttpUrlHandler_GetHttpService(uh);
sto=AqHomeHttpService_GetStorage(sv);
GWEN_Buffer_AppendArgs(pageBuf,
"<h2>%s</h2>"
"<table class=\"dataTable\">"
"<thead>"
" <tr><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th></th></tr>"
"</thead>",
I18N("Values"),
I18N("Name"),
I18N("Topic"),
I18N("Units"),
I18N("Data Path"));
GWEN_Buffer_AppendString(pageBuf, "<tbody>");
rl=AQH_Storage_GetValueList(sto);
if (rl) {
const AQH_VALUE *value;
value=AQH_Value_List_First(rl);
while(value) {
long unsigned int id;
int topicId;
const char *name;
const char *topicName=NULL;
const char *valueUnits;
const char *dataPath;
const AQH_MQTT_TOPIC *topic=NULL;
id=(long unsigned int) AQH_Value_GetId(value);
topicId=(long unsigned int) AQH_Value_GetTopicId(value);
if (topicId>0)
topic=AQH_Storage_GetMqttTopicById(sto, topicId);
if (topic)
topicName=AQH_MqttTopic_GetTopic(topic);
name=AQH_Value_GetName(value);
valueUnits=AQH_Value_GetValueUnits(value);
dataPath=AQH_Value_GetDataPath(value);
GWEN_Buffer_AppendArgs(pageBuf,
"<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td>"
"<td><a href=\"/values/edit/%lu\">"
"<IMG src=\"/pics/edit.png\" width=32 height=32 align=left border=0></a></td>"
"</tr>",
name?name:"",
topicName?topicName:"",
valueUnits?valueUnits:"",
dataPath?dataPath:"",
id);
value=AQH_Value_List_Next(value);
}
}
GWEN_Buffer_AppendString(pageBuf, "</tbody>");
GWEN_Buffer_AppendArgs(pageBuf, "</table><a href=\"/values/add\">%s</a><br>", I18N("Add Value"));
}

View File

@@ -1,23 +0,0 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_STORAGE_U_VALUES_H
#define AQHOME_STORAGE_U_VALUES_H
#include "aqhome/http/urlhandler.h"
AQH_HTTP_URLHANDLER *AQH_ValuesHttpUrlHandler_new(AQH_SERVICE *sv);
#endif

View File

@@ -51,6 +51,7 @@
aqhome aqhome
aqhtool_nodes aqhtool_nodes
aqhtool_data aqhtool_data
aqhtool_image
</useTargets> </useTargets>
<libraries> <libraries>
@@ -61,6 +62,7 @@
<subdirs> <subdirs>
nodes nodes
data data
image
</subdirs> </subdirs>

View File

@@ -23,6 +23,10 @@
#include <gwenhywfar/i18n.h> #include <gwenhywfar/i18n.h>
#include <gwenhywfar/debug.h> #include <gwenhywfar/debug.h>
#include <gwenhywfar/text.h> #include <gwenhywfar/text.h>
#include <gwenhywfar/stringlist.h>
#include <ctype.h>
/* ------------------------------------------------------------------------------------------------ /* ------------------------------------------------------------------------------------------------
@@ -46,6 +50,10 @@
static AQH_MESSAGE *_createRequestMessage(AQH_OBJECT *o, uint32_t msgId); static AQH_MESSAGE *_createRequestMessage(AQH_OBJECT *o, uint32_t msgId);
static int _readValueFromString(const char *s, double *pDouble); static int _readValueFromString(const char *s, double *pDouble);
static int _readValueFromStringList(const char *s, double *pDouble);
static int _read16BitValues(const GWEN_STRINGLIST *sl, double *pDouble);
static int _read8BitValues(const GWEN_STRINGLIST *sl, double *pDouble);
static int _readDouble(const char *s, double *pDouble);
@@ -130,6 +138,115 @@ AQH_MESSAGE *_createRequestMessage(GWEN_UNUSED AQH_OBJECT *o, uint32_t msgId)
int _readValueFromString(const char *s, double *pDouble) int _readValueFromString(const char *s, double *pDouble)
{
if (s && *s) {
if (strchr(s, ':')!=NULL)
return _readValueFromStringList(s, pDouble);
else
return _readDouble(s, pDouble);
}
DBG_ERROR(NULL, "Missing value");
return GWEN_ERROR_INVALID;
}
int _readValueFromStringList(const char *s, double *pDouble)
{
GWEN_STRINGLIST *sl;
sl=GWEN_StringList_fromString2(s, ":", 0, GWEN_TEXT_FLAGS_DEL_QUOTES);
if (sl) {
int cnt;
int rv;
cnt=GWEN_StringList_Count(sl);
if (cnt<3){ /* read two 16 bit values */
rv=_read16BitValues(sl, pDouble);
GWEN_StringList_free(sl);
return rv;
}
else if (cnt<5) { /* read four 8 bit values */
rv=_read8BitValues(sl, pDouble);
GWEN_StringList_free(sl);
return rv;
}
else {
DBG_ERROR(NULL, "Bad value \"%s\"", s);
return GWEN_ERROR_GENERIC;
}
}
else {
DBG_ERROR(NULL, "Bad value \"%s\"", s);
return GWEN_ERROR_GENERIC;
}
}
int _read16BitValues(const GWEN_STRINGLIST *sl, double *pDouble)
{
const GWEN_STRINGLISTENTRY *se;
uint32_t result=0;
se=GWEN_StringList_FirstEntry(sl);
while(se) {
const char *s;
s=GWEN_StringListEntry_Data(se);
if (s && *s) {
double v;
int rv;
rv=_readDouble(s, &v);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
result<<=16;
result+=(((uint32_t) v) & 0xffff);
}
se=GWEN_StringListEntry_Next(se);
}
*pDouble=(double) result;
return 0;
}
int _read8BitValues(const GWEN_STRINGLIST *sl, double *pDouble)
{
const GWEN_STRINGLISTENTRY *se;
uint32_t result=0;
se=GWEN_StringList_FirstEntry(sl);
while(se) {
const char *s;
s=GWEN_StringListEntry_Data(se);
if (s && *s) {
double v;
int rv;
rv=_readDouble(s, &v);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
result<<=8;
result+=(((uint32_t) v) & 0xff);
}
se=GWEN_StringListEntry_Next(se);
}
*pDouble=(double) result;
return 0;
}
int _readDouble(const char *s, double *pDouble)
{ {
int l; int l;
@@ -144,9 +261,9 @@ int _readValueFromString(const char *s, double *pDouble)
} }
} }
else if (l>1 && s[0]=='0' && ((tolower(s[1])=='x') || tolower(s[1])=='b')) { else if (l>1 && s[0]=='0' && ((tolower(s[1])=='x') || tolower(s[1])=='b')) {
unsigned int h; int h;
if (1==sscanf(s, "%u", &h)) { if (1==sscanf(s, "%i", &h)) {
*pDouble=(double) h; *pDouble=(double) h;
return 0; return 0;
} }
@@ -155,14 +272,21 @@ int _readValueFromString(const char *s, double *pDouble)
double d; double d;
if (1==sscanf(s, "%lf", &d)) { if (1==sscanf(s, "%lf", &d)) {
return d; *pDouble=d;
return 0;
} }
} }
DBG_ERROR(NULL, "Bad value \"%s\"", s); DBG_ERROR(NULL, "Bad value \"%s\"", s);
return GWEN_ERROR_GENERIC; return GWEN_ERROR_INVALID;
}
else {
DBG_ERROR(NULL, "Empty value");
return GWEN_ERROR_INVALID;
} }
} }

View File

@@ -0,0 +1,62 @@
<?xml?>
<gwbuild>
<target type="ConvenienceLibrary" name="aqhtool_image" >
<includes type="c" >
$(gwenhywfar_cflags)
-I$(topsrcdir)
-I$(topbuilddir)
</includes>
<includes type="tm2" >
--include=$(builddir)
--include=$(srcdir)
</includes>
<setVar name="local/cflags">$(visibility_cflags)</setVar>
<setVar name="tm2flags" >
</setVar>
<setVar name="local/typefiles" >
</setVar>
<setVar name="local/built_sources" >
</setVar>
<setVar name="local/built_headers_pub">
</setVar>
<setVar name="local/built_headers_priv" >
</setVar>
<headers dist="true" >
readbmp.h
</headers>
<sources>
$(local/typefiles)
readbmp.c
</sources>
<useTargets>
</useTargets>
<libraries>
</libraries>
<subdirs>
</subdirs>
<extradist>
</extradist>
</target>
</gwbuild>

View File

@@ -0,0 +1,652 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2025 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./readbmp.h"
#include <gwenhywfar/i18n.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/text.h>
#include <gwenhywfar/args.h>
/* ------------------------------------------------------------------------------------------------
* defs
* ------------------------------------------------------------------------------------------------
*/
#define I18S(msg) msg
#define I18N(msg) GWEN_I18N_Translate(PACKAGE, msg)
#define A_ARG GWEN_ARGS_FLAGS_HAS_ARGUMENT
#define A_END (GWEN_ARGS_FLAGS_HELP | GWEN_ARGS_FLAGS_LAST)
#define A_CHAR GWEN_ArgsType_Char
#define A_INT GWEN_ArgsType_Int
#define BMP_FILE_OFFS_FILEHEADER 0
#define BMP_FILE_OFFS_IMAGEHEADER 14
#define BMP_FILEHEADER_OFFS_TYPE 0 /* 2 bytes ("BM") */
#define BMP_FILEHEADER_OFFS_FSIZE 2 /* 4 bytes */
#define BMP_FILEHEADER_OFFS_RESERVED1 6 /* 2 bytes */
#define BMP_FILEHEADER_OFFS_RESERVED2 8 /* 2 bytes */
#define BMP_FILEHEADER_OFFS_PIXELOFFS 10 /* 4 bytes offset to begin of pixel data */
#define BMP_FILEHEADER_SIZE 14
#define BMP_IMAGEHEADER_OFFS_HSIZE 0 /* 4 bytes header size */
#define BMP_IMAGEHEADER_OFFS_WIDTH 4 /* 4 bytes */
#define BMP_IMAGEHEADER_OFFS_HEIGHT 8 /* 4 bytes */
#define BMP_IMAGEHEADER_OFFS_PLANES 12 /* 2 bytes (1) */
#define BMP_IMAGEHEADER_OFFS_BPP 14 /* 2 bytes bit per pixel */
#define BMP_IMAGEHEADER_OFFS_COMPR 16 /* 4 bytes (0=uncompressed) */
#define BMP_IMAGEHEADER_OFFS_IMGSIZE 20 /* 4 bytes (may be 0 for uncompressed data) */
#define BMP_IMAGEHEADER_OFFS_XPPM 24 /* 4 bytes X pixel per meter */
#define BMP_IMAGEHEADER_OFFS_YPPM 28 /* 4 bytes Y pixel per meter */
#define BMP_IMAGEHEADER_OFFS_COLORMAPENTRIES 32 /* 4 bytes number of color map entries actually used */
#define BMP_IMAGEHEADER_OFFS_IMPORTANT 36 /* 4 bytes number of significant colors */
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static int AQH_Tool_ReadAndDumpBmpFile(const char *fname);
static int AQH_Tool_ExportBmpFile(const char *fname);
static int _exportBmp_1bpp(const BMP_FILE *bf);
static int _exportBmp_gray8bpp(const BMP_FILE *bf);
static GWEN_BUFFER *_extractPixels_gray8bpp(const uint8_t *ptrPixels, int imageWidth, int imageHeight);
static void _printBytes_ASM(const char *sName, const uint8_t *ptrPixels, int lenPixels, int imageWidth, int imageHeight);
static GWEN_BUFFER *_rleEncode(const uint8_t *ptrPixels, int lenPixels);
static int _countRepeats(const uint8_t *ptrPixels, int lenPixels);
static BMP_FILEHEADER *_fileHeader_new();
static void _fileHeader_free(BMP_FILEHEADER *fh);
static BMP_IMAGEHEADER *_imageHeader_new();
static void _imageHeader_free(BMP_IMAGEHEADER *ih);
static void _dumpBmpFileHeader(const BMP_FILEHEADER *fh);
static void _dumpBmpImageHeader(const BMP_IMAGEHEADER *ih);
static BMP_FILEHEADER *_readFileHeaderAt(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset);
static BMP_IMAGEHEADER *_readImageHeaderAt(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset);
static uint16_t _readUint16At(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset, uint16_t defaultValue);
static uint32_t _readUint32At(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset, uint32_t defaultValue);
static int32_t _readInt32At(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset, int32_t defaultValue);
static GWEN_BUFFER *_readFileIntoBuffer(const char *fname);
/* ------------------------------------------------------------------------------------------------
* code
* ------------------------------------------------------------------------------------------------
*/
int AQH_Tool_DumpBmpFile(GWEN_DB_NODE *dbGlobalArgs, int argc, char **argv)
{
GWEN_DB_NODE *dbLocalArgs;
int rv;
const char *sFilename;
const GWEN_ARGS args[]= {
/* flags type name min max s long short_descr, long_descr */
{ A_ARG, A_CHAR, "bmpFile", 1, 1, "i", "bmpFile", I18S("BMP file to read"), NULL},
{ A_END, A_INT, "help", 0, 0, "h", "help", I18S("Show this help screen"), NULL}
};
dbLocalArgs=GWEN_DB_GetGroup(dbGlobalArgs, GWEN_DB_FLAGS_DEFAULT, "local");
rv=GWEN_Args_Check(argc, argv, 1, GWEN_ARGS_MODE_ALLOW_FREEPARAM, args, dbLocalArgs);
if (rv==GWEN_ARGS_RESULT_ERROR) {
fprintf(stderr, "ERROR: Could not parse arguments\n");
return 1;
}
else if (rv==GWEN_ARGS_RESULT_HELP) {
GWEN_BUFFER *ubuf;
ubuf=GWEN_Buffer_new(0, 1024, 0, 1);
if (GWEN_Args_Usage(args, ubuf, GWEN_ArgsOutType_Txt)) {
fprintf(stderr, "ERROR: Could not create help string\n");
return 1;
}
fprintf(stderr, "%s\n", GWEN_Buffer_GetStart(ubuf));
GWEN_Buffer_free(ubuf);
return 1;
}
sFilename=GWEN_DB_GetCharValue(dbLocalArgs, "bmpFile", 0, NULL);
if (!(sFilename && *sFilename)) {
fprintf(stderr, "Missing file name\n");
return 1;
}
//return AQH_Tool_ReadAndDumpBmpFile(sFilename);
return AQH_Tool_ExportBmpFile(sFilename);
}
int AQH_Tool_ReadAndDumpBmpFile(const char *fname)
{
BMP_FILE *bf;
bf=BMP_File_fromFile(fname);
if (bf) {
_dumpBmpFileHeader(bf->fileHeader);
_dumpBmpImageHeader(bf->imageHeader);
BMP_File_free(bf);
}
return 0;
}
int AQH_Tool_ExportBmpFile(const char *fname)
{
BMP_FILE *bf;
bf=BMP_File_fromFile(fname);
if (bf) {
int rv;
_dumpBmpFileHeader(bf->fileHeader);
_dumpBmpImageHeader(bf->imageHeader);
if (bf->imageHeader->bitsPerPixel==1)
rv=_exportBmp_1bpp(bf);
else if (bf->imageHeader->bitsPerPixel==8)
rv=_exportBmp_gray8bpp(bf);
else {
fprintf(stderr, "Invalid bits per pixel (%d)", bf->imageHeader->bitsPerPixel);
rv=2;
}
BMP_File_free(bf);
return rv;
}
return 2;;
}
int _exportBmp_1bpp(const BMP_FILE *bf)
{
const uint8_t *ptrBuffer;
uint32_t offsPixels;
const uint8_t *ptrPixels;
int imageWidth;
int imageHeight;
int rowWidthInBytes;
int columns;
int y;
ptrBuffer=(const uint8_t *) GWEN_Buffer_GetStart(bf->buffer);
offsPixels=bf->fileHeader->offsPixels;
ptrPixels=ptrBuffer+offsPixels;
imageWidth=bf->imageHeader->imgWidth;
imageHeight=bf->imageHeader->imgHeight;
columns=imageWidth/8;
rowWidthInBytes=4*((columns+3)/4); /* BMPs have multiple of 4 bytes per row! */
fprintf(stdout, "imgData: \n");
fprintf(stdout, " .dw %d, %d\n", imageWidth, imageHeight);
for (y=imageHeight-1; y>=0; y--) {
const uint8_t *rowPtr;
int x;
fprintf(stdout, " .db ");
rowPtr=ptrPixels+(y*rowWidthInBytes);
for (x=0; x<columns; x++) {
if (x)
fprintf(stdout, ", ");
fprintf(stdout, "0x%02x", rowPtr[x]^0xff);
}
fprintf(stdout, "\n");
}
return 0;
}
int _exportBmp_gray8bpp(const BMP_FILE *bf)
{
const uint8_t *ptrBuffer;
uint32_t offsPixels;
const uint8_t *ptrPixels;
int imageWidth;
int imageHeight;
GWEN_BUFFER *pixelBuf;
GWEN_BUFFER *asmBuf;
ptrBuffer=(const uint8_t *) GWEN_Buffer_GetStart(bf->buffer);
offsPixels=bf->fileHeader->offsPixels;
ptrPixels=ptrBuffer+offsPixels;
imageWidth=bf->imageHeader->imgWidth;
imageHeight=bf->imageHeader->imgHeight;
pixelBuf=_extractPixels_gray8bpp(ptrPixels, imageWidth, imageHeight);
asmBuf=_rleEncode((const uint8_t*) GWEN_Buffer_GetStart(pixelBuf), GWEN_Buffer_GetUsedBytes(pixelBuf));
_printBytes_ASM("imageData",
(const uint8_t*) GWEN_Buffer_GetStart(asmBuf), GWEN_Buffer_GetUsedBytes(asmBuf),
imageWidth, imageHeight);
fprintf(stderr, "Compression: %d -> %d bytes\n", GWEN_Buffer_GetUsedBytes(pixelBuf), GWEN_Buffer_GetUsedBytes(asmBuf));
GWEN_Buffer_free(asmBuf);
GWEN_Buffer_free(pixelBuf);
return 0;
}
GWEN_BUFFER *_extractPixels_gray8bpp(const uint8_t *ptrPixels, int imageWidth, int imageHeight)
{
int rowWidthInBytes;
int columns;
int y;
GWEN_BUFFER *destBuf;
destBuf=GWEN_Buffer_new(0, 256, 0, 1);
columns=imageWidth;
rowWidthInBytes=4*((columns+3)/4); /* BMPs have multiple of 4 bytes per row! */
for (y=imageHeight-1; y>=0; y--) {
const uint8_t *rowPtr;
int x;
uint8_t currentByte=0;
int packedPixels=0;
rowPtr=ptrPixels+(y*rowWidthInBytes);
for (x=0; x<columns; x++) {
uint8_t pixel;
uint8_t newPix;
pixel=rowPtr[x];
switch(pixel) {
case 0x00: newPix=0b01; break; /* outline color */
case 0x60: newPix=0b10; break; /* color 1 */
case 0xc0: newPix=0b11; break; /* color 2 */
case 0xff: newPix=0b00; break; /* background color */
default: newPix=0b00; break; /* background color */
}
currentByte<<=2;
currentByte|=newPix;
packedPixels++;
if (packedPixels==4) {
GWEN_Buffer_AppendByte(destBuf, currentByte);
packedPixels=0;
currentByte=0;
}
}
}
if (GWEN_Buffer_GetUsedBytes(destBuf)==0) {
GWEN_Buffer_free(destBuf);
return NULL;
}
return destBuf;
}
void _printBytes_ASM(const char *sName, const uint8_t *ptrPixels, int lenPixels, int imageWidth, int imageHeight)
{
int i;
fprintf(stdout, "%s: \n", sName);
fprintf(stdout, " .dw %d, %d ; width, height\n", imageWidth, imageHeight);
for (i=0; i<lenPixels; i++) {
uint8_t currentByte;
currentByte=ptrPixels[i];
if ((i & 15)==0) {
if (i)
fprintf(stdout, "\n");
fprintf(stdout, " .db 0x%02x", currentByte);
}
else {
fprintf(stdout, ", 0x%02x", currentByte);
}
}
fprintf(stdout, "\n");
}
GWEN_BUFFER *_rleEncode(const uint8_t *ptrPixels, int lenPixels)
{
GWEN_BUFFER *resultBuf;
GWEN_BUFFER *currentBuf;
int numCurrentBuf;
resultBuf=GWEN_Buffer_new(0, 256, 0, 1);
currentBuf=GWEN_Buffer_new(0, 128, 0, 1);
while(ptrPixels && lenPixels>0) {
int countRepeats;
countRepeats=_countRepeats(ptrPixels, lenPixels);
if (countRepeats<4) {
GWEN_Buffer_AppendByte(currentBuf, *ptrPixels);
numCurrentBuf=GWEN_Buffer_GetUsedBytes(currentBuf);
if (numCurrentBuf==127) {
GWEN_Buffer_AppendByte(resultBuf, numCurrentBuf); /* bit 7 =0 */
GWEN_Buffer_AppendBytes(resultBuf, GWEN_Buffer_GetStart(currentBuf), numCurrentBuf);
GWEN_Buffer_Reset(currentBuf);
}
ptrPixels++;
lenPixels--;
}
else {
numCurrentBuf=GWEN_Buffer_GetUsedBytes(currentBuf);
if (numCurrentBuf>0) {
GWEN_Buffer_AppendByte(resultBuf, numCurrentBuf); /* bit 7 =0 */
GWEN_Buffer_AppendBytes(resultBuf, GWEN_Buffer_GetStart(currentBuf), numCurrentBuf);
GWEN_Buffer_Reset(currentBuf);
}
GWEN_Buffer_AppendByte(resultBuf, countRepeats | 128);
GWEN_Buffer_AppendByte(resultBuf, *ptrPixels);
ptrPixels+=countRepeats;
lenPixels-=countRepeats;
}
}
numCurrentBuf=GWEN_Buffer_GetUsedBytes(currentBuf);
if (numCurrentBuf>0) {
GWEN_Buffer_AppendByte(resultBuf, numCurrentBuf); /* bit 7 =0 */
GWEN_Buffer_AppendBytes(resultBuf, GWEN_Buffer_GetStart(currentBuf), numCurrentBuf);
}
GWEN_Buffer_free(currentBuf);
if (GWEN_Buffer_GetUsedBytes(resultBuf)==0) {
GWEN_Buffer_free(resultBuf);
return NULL;
}
return resultBuf;
}
int _countRepeats(const uint8_t *ptrPixels, int lenPixels)
{
if (ptrPixels && lenPixels) {
int currentByte;
int count=1;
currentByte=*(ptrPixels++);
lenPixels--;
while(ptrPixels && lenPixels) {
if (*ptrPixels==currentByte) {
count++;
if (count==127)
return count;
}
else
return count;
ptrPixels++;
lenPixels--;
}
return count;
}
return 0;
}
BMP_FILE *BMP_File_new(const char *fname)
{
BMP_FILE *bf;
GWEN_NEW_OBJECT(BMP_FILE, bf);
bf->filename=fname?strdup(fname):NULL;
return bf;
}
void BMP_File_free(BMP_FILE *bf)
{
if (bf) {
free(bf->filename);
_imageHeader_free(bf->imageHeader);
_fileHeader_free(bf->fileHeader);
GWEN_Buffer_free(bf->buffer);
GWEN_FREE_OBJECT(bf);
}
}
BMP_FILE *BMP_File_fromFile(const char *fname)
{
BMP_FILE *bf;
const uint8_t *ptrBuffer;
uint32_t lenBuffer;
bf=BMP_File_new(fname);
bf->buffer=_readFileIntoBuffer(fname);
if (bf->buffer==NULL) {
fprintf(stderr, "Error reading bmp file \"%s\"\n", fname);
BMP_File_free(bf);
return NULL;
}
ptrBuffer=(const uint8_t *) GWEN_Buffer_GetStart(bf->buffer);
lenBuffer=GWEN_Buffer_GetUsedBytes(bf->buffer);
bf->fileHeader=_readFileHeaderAt(ptrBuffer, lenBuffer, 0);
if (bf->fileHeader==NULL) {
fprintf(stderr, "Error reading bmp file header from \"%s\"\n", fname);
BMP_File_free(bf);
return NULL;
}
bf->imageHeader=_readImageHeaderAt(ptrBuffer, lenBuffer, BMP_FILE_OFFS_IMAGEHEADER);
if (bf->imageHeader==NULL) {
fprintf(stderr, "Error reading bmp image header from \"%s\"\n", fname);
BMP_File_free(bf);
return NULL;
}
return bf;
}
BMP_FILEHEADER *_fileHeader_new()
{
BMP_FILEHEADER *fh;
GWEN_NEW_OBJECT(BMP_FILEHEADER, fh);
return fh;
}
void _fileHeader_free(BMP_FILEHEADER *fh)
{
if (fh) {
GWEN_FREE_OBJECT(fh);
}
}
BMP_IMAGEHEADER *_imageHeader_new()
{
BMP_IMAGEHEADER *ih;
GWEN_NEW_OBJECT(BMP_IMAGEHEADER, ih);
return ih;
}
void _imageHeader_free(BMP_IMAGEHEADER *ih)
{
if (ih) {
GWEN_FREE_OBJECT(ih);
}
}
void _dumpBmpFileHeader(const BMP_FILEHEADER *fh)
{
fprintf(stderr, "BMP File Header:\n");
fprintf(stderr, "- file type: %d\n", fh->fileType);
fprintf(stderr, "- file size: %d\n", fh->fileSize);
fprintf(stderr, "- Pixels at: %08x\n", fh->offsPixels);
}
void _dumpBmpImageHeader(const BMP_IMAGEHEADER *ih)
{
fprintf(stderr, "BMP Image Header:\n");
fprintf(stderr, "- dims : %d x %d (%d planes, %d bpp)\n", ih->imgWidth, ih->imgHeight, ih->imgPlanes, ih->bitsPerPixel);
fprintf(stderr, "- compression : %d\n", ih->compression);
fprintf(stderr, "- image size : %d\n", ih->imgSize);
fprintf(stderr, "- resolution : %d x %d per meter\n", ih->imgXPixelsPerMeter, ih->imgYPixelsPerMeter);
fprintf(stderr, "- used colormap: %d entries (%d important)\n", ih->colorMapUsedEntries, ih->colorMapImportantColors);
}
BMP_FILEHEADER *_readFileHeaderAt(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset)
{
BMP_FILEHEADER *fh;
if (lenBuffer<(offset+BMP_FILEHEADER_SIZE)) {
DBG_ERROR(NULL, "Offset 0x%08x out of boundary", offset);
return NULL;
}
fh=_fileHeader_new();
fh->fileType=_readUint16At(ptrBuffer, lenBuffer, offset+BMP_FILEHEADER_OFFS_TYPE, 0);
fh->fileSize=_readUint32At(ptrBuffer, lenBuffer, offset+BMP_FILEHEADER_OFFS_FSIZE, 0);
fh->offsPixels=_readUint32At(ptrBuffer, lenBuffer, offset+BMP_FILEHEADER_OFFS_PIXELOFFS, 0xffffffff);
return fh;
}
BMP_IMAGEHEADER *_readImageHeaderAt(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset)
{
BMP_IMAGEHEADER *ih;
uint32_t hsize;
hsize=_readUint32At(ptrBuffer, lenBuffer, BMP_FILE_OFFS_IMAGEHEADER, 0xffffffff);
if (lenBuffer<(offset+hsize)) {
DBG_ERROR(NULL, "Offset 0x%08x out of boundary", offset);
return NULL;
}
ih=_imageHeader_new();
ih->imgWidth=_readInt32At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_WIDTH, 0);
ih->imgHeight=_readInt32At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_HEIGHT, 0);
ih->imgPlanes=_readUint16At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_PLANES, 0);
ih->bitsPerPixel=_readUint16At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_BPP, 0);
ih->compression=_readInt32At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_COMPR, 0);
ih->imgSize=_readInt32At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_IMGSIZE, 0);
ih->imgXPixelsPerMeter=_readInt32At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_XPPM, 0);
ih->imgYPixelsPerMeter=_readInt32At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_YPPM, 0);
ih->colorMapUsedEntries=_readInt32At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_COLORMAPENTRIES, 0);
ih->colorMapImportantColors=_readInt32At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_IMPORTANT, 0);
return ih;
}
uint16_t _readUint16At(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset, uint16_t defaultValue)
{
uint16_t v;
if (lenBuffer<(offset+2)) {
DBG_ERROR(NULL, "Offset 0x%08x out of boundary", offset);
return defaultValue;
}
v=ptrBuffer[offset]+(ptrBuffer[offset+1]<<8);
return v;
}
uint32_t _readUint32At(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset, uint32_t defaultValue)
{
uint32_t v;
if (lenBuffer<(offset+4)) {
DBG_ERROR(NULL, "Offset 0x%08x out of boundary", offset);
return defaultValue;
}
v=ptrBuffer[offset]+(ptrBuffer[offset+1]<<8)+(ptrBuffer[offset+2]<<16)+(ptrBuffer[offset+2]<<16);
return v;
}
int32_t _readInt32At(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset, int32_t defaultValue)
{
uint32_t v;
if (lenBuffer<(offset+4)) {
DBG_ERROR(NULL, "Offset 0x%08x out of boundary", offset);
return defaultValue;
}
v=ptrBuffer[offset]+(ptrBuffer[offset+1]<<8)+(ptrBuffer[offset+2]<<16)+(ptrBuffer[offset+2]<<16);
return (int32_t) v;
}
GWEN_BUFFER *_readFileIntoBuffer(const char *fname)
{
GWEN_BUFFER *buf;
int rv;
buf=GWEN_Buffer_new(0, 1024, 0, 1);
rv=GWEN_SyncIo_Helper_ReadFile(fname, buf);
if (rv<0) {
DBG_ERROR(NULL, "Error reading file \"%s\": %d", fname?fname:"<empty>", rv);
GWEN_Buffer_free(buf);
return NULL;
}
return buf;
}

View File

@@ -0,0 +1,69 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQHOME_TOOL_READBMP_H
#define AQHOME_TOOL_READBMP_H
#include <gwenhywfar/db.h>
#include <gwenhywfar/endpoint.h>
#include <aqhome/data/value.h>
#include <aqhome/data/device.h>
#include <aqhome/events2/eventloop.h>
#include <aqhome/events2/object.h>
#include <aqhome/ipc2/message.h>
typedef struct BMP_FILEHEADER BMP_FILEHEADER;
struct BMP_FILEHEADER {
uint16_t fileType;
uint32_t fileSize;
uint32_t offsPixels;
};
typedef struct BMP_IMAGEHEADER BMP_IMAGEHEADER;
struct BMP_IMAGEHEADER {
int32_t imgWidth;
int32_t imgHeight;
uint16_t imgPlanes; /* 1 */
uint16_t bitsPerPixel;
uint32_t compression;
uint32_t imgSize;
uint32_t imgXPixelsPerMeter;
uint32_t imgYPixelsPerMeter;
uint32_t colorMapUsedEntries;
uint32_t colorMapImportantColors;
};
typedef struct BMP_FILE BMP_FILE;
struct BMP_FILE {
char *filename;
BMP_FILEHEADER *fileHeader;
BMP_IMAGEHEADER *imageHeader;
GWEN_BUFFER *buffer;
};
int AQH_Tool_DumpBmpFile(GWEN_DB_NODE *dbGlobalArgs, int argc, char **argv);
BMP_FILE *BMP_File_new(const char *fname);
void BMP_File_free(BMP_FILE *bf);
BMP_FILE *BMP_File_fromFile(const char *fname);
#endif

View File

@@ -1,6 +1,6 @@
/**************************************************************************** /****************************************************************************
* This file is part of the project AqHome. * This file is part of the project AqHome.
* AqHome (c) by 2025 Martin Preuss, all rights reserved. * AqHome (c) by 2026 Martin Preuss, all rights reserved.
* *
* The license for this file can be found in the file COPYING which you * The license for this file can be found in the file COPYING which you
* should have received along with this file. * should have received along with this file.
@@ -12,6 +12,7 @@
#include "./nodes/ping.h" #include "./nodes/ping.h"
#include "./nodes/flash.h" #include "./nodes/flash.h"
#include "./nodes/settime.h"
#include "./nodes/getnodes.h" #include "./nodes/getnodes.h"
#include "./data/getvalues.h" #include "./data/getvalues.h"
#include "./data/getdevices.h" #include "./data/getdevices.h"
@@ -25,6 +26,7 @@
#include "./data/watch.h" #include "./data/watch.h"
#include "./data/devicestate.h" #include "./data/devicestate.h"
#include "./data/imgperioddata.h" #include "./data/imgperioddata.h"
#include "./image/readbmp.h"
#include <aqhome/api.h> #include <aqhome/api.h>
#include <aqhome/aqhome.h> #include <aqhome/aqhome.h>
@@ -90,6 +92,7 @@ int main(int argc, char **argv)
}; };
const GWEN_FUNCS cmdDefArray[]= { const GWEN_FUNCS cmdDefArray[]= {
GWEN_FE_DAH("ping", AQH_Tool_Ping, I18N("Ping a given node on the network")), GWEN_FE_DAH("ping", AQH_Tool_Ping, I18N("Ping a given node on the network")),
GWEN_FE_DAH("time", AQH_Tool_SetTime, I18N("Set node network time (handled by RTC module)")),
GWEN_FE_DAH("flash", AQH_Tool_Flash, I18N("Flash a given node on the network")), GWEN_FE_DAH("flash", AQH_Tool_Flash, I18N("Flash a given node on the network")),
GWEN_FE_DAH("getnodes", AQH_Tool_GetNodes, I18N("Request list of known devices on the network")), GWEN_FE_DAH("getnodes", AQH_Tool_GetNodes, I18N("Request list of known devices on the network")),
GWEN_FE_DAH("getvalues", AQH_Tool_GetValues, I18N("Request list of known values on the data server")), GWEN_FE_DAH("getvalues", AQH_Tool_GetValues, I18N("Request list of known values on the data server")),
@@ -105,6 +108,7 @@ int main(int argc, char **argv)
GWEN_FE_DAH("watch", AQH_Tool_Watch, I18N("Watch and print changes of values on the data server")), GWEN_FE_DAH("watch", AQH_Tool_Watch, I18N("Watch and print changes of values on the data server")),
GWEN_FE_DAH("devicestate", AQH_Tool_DeviceState, I18N("Show state of devices")), GWEN_FE_DAH("devicestate", AQH_Tool_DeviceState, I18N("Show state of devices")),
GWEN_FE_DAH("imgperioddata", AQH_Tool_ImgPeriodData, I18N("Create diagram of datapoints from a date range")), GWEN_FE_DAH("imgperioddata", AQH_Tool_ImgPeriodData, I18N("Create diagram of datapoints from a date range")),
GWEN_FE_DAH("dumpbmp", AQH_Tool_DumpBmpFile, I18N("Dump headers of BMP file")),
GWEN_FE_END(), GWEN_FE_END(),
}; };
const GWEN_FUNCS *func; const GWEN_FUNCS *func;

View File

@@ -36,6 +36,7 @@
ping.h ping.h
flash.h flash.h
getnodes.h getnodes.h
settime.h
</headers> </headers>
<sources> <sources>
@@ -44,6 +45,7 @@
ping.c ping.c
flash.c flash.c
getnodes.c getnodes.c
settime.c
</sources> </sources>
<useTargets> <useTargets>

View File

@@ -0,0 +1,139 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2026 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./settime.h"
#include "../client.h"
#include "../utils.h"
#include "aqhome/msg/ipc/m_ipc.h"
#include "aqhome/msg/ipc/m_ipc_result.h"
#include "aqhome/msg/ipc/nodes/m_ipcn.h"
#include "aqhome/msg/ipc/nodes/m_ipcn_forward.h"
#include "aqhome/msg/node/m_node.h"
#include "aqhome/msg/node/m_time.h"
#include <gwenhywfar/args.h>
#include <gwenhywfar/i18n.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/text.h>
/* ------------------------------------------------------------------------------------------------
* defs
* ------------------------------------------------------------------------------------------------
*/
#define I18S(msg) msg
#define I18N(msg) GWEN_I18N_Translate(PACKAGE, msg)
#define A_ARG GWEN_ARGS_FLAGS_HAS_ARGUMENT
#define A_END (GWEN_ARGS_FLAGS_HELP | GWEN_ARGS_FLAGS_LAST)
#define A_CHAR GWEN_ArgsType_Char
#define A_INT GWEN_ArgsType_Int
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static AQH_MESSAGE *_createRequestMessage(AQH_OBJECT *o, uint32_t msgId);
static int _handleResponseMessage(AQH_OBJECT *o, const AQH_MESSAGE *msg, const GWEN_TAG16_LIST *tagList, int first);
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
int AQH_Tool_SetTime(GWEN_DB_NODE *dbGlobalArgs, int argc, char **argv)
{
AQH_EVENT_LOOP *eventLoop;
AQH_OBJECT *o;
int rv;
const GWEN_ARGS args[]= {
/* flags type name min max s long short_descr, long_descr */
{ A_ARG, A_CHAR, "tcpAddress", 0, 1, "t", "tcpaddress", I18S("TCP address to connect to [127.0.0.1]"), NULL},
{ A_ARG, A_INT, "tcpPort", 0, 1, "P", "tcpport", I18S("Specify the TCP port to listen on"), NULL},
{ A_ARG, A_INT, "timeout", 0, 1, "T", NULL, I18S("Specify timeout in seconds for response"), NULL},
{ A_ARG, A_CHAR, "userId", 0, 1, "u", "userid", I18S("Specify user id"), NULL},
{ A_ARG, A_CHAR, "password", 0, 1, "p", "password", I18S("Specify service password"), NULL},
{ A_ARG, A_INT, "nodeAddr", 1, 1, "n", "nodeaddr", I18S("Specify bus addr of the node to ping"), NULL},
{ A_END, A_INT, "help", 0, 0, "h", "help", I18S("Show this help screen"), NULL}
};
eventLoop=AQH_EventLoop_new();
o=AQH_ToolClient_new(eventLoop, AQH_IPC_PROTOCOL_NODES_ID, AQH_IPC_PROTOCOL_NODES_VERSION, dbGlobalArgs, args);
AQH_ToolClient_SetCreateRequestMessageFn(o, _createRequestMessage);
AQH_ToolClient_SetHandleResponseMessageFn(o, _handleResponseMessage);
rv=AQH_ToolClient_ReadLocalArgs(o, argc, argv);
if (rv!=0)
return rv;
rv=AQH_ToolClient_Connect(o, AQH_TOOL_CLIENT_CONNECTFLAGS_WITHCONNECTMSG | AQH_TOOL_CLIENT_CONNECTFLAGS_WITHGRPMSG,
0, AQH_MSG_TYPEGROUP_ALL);
if (rv<0) {
DBG_INFO(NULL, "here(%d)", rv);
AQH_Object_free(o);
AQH_EventLoop_free(eventLoop);
return rv;
}
rv=AQH_ToolClient_RunConnectedWithNodeMsgs(o);
AQH_Object_free(o);
AQH_EventLoop_free(eventLoop);
return rv;
}
AQH_MESSAGE *_createRequestMessage(GWEN_UNUSED AQH_OBJECT *o, GWEN_UNUSED uint32_t msgId)
{
AQH_MESSAGE *nodeMsg;
GWEN_DB_NODE *dbArgs;
int nodeAddr;
GWEN_TIMESTAMP *ts;
dbArgs=AQH_ToolClient_GetDbLocalArgs(o);
nodeAddr=GWEN_DB_GetIntValue(dbArgs, "nodeAddr", 0, 0);
ts=GWEN_Timestamp_NowInLocalTime();
nodeMsg=AQH_TimeMessage_new(nodeAddr, AQH_TOOL_CLIENT_NODEADDR, AQH_MSG_TYPE_TIME_REQSET, 0, ts);
return nodeMsg;
}
int _handleResponseMessage(AQH_OBJECT *o, const AQH_MESSAGE *msg, GWEN_UNUSED const GWEN_TAG16_LIST *tagList, GWEN_UNUSED int first)
{
GWEN_DB_NODE *dbArgs;
uint16_t code;
int nodeAddr;
dbArgs=AQH_ToolClient_GetDbLocalArgs(o);
nodeAddr=GWEN_DB_GetIntValue(dbArgs, "nodeAddr", 0, 0);
code=AQH_NodeMessage_GetMsgType(msg);
if ((code==AQH_MSG_TYPE_TIME_RSPSET) &&
(nodeAddr==0 || nodeAddr==0xff || nodeAddr==AQH_NodeMessage_GetSourceAddress(msg))) {
return 1;
}
else {
DBG_INFO(NULL, "Unexpected message \"%d\"", code);
return 0;
}
}

View File

@@ -1,21 +1,20 @@
/**************************************************************************** /****************************************************************************
* This file is part of the project AqHome. * This file is part of the project AqHome.
* AqHome (c) by 2023 Martin Preuss, all rights reserved. * AqHome (c) by 2026 Martin Preuss, all rights reserved.
* *
* The license for this file can be found in the file COPYING which you * The license for this file can be found in the file COPYING which you
* should have received along with this file. * should have received along with this file.
****************************************************************************/ ****************************************************************************/
#ifndef AQHOME_STORAGE_HTTP_H #ifndef AQHOME_TOOL_SETTIME_H
#define AQHOME_STORAGE_HTTP_H #define AQHOME_TOOL_SETTIME_H
#include "./aqhomestorage.h" #include <gwenhywfar/db.h>
void AqHomeStorage_ReadAndHandleHttpMessages(AQHOME_STORAGE *aqh); int AQH_Tool_SetTime(GWEN_DB_NODE *dbArgs, int argc, char **argv);
#endif #endif

View File

@@ -1,184 +0,0 @@
<devices>
<device id="plug01" name="tasmotaplug" driver="mqtt">
<mqtttopics>
<mqtttopic type="json" direction="in" name="sensor">
<topic>tele/tasmota/plug01/SENSOR</topic>
<mask>tele/tasmota/*/SENSOR</mask>
<beforeId>tele/tasmota/</beforeId>
<afterId>/SENSOR</afterId>
<values>
<value name="powerusage" units="W" path="ENERGY/POWER" type="sensor"></value>
<value name="totalenergy" units="kWh" path="ENERGY/TOTAL" type="sensor"></value>
<value name="apparentpower" units="VA" path="ENERGY/ApparentPower" type="sensor"></value>
<value name="reactivepower" units="VAr" path="ENERGY/ReactivePower" type="sensor"></value>
<value name="powerfactor" path="ENERGY/Factor" type="sensor"></value>
<value name="voltage" units="V" path="ENERGY/Voltage" type="sensor"></value>
<value name="current" units="A" path="ENERGY/Current" type="sensor"></value>
</values>
</mqtttopic>
<mqtttopic type="num" direction="out" name="power">
<beforeId>cmnd/tasmota/</beforeId>
<afterId>/Power</afterId>
<values>
<value name="power" type="actor"></value>
</values>
</mqtttopic>
</mqtttopics>
</device>
<device id="plug02" name="tasmotaplug" driver="mqtt">
<mqtttopics>
<mqtttopic type="json" direction="in" name="sensor">
<topic>tele/tasmota/plug02/SENSOR</topic>
<mask>tele/tasmota/*/SENSOR</mask>
<beforeId>tele/tasmota/</beforeId>
<afterId>/SENSOR</afterId>
<values>
<value name="powerusage" units="W" path="ENERGY/POWER" type="sensor"></value>
<value name="totalenergy" units="kWh" path="ENERGY/TOTAL" type="sensor"></value>
<value name="apparentpower" units="VA" path="ENERGY/ApparentPower" type="sensor"></value>
<value name="reactivepower" units="VAr" path="ENERGY/ReactivePower" type="sensor"></value>
<value name="powerfactor" path="ENERGY/Factor" type="sensor"></value>
<value name="voltage" units="V" path="ENERGY/Voltage" type="sensor"></value>
<value name="current" units="A" path="ENERGY/Current" type="sensor"></value>
</values>
</mqtttopic>
<mqtttopic type="num" direction="out" name="power">
<beforeId>cmnd/tasmota/</beforeId>
<afterId>/Power</afterId>
<values>
<value name="power" type="actor"></value>
</values>
</mqtttopic>
</mqtttopics>
</device>
<device id="plug03" name="tasmotaplug" driver="mqtt">
<mqtttopics>
<mqtttopic type="json" direction="in" name="sensor">
<topic>tele/tasmota/plug03/SENSOR</topic>
<mask>tele/tasmota/*/SENSOR</mask>
<beforeId>tele/tasmota/</beforeId>
<afterId>/SENSOR</afterId>
<values>
<value name="powerusage" units="W" path="ENERGY/POWER" type="sensor"></value>
<value name="totalenergy" units="kWh" path="ENERGY/TOTAL" type="sensor"></value>
<value name="apparentpower" units="VA" path="ENERGY/ApparentPower" type="sensor"></value>
<value name="reactivepower" units="VAr" path="ENERGY/ReactivePower" type="sensor"></value>
<value name="powerfactor" path="ENERGY/Factor" type="sensor"></value>
<value name="voltage" units="V" path="ENERGY/Voltage" type="sensor"></value>
<value name="current" units="A" path="ENERGY/Current" type="sensor"></value>
</values>
</mqtttopic>
<mqtttopic type="num" direction="out" name="power">
<beforeId>cmnd/tasmota/</beforeId>
<afterId>/Power</afterId>
<values>
<value name="power" type="actor"></value>
</values>
</mqtttopic>
</mqtttopics>
</device>
<device id="109C2F" name="tasmotaplug" driver="mqtt">
<mqtttopics>
<mqtttopic type="json" direction="in" name="sensor">
<topic>tele/tasmota_109C2F/SENSOR</topic>
<mask>tele/tasmota_*/SENSOR</mask>
<beforeId>tele/tasmota_</beforeId>
<afterId>/SENSOR</afterId>
<values>
<value name="powerusage" units="W" path="ENERGY/POWER" type="sensor"></value>
<value name="totalenergy" units="kWh" path="ENERGY/TOTAL" type="sensor"></value>
<value name="apparentpower" units="VA" path="ENERGY/ApparentPower" type="sensor"></value>
<value name="reactivepower" units="VAr" path="ENERGY/ReactivePower" type="sensor"></value>
<value name="powerfactor" path="ENERGY/Factor" type="sensor"></value>
<value name="voltage" units="V" path="ENERGY/Voltage" type="sensor"></value>
<value name="current" units="A" path="ENERGY/Current" type="sensor"></value>
</values>
</mqtttopic>
<mqtttopic type="num" direction="out" name="power">
<beforeId>cmnd/tasmota/</beforeId>
<afterId>/Power</afterId>
<values>
<value name="power" type="actor"></value>
</values>
</mqtttopic>
</mqtttopics>
</device>
<device id="10359B" name="tasmotaplug" driver="mqtt">
<mqtttopics>
<mqtttopic type="json" direction="in" name="sensor">
<topic>tele/tasmota_10359B/SENSOR</topic>
<mask>tele/tasmota_*/SENSOR</mask>
<beforeId>tele/tasmota_</beforeId>
<afterId>/SENSOR</afterId>
<values>
<value name="powerusage" units="W" path="ENERGY/POWER" type="sensor"></value>
<value name="totalenergy" units="kWh" path="ENERGY/TOTAL" type="sensor"></value>
<value name="apparentpower" units="VA" path="ENERGY/ApparentPower" type="sensor"></value>
<value name="reactivepower" units="VAr" path="ENERGY/ReactivePower" type="sensor"></value>
<value name="powerfactor" path="ENERGY/Factor" type="sensor"></value>
<value name="voltage" units="V" path="ENERGY/Voltage" type="sensor"></value>
<value name="current" units="A" path="ENERGY/Current" type="sensor"></value>
</values>
</mqtttopic>
<mqtttopic type="num" direction="out" name="power">
<beforeId>cmnd/tasmota_</beforeId>
<afterId>/Power</afterId>
<values>
<value name="power" type="actor"></value>
</values>
</mqtttopic>
</mqtttopics>
</device>
<device id="0F25C2" name="tasmotaplug" driver="mqtt">
<mqtttopics>
<mqtttopic type="json" direction="in" name="sensor">
<topic>tele/tasmota_0F25C2/SENSOR</topic>
<mask>tele/tasmota_*/SENSOR</mask>
<beforeId>tele/tasmota_</beforeId>
<afterId>/SENSOR</afterId>
<values>
<value name="powerusage" units="W" path="ENERGY/POWER" type="sensor"></value>
<value name="totalenergy" units="kWh" path="ENERGY/TOTAL" type="sensor"></value>
<value name="apparentpower" units="VA" path="ENERGY/ApparentPower" type="sensor"></value>
<value name="reactivepower" units="VAr" path="ENERGY/ReactivePower" type="sensor"></value>
<value name="powerfactor" path="ENERGY/Factor" type="sensor"></value>
<value name="voltage" units="V" path="ENERGY/Voltage" type="sensor"></value>
<value name="current" units="A" path="ENERGY/Current" type="sensor"></value>
</values>
</mqtttopic>
<mqtttopic type="num" direction="out" name="power">
<beforeId>cmnd/tasmota_</beforeId>
<afterId>/Power</afterId>
<values>
<value name="power" type="actor"></value>
</values>
</mqtttopic>
</mqtttopics>
</device>
<device id="10A4B7" name="tasmotaplug" driver="mqtt">
<mqtttopics>
<mqtttopic type="json" direction="in" name="sensor">
<topic>tele/tasmota_10A4B7/SENSOR</topic>
<mask>tele/tasmota_*/SENSOR</mask>
<beforeId>tele/tasmota_</beforeId>
<afterId>/SENSOR</afterId>
<values>
<value name="powerusage" units="W" path="ENERGY/POWER" type="sensor"></value>
<value name="totalenergy" units="kWh" path="ENERGY/TOTAL" type="sensor"></value>
<value name="apparentpower" units="VA" path="ENERGY/ApparentPower" type="sensor"></value>
<value name="reactivepower" units="VAr" path="ENERGY/ReactivePower" type="sensor"></value>
<value name="powerfactor" path="ENERGY/Factor" type="sensor"></value>
<value name="voltage" units="V" path="ENERGY/Voltage" type="sensor"></value>
<value name="current" units="A" path="ENERGY/Current" type="sensor"></value>
</values>
</mqtttopic>
<mqtttopic type="num" direction="out" name="power">
<beforeId>cmnd/tasmota_</beforeId>
<afterId>/Power</afterId>
<values>
<value name="power" type="actor"></value>
</values>
</mqtttopic>
</mqtttopics>
</device>
</devices>

View File

@@ -1,238 +0,0 @@
==2732905== Memcheck, a memory error detector
==2732905== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==2732905== Using Valgrind-3.19.0-8d3c8034b8-20220411 and LibVEX; rerun with -h for copyright info
==2732905== Command: 0-build/apps/aqhome-react/aqhome-react -p ./aqhome-react.pid -V ./aqhome-react.vars -ba 127.0.0.1 -T 350
==2732905== Parent PID: 2732904
==2732905==
--2732905--
--2732905-- Valgrind options:
--2732905-- --tool=memcheck
--2732905-- --trace-children=yes
--2732905-- -v
--2732905-- --log-file=aqhome-react.vg
--2732905-- --leak-check=full
--2732905-- --show-reachable=yes
--2732905-- --track-origins=yes
--2732905-- --num-callers=50
--2732905-- --keep-stacktraces=alloc-and-free
--2732905-- Contents of /proc/version:
--2732905-- Linux version 6.1.0-31-amd64 (debian-kernel@lists.debian.org) (gcc-12 (Debian 12.2.0-14) 12.2.0, GNU ld (GNU Binutils for Debian) 2.40) #1 SMP PREEMPT_DYNAMIC Debian 6.1.128-1 (2025-02-07)
--2732905--
--2732905-- Arch and hwcaps: AMD64, LittleEndian, amd64-cx16-lzcnt-rdtscp-sse3-ssse3-avx-avx2-bmi-f16c-rdrand-rdseed
--2732905-- Page sizes: currently 4096, max supported 4096
--2732905-- Valgrind library directory: /usr/libexec/valgrind
--2732905-- Reading syms from /home/martin/projekte/c/0_current/hausbus/aqhome/0-build/apps/aqhome-react/aqhome-react
--2732905-- Reading syms from /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
--2732905-- Considering /usr/lib/debug/.build-id/f4/bc47db467906580a47640e01e6d901e2642a7b.debug ..
--2732905-- .. build-id is valid
--2732905-- Reading syms from /usr/libexec/valgrind/memcheck-amd64-linux
--2732905-- Considering /usr/lib/debug/.build-id/82/26c2aa6b808ebd5a6fafb694a7fb3287f33590.debug ..
--2732905-- .. build-id is valid
--2732905-- object doesn't have a dynamic symbol table
--2732905-- Scheduler: using generic scheduler lock implementation.
--2732905-- Reading suppressions file: /usr/libexec/valgrind/default.supp
==2732905== embedded gdbserver: reading from /tmp/vgdb-pipe-from-vgdb-to-2732905-by-martin-on-???
==2732905== embedded gdbserver: writing to /tmp/vgdb-pipe-to-vgdb-from-2732905-by-martin-on-???
==2732905== embedded gdbserver: shared mem /tmp/vgdb-pipe-shared-mem-vgdb-2732905-by-martin-on-???
==2732905==
==2732905== TO CONTROL THIS PROCESS USING vgdb (which you probably
==2732905== don't want to do, unless you know exactly what you're doing,
==2732905== or are doing some strange experiment):
==2732905== /usr/bin/vgdb --pid=2732905 ...command...
==2732905==
==2732905== TO DEBUG THIS PROCESS USING GDB: start GDB like this
==2732905== /path/to/gdb 0-build/apps/aqhome-react/aqhome-react
==2732905== and then give GDB the following command
==2732905== target remote | /usr/bin/vgdb --pid=2732905
==2732905== --pid is optional if only one valgrind process is running
==2732905==
--2732905-- REDIR: 0x40238e0 (ld-linux-x86-64.so.2:strlen) redirected to 0x580bb0e2 (vgPlain_amd64_linux_REDIR_FOR_strlen)
--2732905-- REDIR: 0x40220c0 (ld-linux-x86-64.so.2:index) redirected to 0x580bb0fc (vgPlain_amd64_linux_REDIR_FOR_index)
--2732905-- Reading syms from /usr/libexec/valgrind/vgpreload_core-amd64-linux.so
--2732905-- Considering /usr/lib/debug/.build-id/ad/f1388be4d8781737b0c83fe111a5a9c6e930aa.debug ..
--2732905-- .. build-id is valid
--2732905-- Reading syms from /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so
--2732905-- Considering /usr/lib/debug/.build-id/d8/ec66cffcb23a75c3f15940674d6028709121f8.debug ..
--2732905-- .. build-id is valid
==2732905== WARNING: new redirection conflicts with existing -- ignoring it
--2732905-- old: 0x040238e0 (strlen ) R-> (0000.0) 0x580bb0e2 vgPlain_amd64_linux_REDIR_FOR_strlen
--2732905-- new: 0x040238e0 (strlen ) R-> (2007.0) 0x048468a0 strlen
--2732905-- REDIR: 0x40222e0 (ld-linux-x86-64.so.2:strcmp) redirected to 0x4847780 (strcmp)
--2732905-- REDIR: 0x4021550 (ld-linux-x86-64.so.2:mempcpy) redirected to 0x484b1a0 (mempcpy)
--2732905-- Reading syms from /home/martin/projekte/c/0_current/hausbus/aqhome/0-build/aqhome/libaqhome.so.0.0.8
--2732905-- Reading syms from /usr/local/lib/libgwenhywfar.so.79.12.0
--2732905-- Reading syms from /usr/lib/x86_64-linux-gnu/libm.so.6
--2732905-- Considering /usr/lib/debug/.build-id/6d/201df2cb50847f0ed42da4158c3a608d578f03.debug ..
--2732905-- .. build-id is valid
--2732905-- Reading syms from /usr/lib/x86_64-linux-gnu/libc.so.6
--2732905-- Considering /usr/lib/debug/.build-id/c0/47672cae7964324658491e7dee26748ae5d2f8.debug ..
--2732905-- .. build-id is valid
==2732905== WARNING: new redirection conflicts with existing -- ignoring it
--2732905-- old: 0x04bad5d0 (memalign ) R-> (1011.0) 0x04845bc0 memalign
--2732905-- new: 0x04bad5d0 (memalign ) R-> (1017.0) 0x04845b90 aligned_alloc
==2732905== WARNING: new redirection conflicts with existing -- ignoring it
--2732905-- old: 0x04bad5d0 (memalign ) R-> (1011.0) 0x04845bc0 memalign
--2732905-- new: 0x04bad5d0 (memalign ) R-> (1017.0) 0x04845b60 aligned_alloc
==2732905== WARNING: new redirection conflicts with existing -- ignoring it
--2732905-- old: 0x04bad5d0 (memalign ) R-> (1011.0) 0x04845bc0 memalign
--2732905-- new: 0x04bad5d0 (memalign ) R-> (1017.0) 0x04845b90 aligned_alloc
==2732905== WARNING: new redirection conflicts with existing -- ignoring it
--2732905-- old: 0x04bad5d0 (memalign ) R-> (1011.0) 0x04845bc0 memalign
--2732905-- new: 0x04bad5d0 (memalign ) R-> (1017.0) 0x04845b60 aligned_alloc
--2732905-- Reading syms from /usr/lib/x86_64-linux-gnu/libgpg-error.so.0.33.1
--2732905-- object doesn't have a symbol table
--2732905-- Reading syms from /usr/lib/x86_64-linux-gnu/libgcrypt.so.20.4.1
--2732905-- object doesn't have a symbol table
--2732905-- Reading syms from /usr/lib/x86_64-linux-gnu/libgnutls.so.30.34.3
--2732905-- object doesn't have a symbol table
--2732905-- Reading syms from /usr/lib/x86_64-linux-gnu/libp11-kit.so.0.3.0
--2732905-- object doesn't have a symbol table
--2732905-- Reading syms from /usr/lib/x86_64-linux-gnu/libidn2.so.0.3.8
--2732905-- object doesn't have a symbol table
--2732905-- Reading syms from /usr/lib/x86_64-linux-gnu/libunistring.so.2.2.0
--2732905-- object doesn't have a symbol table
--2732905-- Reading syms from /usr/lib/x86_64-linux-gnu/libtasn1.so.6.6.3
--2732905-- object doesn't have a symbol table
--2732905-- Reading syms from /usr/lib/x86_64-linux-gnu/libnettle.so.8.6
--2732905-- object doesn't have a symbol table
--2732905-- Reading syms from /usr/lib/x86_64-linux-gnu/libhogweed.so.6.6
--2732905-- object doesn't have a symbol table
--2732905-- Reading syms from /usr/lib/x86_64-linux-gnu/libgmp.so.10.4.1
--2732905-- object doesn't have a symbol table
--2732905-- Reading syms from /usr/lib/x86_64-linux-gnu/libffi.so.8.1.2
--2732905-- object doesn't have a symbol table
--2732905-- REDIR: 0x4bb3510 (libc.so.6:strnlen) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb35a0 (libc.so.6:strpbrk) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb16c0 (libc.so.6:strcmp) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bc64a0 (libc.so.6:wcsnlen) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb0800 (libc.so.6:memset) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bc4de0 (libc.so.6:wcslen) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bafe30 (libc.so.6:memcpy@@GLIBC_2.14) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bc4c10 (libc.so.6:wcschr) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb15b0 (libc.so.6:index) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb35d0 (libc.so.6:rindex) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bc4ca0 (libc.so.6:wcscmp) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb0a40 (libc.so.6:stpncpy) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bc51f0 (libc.so.6:wmemchr) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb33c0 (libc.so.6:strncmp) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb0ab0 (libc.so.6:strcasecmp) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb2920 (libc.so.6:strcspn) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bc4d30 (libc.so.6:wcscpy) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb1530 (libc.so.6:strcat) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb32b0 (libc.so.6:strncasecmp_l) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bafd40 (libc.so.6:bcmp) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb0770 (libc.so.6:memrchr) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb1630 (libc.so.6:strchrnul) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb28a0 (libc.so.6:strcpy) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb0b50 (libc.so.6:strcasecmp_l) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb3180 (libc.so.6:strlen) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb3460 (libc.so.6:strncpy) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb09c0 (libc.so.6:stpcpy) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb0550 (libc.so.6:memmove) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
==2732905== Preferring higher priority redirection:
--2732905-- old: 0x04c66940 (__memcpy_avx_unalign) R-> (2018.0) 0x04848a60 __memcpy_avx_unaligned_erms
--2732905-- new: 0x04c66940 (__memcpy_avx_unalign) R-> (2018.1) 0x0484a2b0 memmove
--2732905-- REDIR: 0x4bafcc0 (libc.so.6:memchr) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb3790 (libc.so.6:strspn) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb0660 (libc.so.6:mempcpy) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb3210 (libc.so.6:strncasecmp) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb0900 (libc.so.6:rawmemchr) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4bb3350 (libc.so.6:strncat) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4c2a050 (libc.so.6:__memcpy_chk) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
--2732905-- REDIR: 0x4c2a160 (libc.so.6:__memmove_chk) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
==2732905== WARNING: new redirection conflicts with existing -- ignoring it
--2732905-- old: 0x04c66900 (__memcpy_chk_avx_una) R-> (2030.0) 0x0484b290 __memcpy_chk
--2732905-- new: 0x04c66900 (__memcpy_chk_avx_una) R-> (2024.0) 0x0484ac30 __memmove_chk
--2732905-- REDIR: 0x4bb3f30 (libc.so.6:strstr) redirected to 0x48371b0 (_vgnU_ifunc_wrapper)
==2732905== WARNING: new redirection conflicts with existing -- ignoring it
--2732905-- old: 0x04c66900 (__memcpy_chk_avx_una) R-> (2030.0) 0x0484b290 __memcpy_chk
--2732905-- new: 0x04c66900 (__memcpy_chk_avx_una) R-> (2024.0) 0x0484ac30 __memmove_chk
--2732905-- REDIR: 0x4c6ca10 (libc.so.6:__strrchr_avx2) redirected to 0x48462e0 (rindex)
--2732905-- REDIR: 0x4c6a0c0 (libc.so.6:__strlen_avx2) redirected to 0x4846780 (strlen)
--2732905-- REDIR: 0x4c661e0 (libc.so.6:__memcmp_avx2_movbe) redirected to 0x4849aa0 (bcmp)
--2732905-- REDIR: 0x4c6b790 (libc.so.6:__strncmp_avx2) redirected to 0x4846ed0 (strncmp)
--2732905-- REDIR: 0x4c693a0 (libc.so.6:__strchr_avx2) redirected to 0x4846460 (index)
--2732905-- REDIR: 0x4c697d0 (libc.so.6:__strcmp_avx2) redirected to 0x4847680 (strcmp)
--2732905-- REDIR: 0x4bac8f0 (libc.so.6:malloc) redirected to 0x4840740 (malloc)
--2732905-- REDIR: 0x4c66940 (libc.so.6:__memcpy_avx_unaligned_erms) redirected to 0x484a2b0 (memmove)
--2732905-- REDIR: 0x4bad6a0 (libc.so.6:calloc) redirected to 0x4845540 (calloc)
--2732905-- REDIR: 0x4c66900 (libc.so.6:__memcpy_chk_avx_unaligned_erms) redirected to 0x484b290 (__memcpy_chk)
--2732905-- REDIR: 0x4baceb0 (libc.so.6:free) redirected to 0x4843110 (free)
--2732905-- REDIR: 0x4c68320 (libc.so.6:__strcasecmp_avx2) redirected to 0x4847050 (strcasecmp)
--2732905-- REDIR: 0x4c65f40 (libc.so.6:__memchr_avx2) redirected to 0x4847800 (memchr)
--2732905-- REDIR: 0x4bc41b0 (libc.so.6:__strstr_sse2_unaligned) redirected to 0x484b3a0 (strstr)
--2732905-- REDIR: 0x4bad0f0 (libc.so.6:realloc) redirected to 0x48457b0 (realloc)
--2732905-- REDIR: 0x4c67480 (libc.so.6:__rawmemchr_avx2) redirected to 0x484acd0 (rawmemchr)
--2732905-- REDIR: 0x4c695e0 (libc.so.6:__strchrnul_avx2) redirected to 0x484aca0 (strchrnul)
--2732905-- REDIR: 0x4c668f0 (libc.so.6:__mempcpy_avx_unaligned_erms) redirected to 0x484adb0 (mempcpy)
--2732905-- REDIR: 0x4c67340 (libc.so.6:__memset_avx2_unaligned_erms) redirected to 0x484a1c0 (memset)
--2732905-- REDIR: 0x4c675e0 (libc.so.6:__stpcpy_avx2) redirected to 0x4849b60 (stpcpy)
--2732905-- REDIR: 0x4c6c740 (libc.so.6:__strnlen_avx2) redirected to 0x4846720 (strnlen)
==2732905==
==2732905== HEAP SUMMARY:
==2732905== in use at exit: 192 bytes in 12 blocks
==2732905== total heap usage: 8,221 allocs, 8,209 frees, 1,218,973 bytes allocated
==2732905==
==2732905== Searching for pointers to 12 not-freed blocks
==2732905== Checked 279,376 bytes
==2732905==
==2732905== 48 bytes in 6 blocks are still reachable in loss record 1 of 2
==2732905== at 0x48407B4: malloc (vg_replace_malloc.c:381)
==2732905== by 0x4D323C0: ??? (in /usr/lib/x86_64-linux-gnu/libgcrypt.so.20.4.1)
==2732905== by 0x4D33B9F: ??? (in /usr/lib/x86_64-linux-gnu/libgcrypt.so.20.4.1)
==2732905== by 0x4E112D6: ??? (in /usr/lib/x86_64-linux-gnu/libgcrypt.so.20.4.1)
==2732905== by 0x4D32339: ??? (in /usr/lib/x86_64-linux-gnu/libgcrypt.so.20.4.1)
==2732905== by 0x4D333DE: ??? (in /usr/lib/x86_64-linux-gnu/libgcrypt.so.20.4.1)
==2732905== by 0x4D2E788: gcry_control (in /usr/lib/x86_64-linux-gnu/libgcrypt.so.20.4.1)
==2732905== by 0x4943601: GWEN_Crypt3_ModuleInit (cryptkey.c:36)
==2732905== by 0x49234C9: GWEN_Init (gwenhywfar.c:265)
==2732905== by 0x4921F98: GWEN_LibInit (init.c:48)
==2732905== by 0x40049CD: call_init (dl-init.c:74)
==2732905== by 0x40049CD: call_init (dl-init.c:26)
==2732905== by 0x4004AB3: _dl_init (dl-init.c:121)
==2732905== by 0x401AA7F: ??? (in /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2)
==2732905== by 0x8: ???
==2732905== by 0x1FFF000212: ???
==2732905== by 0x1FFF000239: ???
==2732905== by 0x1FFF00023C: ???
==2732905== by 0x1FFF00024F: ???
==2732905== by 0x1FFF000252: ???
==2732905== by 0x1FFF000266: ???
==2732905== by 0x1FFF00026A: ???
==2732905== by 0x1FFF000274: ???
==2732905== by 0x1FFF000277: ???
==2732905==
==2732905== 144 bytes in 6 blocks are still reachable in loss record 2 of 2
==2732905== at 0x48407B4: malloc (vg_replace_malloc.c:381)
==2732905== by 0x4D323C0: ??? (in /usr/lib/x86_64-linux-gnu/libgcrypt.so.20.4.1)
==2732905== by 0x4D33B9F: ??? (in /usr/lib/x86_64-linux-gnu/libgcrypt.so.20.4.1)
==2732905== by 0x4E112C9: ??? (in /usr/lib/x86_64-linux-gnu/libgcrypt.so.20.4.1)
==2732905== by 0x4D32339: ??? (in /usr/lib/x86_64-linux-gnu/libgcrypt.so.20.4.1)
==2732905== by 0x4D333DE: ??? (in /usr/lib/x86_64-linux-gnu/libgcrypt.so.20.4.1)
==2732905== by 0x4D2E788: gcry_control (in /usr/lib/x86_64-linux-gnu/libgcrypt.so.20.4.1)
==2732905== by 0x4943601: GWEN_Crypt3_ModuleInit (cryptkey.c:36)
==2732905== by 0x49234C9: GWEN_Init (gwenhywfar.c:265)
==2732905== by 0x4921F98: GWEN_LibInit (init.c:48)
==2732905== by 0x40049CD: call_init (dl-init.c:74)
==2732905== by 0x40049CD: call_init (dl-init.c:26)
==2732905== by 0x4004AB3: _dl_init (dl-init.c:121)
==2732905== by 0x401AA7F: ??? (in /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2)
==2732905== by 0x8: ???
==2732905== by 0x1FFF000212: ???
==2732905== by 0x1FFF000239: ???
==2732905== by 0x1FFF00023C: ???
==2732905== by 0x1FFF00024F: ???
==2732905== by 0x1FFF000252: ???
==2732905== by 0x1FFF000266: ???
==2732905== by 0x1FFF00026A: ???
==2732905== by 0x1FFF000274: ???
==2732905== by 0x1FFF000277: ???
==2732905==
==2732905== LEAK SUMMARY:
==2732905== definitely lost: 0 bytes in 0 blocks
==2732905== indirectly lost: 0 bytes in 0 blocks
==2732905== possibly lost: 0 bytes in 0 blocks
==2732905== still reachable: 192 bytes in 12 blocks
==2732905== suppressed: 0 bytes in 0 blocks
==2732905==
==2732905== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

View File

@@ -1,16 +0,0 @@
#!/bin/bash
export AQHOME_LOGLEVEL=info
export LD_LIBRARY_PATH="0-build/aqhome/:$LD_LIBRARY_PATH"
# 0-build/apps/aqhome-storage/aqhome-storage $*
0-build/apps/aqhome-storage/aqhome-storage \
--sourcefolder=apps/aqhome-storage/test/html \
--datafolder=apps/aqhome-storage/test/data \
-D apps/aqhome-storage/test/config \
--statefile=apps/aqhome-storage/test/config/state \
-ma 192.168.117.192 -mp 1883 --mqttclientid=AQHOMESTORAGETEST \
-ha 127.0.0.1 -hp 1884 \
-p ./aqhome-storage.pid \
"$@"

View File

@@ -41,6 +41,7 @@ static void _signalReadyFdObjects(AQH_OBJECT_LIST2 *ol);
void AQH_EventLoop_Run(AQH_EVENT_LOOP *eventLoop, int timeoutInMillisecs) void AQH_EventLoop_Run(AQH_EVENT_LOOP *eventLoop, int timeoutInMillisecs)
{ {
static int selectErrorCount=0;
fd_set fdRead; fd_set fdRead;
fd_set fdWrite; fd_set fdWrite;
int highestFd=-1; int highestFd=-1;
@@ -61,6 +62,8 @@ void AQH_EventLoop_Run(AQH_EVENT_LOOP *eventLoop, int timeoutInMillisecs)
rv=select(highestFd+1, &fdRead, &fdWrite, NULL, &tv); rv=select(highestFd+1, &fdRead, &fdWrite, NULL, &tv);
if (rv>0) { if (rv>0) {
/* some fds became active */ /* some fds became active */
if (selectErrorCount)
selectErrorCount--;
_markReadyFdObjects(eventLoop->fdObjectList, &fdRead, AQH_FDOBJECT_FDMODE_READ); _markReadyFdObjects(eventLoop->fdObjectList, &fdRead, AQH_FDOBJECT_FDMODE_READ);
_markReadyFdObjects(eventLoop->fdObjectList, &fdWrite, AQH_FDOBJECT_FDMODE_WRITE); _markReadyFdObjects(eventLoop->fdObjectList, &fdWrite, AQH_FDOBJECT_FDMODE_WRITE);
_signalReadyFdObjects(eventLoop->fdObjectList); _signalReadyFdObjects(eventLoop->fdObjectList);
@@ -68,11 +71,24 @@ void AQH_EventLoop_Run(AQH_EVENT_LOOP *eventLoop, int timeoutInMillisecs)
else if (rv<0) { else if (rv<0) {
if (errno!=EINTR) { if (errno!=EINTR) {
/* error */ /* error */
DBG_ERROR(AQH_LOGDOMAIN, "Error on SELECT: %d (%s)", errno, strerror(errno)); DBG_ERROR(AQH_LOGDOMAIN, "Error on SELECT: %d (%s)", errno, strerror(errno));
selectErrorCount++;
/* TODO: walk through all sockets to find the bad one */
if (selectErrorCount==90) {
/* increase log level */
DBG_ERROR(AQH_LOGDOMAIN, "Increasing log level to INFO");
GWEN_Logger_SetLevel(AQH_LOGDOMAIN, GWEN_LoggerLevel_Info);
}
else if (selectErrorCount==100) {
DBG_ERROR(AQH_LOGDOMAIN, "Aborting due to too many SELECT errors, will be restarted by systemd");
abort();
}
} }
} }
else { else {
/* no fd became active (TODO: maybe signal deep idle objects?) */ /* no fd became active (TODO: maybe signal deep idle objects?) */
if (selectErrorCount)
selectErrorCount--;
} }
} }
} }

View File

@@ -63,6 +63,7 @@
m_flashready.h m_flashready.h
m_flashresponse.h m_flashresponse.h
m_range.h m_range.h
m_time.h
</headers> </headers>
@@ -91,6 +92,7 @@
m_flashready.c m_flashready.c
m_flashresponse.c m_flashresponse.c
m_range.c m_range.c
m_time.c
</sources> </sources>

View File

@@ -20,11 +20,18 @@
#define AQH_MSG_OFFS_MEMSTATS_UID 0 /* 4 bytes */ #define AQH_MSG_OFFS_MEMSTATS_UID 0 /* 4 bytes */
#define AQH_MSG_OFFS_MEMSTATS_SECONDS 4 /* 4 bytes */ #define AQH_MSG_OFFS_MEMSTATS_UPHOURS 4 /* 1 byte */
#define AQH_MSG_OFFS_MEMSTATS_UPMINS 5 /* 1 byte */
#define AQH_MSG_OFFS_MEMSTATS_UPSECS 6 /* 1 byte */
#define AQH_MSG_OFFS_MEMSTATS_UPTICKS 7 /* 1 byte */
#define AQH_MSG_OFFS_MEMSTATS_STACKUSAGE 8 /* 2 bytes */ #define AQH_MSG_OFFS_MEMSTATS_STACKUSAGE 8 /* 2 bytes */
#define AQH_MSG_OFFS_MEMSTATS_BUFFERSUSED 10 /* 1 byte */ #define AQH_MSG_OFFS_MEMSTATS_BUFFERSUSED 10 /* 1 byte */
#define AQH_MSG_OFFS_MEMSTATS_MAXBUFFERSUSED 11 /* 1 byte */ #define AQH_MSG_OFFS_MEMSTATS_MAXBUFFERSUSED 11 /* 1 byte */
#define AQH_MSG_OFFS_MEMSTATS_RECVNOBUFFER 12 /* 2 bytes */ #define AQH_MSG_OFFS_MEMSTATS_RECVNOBUFFER 12 /* 2 bytes */
#define AQH_MSG_OFFS_MEMSTATS_HEAPUSED 14 /* 2 bytes */
#define AQH_MSG_OFFS_MEMSTATS_HEAPFREE 16 /* 2 bytes */
#define AQH_MSG_OFFS_MEMSTATS_XRAMSIZE 18 /* 2 bytes */
@@ -35,12 +42,34 @@ uint32_t AQH_MemStatsMessage_GetUid(const AQH_MESSAGE *msg)
uint32_t AQH_MemStatsMessage_GetSeconds(const AQH_MESSAGE *msg) uint8_t AQH_MemStatsMessage_GetUptimeHours(const AQH_MESSAGE *msg)
{ {
return AQH_Message_ReadUint32At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_MEMSTATS_SECONDS, 0); return AQH_Message_ReadUint8At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_MEMSTATS_UPHOURS, 0);
} }
uint8_t AQH_MemStatsMessage_GetUptimeMinutes(const AQH_MESSAGE *msg)
{
return AQH_Message_ReadUint8At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_MEMSTATS_UPMINS, 0);
}
uint8_t AQH_MemStatsMessage_GetUptimeSeconds(const AQH_MESSAGE *msg)
{
return AQH_Message_ReadUint8At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_MEMSTATS_UPSECS, 0);
}
uint8_t AQH_MemStatsMessage_GetUptimeTicks(const AQH_MESSAGE *msg)
{
return AQH_Message_ReadUint8At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_MEMSTATS_UPTICKS, 0);
}
uint16_t AQH_MemStatsMessage_GetStackUsage(const AQH_MESSAGE *msg) uint16_t AQH_MemStatsMessage_GetStackUsage(const AQH_MESSAGE *msg)
{ {
return AQH_Message_ReadUint16At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_MEMSTATS_STACKUSAGE, 0); return AQH_Message_ReadUint16At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_MEMSTATS_STACKUSAGE, 0);
@@ -69,20 +98,49 @@ uint16_t AQH_MemStatsMessage_GetRecvNoBufferErrors(const AQH_MESSAGE *msg)
uint16_t AQH_MemStatsMessage_GetHeapUsed(const AQH_MESSAGE *msg)
{
return AQH_Message_ReadUint16At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_MEMSTATS_HEAPUSED, 0);
}
uint16_t AQH_MemStatsMessage_GetHeapFree(const AQH_MESSAGE *msg)
{
return AQH_Message_ReadUint16At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_MEMSTATS_HEAPFREE, 0);
}
uint16_t AQH_MemStatsMessage_GetXramSize(const AQH_MESSAGE *msg)
{
return AQH_Message_ReadUint16At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_MEMSTATS_XRAMSIZE, 0);
}
void AQH_MemStatsMessage_DumpToBuffer(const AQH_MESSAGE *msg, GWEN_BUFFER *dbuf, const char *sText) void AQH_MemStatsMessage_DumpToBuffer(const AQH_MESSAGE *msg, GWEN_BUFFER *dbuf, const char *sText)
{ {
GWEN_Buffer_AppendArgs(dbuf, GWEN_Buffer_AppendArgs(dbuf,
"0x%02x->0x%02x: MEMSTATS(%s) %s (uid=0x%08x, uptime=%d, stack used=%d, buffers used=%d(max=%d), no recvbuf=%d)\n", "0x%02x->0x%02x: MEMSTATS(%s) %s (uid=0x%08x, up=%dh:%dm:%ds:%dt, "
"stack used=%d, buffers used=%d(max=%d), no recvbuf=%d, "
"heap used=%d, heap free=%d, xram=%d)\n",
AQH_NodeMessage_GetSourceAddress(msg), AQH_NodeMessage_GetSourceAddress(msg),
AQH_NodeMessage_GetDestAddress(msg), AQH_NodeMessage_GetDestAddress(msg),
AQH_NodeMessage_MsgTypeToChar(AQH_NodeMessage_GetMsgType(msg)), AQH_NodeMessage_MsgTypeToChar(AQH_NodeMessage_GetMsgType(msg)),
sText, sText,
(unsigned int) AQH_MemStatsMessage_GetUid(msg), (unsigned int) AQH_MemStatsMessage_GetUid(msg),
AQH_MemStatsMessage_GetSeconds(msg), AQH_MemStatsMessage_GetUptimeHours(msg),
AQH_MemStatsMessage_GetUptimeMinutes(msg),
AQH_MemStatsMessage_GetUptimeSeconds(msg),
AQH_MemStatsMessage_GetUptimeTicks(msg),
AQH_MemStatsMessage_GetStackUsage(msg), AQH_MemStatsMessage_GetStackUsage(msg),
AQH_MemStatsMessage_GetBuffersUsed(msg), AQH_MemStatsMessage_GetBuffersUsed(msg),
AQH_MemStatsMessage_GetMaxBuffersUsed(msg), AQH_MemStatsMessage_GetMaxBuffersUsed(msg),
AQH_MemStatsMessage_GetRecvNoBufferErrors(msg)); AQH_MemStatsMessage_GetRecvNoBufferErrors(msg),
AQH_MemStatsMessage_GetHeapUsed(msg),
AQH_MemStatsMessage_GetHeapFree(msg),
AQH_MemStatsMessage_GetXramSize(msg));
} }

View File

@@ -17,11 +17,17 @@
AQHOME_API uint32_t AQH_MemStatsMessage_GetUid(const AQH_MESSAGE *msg); AQHOME_API uint32_t AQH_MemStatsMessage_GetUid(const AQH_MESSAGE *msg);
AQHOME_API uint32_t AQH_MemStatsMessage_GetSeconds(const AQH_MESSAGE *msg); AQHOME_API uint8_t AQH_MemStatsMessage_GetUptimeHours(const AQH_MESSAGE *msg);
AQHOME_API uint8_t AQH_MemStatsMessage_GetUptimeMinutes(const AQH_MESSAGE *msg);
AQHOME_API uint8_t AQH_MemStatsMessage_GetUptimeSeconds(const AQH_MESSAGE *msg);
AQHOME_API uint8_t AQH_MemStatsMessage_GetUptimeTicks(const AQH_MESSAGE *msg);
AQHOME_API uint16_t AQH_MemStatsMessage_GetStackUsage(const AQH_MESSAGE *msg); AQHOME_API uint16_t AQH_MemStatsMessage_GetStackUsage(const AQH_MESSAGE *msg);
AQHOME_API uint8_t AQH_MemStatsMessage_GetBuffersUsed(const AQH_MESSAGE *msg); AQHOME_API uint8_t AQH_MemStatsMessage_GetBuffersUsed(const AQH_MESSAGE *msg);
AQHOME_API uint8_t AQH_MemStatsMessage_GetMaxBuffersUsed(const AQH_MESSAGE *msg); AQHOME_API uint8_t AQH_MemStatsMessage_GetMaxBuffersUsed(const AQH_MESSAGE *msg);
AQHOME_API uint16_t AQH_MemStatsMessage_GetRecvNoBufferErrors(const AQH_MESSAGE *msg); AQHOME_API uint16_t AQH_MemStatsMessage_GetRecvNoBufferErrors(const AQH_MESSAGE *msg);
AQHOME_API uint16_t AQH_MemStatsMessage_GetHeapUsed(const AQH_MESSAGE *msg);
AQHOME_API uint16_t AQH_MemStatsMessage_GetHeapFree(const AQH_MESSAGE *msg);
AQHOME_API uint16_t AQH_MemStatsMessage_GetXramSize(const AQH_MESSAGE *msg);
AQHOME_API void AQH_MemStatsMessage_DumpToBuffer(const AQH_MESSAGE *msg, GWEN_BUFFER *dbuf, const char *sText); AQHOME_API void AQH_MemStatsMessage_DumpToBuffer(const AQH_MESSAGE *msg, GWEN_BUFFER *dbuf, const char *sText);

View File

@@ -230,6 +230,9 @@ const char *AQH_NodeMessage_MsgTypeToChar(uint8_t i)
case AQH_MSG_TYPE_VALUE_SET: return "ValueSet"; case AQH_MSG_TYPE_VALUE_SET: return "ValueSet";
case AQH_MSG_TYPE_VALUE_SET_ACK: return "ValueSetAck"; case AQH_MSG_TYPE_VALUE_SET_ACK: return "ValueSetAck";
case AQH_MSG_TYPE_VALUE_SET_NACK: return "ValueSetNack"; case AQH_MSG_TYPE_VALUE_SET_NACK: return "ValueSetNack";
case AQH_MSG_TYPE_TIME_ANNOUNCE: return "TimeAnnounce";
case AQH_MSG_TYPE_TIME_REQSET: return "TimeSetRequest";
case AQH_MSG_TYPE_TIME_RSPSET: return "TimeSetResponse";
default: return "(unknown)"; default: return "(unknown)";
} }
} }
@@ -268,6 +271,10 @@ void AQH_NodeMessage_DumpSpecificToBuffer(const AQH_MESSAGE *msg, GWEN_BUFFER *d
case AQH_MSG_TYPE_VALUE_SET_ACK: AQH_ValueMessage_DumpToBuffer(msg, dbuf, sText); break; case AQH_MSG_TYPE_VALUE_SET_ACK: AQH_ValueMessage_DumpToBuffer(msg, dbuf, sText); break;
case AQH_MSG_TYPE_VALUE_SET_NACK: AQH_ValueMessage_DumpToBuffer(msg, dbuf, sText); break; case AQH_MSG_TYPE_VALUE_SET_NACK: AQH_ValueMessage_DumpToBuffer(msg, dbuf, sText); break;
case AQH_MSG_TYPE_TIME_ANNOUNCE: AQH_TimeMessage_DumpToBuffer(msg, dbuf, sText); break;
case AQH_MSG_TYPE_TIME_REQSET: AQH_TimeMessage_DumpToBuffer(msg, dbuf, sText); break;
case AQH_MSG_TYPE_TIME_RSPSET: AQH_TimeMessage_DumpToBuffer(msg, dbuf, sText); break;
case AQH_MSG_TYPE_DEBUG: case AQH_MSG_TYPE_DEBUG:
case AQH_MSG_TYPE_TWIBUSMEMBER: case AQH_MSG_TYPE_TWIBUSMEMBER:
default: AQH_NodeMessage_DumpToBuffer(msg, dbuf, sText); break; default: AQH_NodeMessage_DumpToBuffer(msg, dbuf, sText); break;
@@ -299,6 +306,11 @@ uint32_t AQH_NodeMessage_GetMsgGroup(uint8_t msgType)
case AQH_MSG_TYPE_VALUE_SET_NACK: case AQH_MSG_TYPE_VALUE_SET_NACK:
return AQH_MSG_TYPEGROUP_VALUES; return AQH_MSG_TYPEGROUP_VALUES;
case AQH_MSG_TYPE_TIME_ANNOUNCE:
case AQH_MSG_TYPE_TIME_REQSET:
case AQH_MSG_TYPE_TIME_RSPSET:
return AQH_MSG_TYPEGROUP_TIME;
case AQH_MSG_TYPE_NEED_ADDRESS: case AQH_MSG_TYPE_NEED_ADDRESS:
case AQH_MSG_TYPE_HAVE_ADDRESS: case AQH_MSG_TYPE_HAVE_ADDRESS:
case AQH_MSG_TYPE_CLAIM_ADDRESS: case AQH_MSG_TYPE_CLAIM_ADDRESS:

View File

@@ -60,6 +60,10 @@
#define AQH_MSG_TYPE_VALUE_SET_ACK 102 #define AQH_MSG_TYPE_VALUE_SET_ACK 102
#define AQH_MSG_TYPE_VALUE_SET_NACK 103 #define AQH_MSG_TYPE_VALUE_SET_NACK 103
#define AQH_MSG_TYPE_TIME_ANNOUNCE 120
#define AQH_MSG_TYPE_TIME_REQSET 121
#define AQH_MSG_TYPE_TIME_RSPSET 122
/* internal msg types via NET interface */ /* internal msg types via NET interface */
#define AQH_MSG_TYPE_NET_SET_ACCEPTED_MSGGROUPS 200 #define AQH_MSG_TYPE_NET_SET_ACCEPTED_MSGGROUPS 200
@@ -70,6 +74,7 @@
#define AQH_MSG_TYPEGROUP_ADDRESS 0x00000004 #define AQH_MSG_TYPEGROUP_ADDRESS 0x00000004
#define AQH_MSG_TYPEGROUP_FLASH 0x00000008 #define AQH_MSG_TYPEGROUP_FLASH 0x00000008
#define AQH_MSG_TYPEGROUP_ADMIN 0x00000010 #define AQH_MSG_TYPEGROUP_ADMIN 0x00000010
#define AQH_MSG_TYPEGROUP_TIME 0x00000020
#define AQH_MSG_TYPEGROUP_ALL 0xffffffff #define AQH_MSG_TYPEGROUP_ALL 0xffffffff

177
aqhome/msg/node/m_time.c Normal file
View File

@@ -0,0 +1,177 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2026 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "aqhome/aqhome.h"
#include "aqhome/msg/node/m_time.h"
#include "aqhome/msg/node/m_node.h"
#include <gwenhywfar/debug.h>
#include <gwenhywfar/endianfns.h>
#define AQH_MSG_OFFS_TIME_UID 0 /* 4 bytes */
#define AQH_MSG_OFFS_TIME_MSGID 4 /* 2 bytes */
#define AQH_MSG_OFFS_TIME_YEAR 6 /* 2 bytes */
#define AQH_MSG_OFFS_TIME_MONTH 8 /* 1 byte */
#define AQH_MSG_OFFS_TIME_DAYOFMONTH 9 /* 1 byte */
#define AQH_MSG_OFFS_TIME_DAYOFWEEK 10 /* 1 byte */
#define AQH_MSG_OFFS_TIME_HOUR 11 /* 1 byte */
#define AQH_MSG_OFFS_TIME_MINUTE 12 /* 1 byte */
#define AQH_MSG_OFFS_TIME_SECOND 13 /* 1 byte */
AQH_MESSAGE *AQH_TimeMessage_new(uint8_t destAddr, uint8_t srcAddr, uint8_t code, uint16_t msgId, const GWEN_TIMESTAMP *ts)
{
uint8_t payload[14];
uint8_t *ptr;
int i;
ptr=payload;
*(ptr++)=0; /* uid (empty) */
*(ptr++)=0;
*(ptr++)=0;
*(ptr++)=0;
*(ptr++)=msgId & 0xff; /* msgid */
*(ptr++)=(msgId>>8) & 0xff;
i=GWEN_Timestamp_GetYear(ts); /* year */
*(ptr++)=i & 0xff;
*(ptr++)=(i>>8) & 0xff;
*(ptr++)=GWEN_Timestamp_GetMonth(ts) & 0xff; /* month */
*(ptr++)=GWEN_Timestamp_GetDay(ts) & 0xff; /* day of month */
*(ptr++)=((GWEN_Timestamp_GetWeekDay(ts)+1) & 0xff); /* day of week */
*(ptr++)=GWEN_Timestamp_GetHour(ts) & 0xff; /* hour */
*(ptr++)=GWEN_Timestamp_GetMinute(ts) & 0xff; /* minute */
*(ptr++)=GWEN_Timestamp_GetSecond(ts) & 0xff; /* second */
return AQH_NodeMessage_new(destAddr, srcAddr, code, sizeof(payload), payload);
}
uint32_t AQH_TimeMessage_GetUid(const AQH_MESSAGE *msg)
{
return AQH_Message_ReadUint32At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_TIME_UID, 0);
}
uint16_t AQH_TimeMessage_GetMsgId(const AQH_MESSAGE *msg)
{
return AQH_Message_ReadUint16At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_TIME_MSGID, 0);
}
uint16_t AQH_TimeMessage_GetYear(const AQH_MESSAGE *msg)
{
return AQH_Message_ReadUint16At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_TIME_YEAR, 0);
}
uint8_t AQH_TimeMessage_GetMonth(const AQH_MESSAGE *msg)
{
return AQH_Message_ReadUint8At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_TIME_MONTH, 0);
}
uint8_t AQH_TimeMessage_GetDayOfMonth(const AQH_MESSAGE *msg)
{
return AQH_Message_ReadUint8At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_TIME_DAYOFMONTH, 0);
}
uint8_t AQH_TimeMessage_GetDayOfWeek(const AQH_MESSAGE *msg)
{
return AQH_Message_ReadUint8At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_TIME_DAYOFWEEK, 0);
}
uint8_t AQH_TimeMessage_GetHour(const AQH_MESSAGE *msg)
{
return AQH_Message_ReadUint8At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_TIME_HOUR, 0);
}
uint8_t AQH_TimeMessage_GetMinute(const AQH_MESSAGE *msg)
{
return AQH_Message_ReadUint8At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_TIME_MINUTE, 0);
}
uint8_t AQH_TimeMessage_GetSecond(const AQH_MESSAGE *msg)
{
return AQH_Message_ReadUint8At(msg, AQH_MSG_OFFS_ALL_DATA_BEGIN+AQH_MSG_OFFS_TIME_SECOND, 0);
}
GWEN_TIMESTAMP *AQH_TimeMessage_GetTime(const AQH_MESSAGE *msg)
{
return GWEN_Timestamp_new(AQH_TimeMessage_GetYear(msg),
AQH_TimeMessage_GetMonth(msg),
AQH_TimeMessage_GetDayOfMonth(msg),
AQH_TimeMessage_GetHour(msg),
AQH_TimeMessage_GetMinute(msg),
AQH_TimeMessage_GetSecond(msg));
}
void AQH_TimeMessage_DumpToBuffer(const AQH_MESSAGE *msg, GWEN_BUFFER *dbuf, const char *sText)
{
const char *sCmd;
switch(AQH_NodeMessage_GetMsgType(msg)) {
case AQH_MSG_TYPE_TIME_ANNOUNCE: sCmd="announce"; break;
case AQH_MSG_TYPE_TIME_REQSET: sCmd="set"; break;
case AQH_MSG_TYPE_TIME_RSPSET: sCmd="ack"; break;
default: sCmd="unknown"; break;
}
GWEN_Buffer_AppendArgs(dbuf,
"0x%02x->0x%02x: TIME(%s) %s (uid=0x%08x, msgId=%u, %04d/%02d/%02d-%02d:%02d:%02d (%d))\n",
AQH_NodeMessage_GetSourceAddress(msg),
AQH_NodeMessage_GetDestAddress(msg),
sCmd,
sText,
(unsigned int) AQH_TimeMessage_GetUid(msg),
(unsigned int)AQH_TimeMessage_GetMsgId(msg),
AQH_TimeMessage_GetYear(msg),
AQH_TimeMessage_GetMonth(msg),
AQH_TimeMessage_GetDayOfMonth(msg),
AQH_TimeMessage_GetHour(msg),
AQH_TimeMessage_GetMinute(msg),
AQH_TimeMessage_GetSecond(msg),
AQH_TimeMessage_GetDayOfWeek(msg));
}

34
aqhome/msg/node/m_time.h Normal file
View File

@@ -0,0 +1,34 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2026 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQH_M_TIME_H
#define AQH_M_TIME_H
#include <aqhome/api.h>
#include <aqhome/ipc2/message.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/timestamp.h>
AQHOME_API AQH_MESSAGE *AQH_TimeMessage_new(uint8_t destAddr, uint8_t srcAddr, uint8_t code, uint16_t msgId, const GWEN_TIMESTAMP *ts);
AQHOME_API uint32_t AQH_TimeMessage_GetUid(const AQH_MESSAGE *msg);
AQHOME_API uint16_t AQH_TimeMessage_GetMsgId(const AQH_MESSAGE *msg);
AQHOME_API uint16_t AQH_TimeMessage_GetYear(const AQH_MESSAGE *msg);
AQHOME_API uint8_t AQH_TimeMessage_GetMonth(const AQH_MESSAGE *msg);
AQHOME_API uint8_t AQH_TimeMessage_GetDayOfMonth(const AQH_MESSAGE *msg);
AQHOME_API uint8_t AQH_TimeMessage_GetDayOfWeek(const AQH_MESSAGE *msg);
AQHOME_API uint8_t AQH_TimeMessage_GetHour(const AQH_MESSAGE *msg);
AQHOME_API uint8_t AQH_TimeMessage_GetMinute(const AQH_MESSAGE *msg);
AQHOME_API uint8_t AQH_TimeMessage_GetSecond(const AQH_MESSAGE *msg);
AQHOME_API GWEN_TIMESTAMP *AQH_TimeMessage_GetTime(const AQH_MESSAGE *msg);
AQHOME_API void AQH_TimeMessage_DumpToBuffer(const AQH_MESSAGE *msg, GWEN_BUFFER *dbuf, const char *sText);
#endif

View File

@@ -1,13 +0,0 @@
#!/bin/bash
# AQHOME_LOGLEVEL=info LD_LIBRARY_PATH="../../aqhome/:$LD_LIBRARY_PATH" ./aqhomed -l aqhome.log -db aqhome.db -ma 127.0.0.1
export AQHOME_LOGLEVEL=info
export LD_LIBRARY_PATH="0-build/aqhome/:$LD_LIBRARY_PATH"
# aqhomed -l /var/log/aqhome.log -db /var/cache/aqhome/nodes.db -ma 192.168.117.192 -mp 1883 -t 127.0.0.1 -W /var/cache/aqhome
# 0-build/apps/aqhomed/aqhomed -l aqhome.log -db aqhome.db -ma 192.168.117.192 -mp 1883 -t 127.0.0.1 -p aqhomed.pid
# 0-build/apps/aqhomed/aqhomed -l aqhome.log -db aqhome.db -p aqhomed.pid -W /tmp/aqhome/aqhomed -ma 192.168.117.192 -mp 1883 -t 127.0.0.1 --mqttclientid=AQHOMEMQTTLOGTEST1
0-build/apps/aqhomed/aqhomed -l aqhome.log -db aqhome.db -p aqhomed.pid -t 127.0.0.1 "$@"

65
avr/README Normal file
View File

@@ -0,0 +1,65 @@
AqHome Node Operating System (AqOS4Avr)
=======================================
This is a very simple operating system for AVR nodes using ATtiny and ATmega MCUs.
It doesn't use administration objects in RAM, it just defines some routine calls which
have to be added into the system to add new drivers and modules.
Without any modules/drivers included the base system uses:
- <500 words of FLASH (<1KB)
- 6 bytes of SDRAM
With full AqHome network stack (including address assignment protocol):
- about 1700 words of FLASH
- <300 bytes SDRAM (including network buffers)
The following system routine types are defined:
Function I Description I required I add to file
-----------+-------------------------------------------------+----------+----------------------------------
INIT I init module/app I yes I modules_init.asm or apps_init.asm
RUN I called from the main loop I no I modules_run.asm or apps_run.asm
MSGRECVD I called when new messages arrive I no I modules_msg.asm or apps_msg.asm
EVERY100MS I called every 100 millisecs (system timer tick) I no I modules_100ms.asm or apps_100ms.asm
If the CLOCK module is enable the following routines are also available:
Function I Description I required I add to file
----------+------------------------------------------------+-----------+----------------------------------
EVERY1S I called every second I no I modules_1s.asm or apps_1s.asm
EVERY1M I called every minute I no I modules_1m.asm or apps_1m.asm
EVERY1H I called every hour I no I modules_1h.asm or apps_1h.asm
EVERY1D I called every day I no I modules_1d.asm or apps_1d.asm
Not all modules implement all those functions. Some modules only need to be called
once every main loop, others need to be called periodically every once in a while.
If a module implements a function a call to that routine must be inserted into the appropriate
file in "avr/devices/all/".
In order to make it easier for devices to optionally include modules/apps those calls need to be
between preprocessor directives like in:
----------------------------------X8
#ifdef MODULES_DS18B20
bigcall Ds18b20_OnEverySecond
#endif
----------------------------------X8
This way it is very easy to use a module (like that DS18B20 module) by using the
following line in your main code:
----------------------------------X8
#define MODULES_DS18B20
----------------------------------X8
This enables all the function calls for this module. Nothing else needs to be changed in your main
code file in order to enable that module.

View File

@@ -107,7 +107,11 @@ appDoorSendValue:
appDoorSendValue_send: appDoorSendValue_send:
ldi r17, VALUE_ID_TCRT1K ; VALUE ID ldi r17, VALUE_ID_TCRT1K ; VALUE ID
ldi r22, AQHOME_VALUETYPE_DOOR ; VALUE TYPE ldi r22, AQHOME_VALUETYPE_DOOR ; VALUE TYPE
rjmp Main_Send8BitValueReport rcall Main_Send8BitValueReport
#ifdef MODULES_LED_ACTIVITY
rcall LedActivity_Trigger ; (r16)
#endif
ret
; @end ; @end

View File

@@ -285,12 +285,12 @@ appForwarderLetSysHandleMsg_forMe:
pop xl pop xl
push xl push xl
push xh push xh
rcall mainModulesOnPacketReceived rcall modulesOnPacketReceived
pop xh pop xh
pop xl pop xl
push xl push xl
push xh push xh
rcall mainAppsOnPacketReceived rcall appsOnPacketReceived
pop xh pop xh
pop xl pop xl
appForwarderLetSysHandleMsg_end: appForwarderLetSysHandleMsg_end:

View File

@@ -1,7 +1,6 @@
App for Motion Activated Light App for Motion Activated Light
============================== ==============================
==============================
This app listens for VALUE_REPORT messages on the bus. If a preselected value from a preselected node This app listens for VALUE_REPORT messages on the bus. If a preselected value from a preselected node
is received (with a value !=0) the LED stripe attached to the node which runs this app will be turned on in is received (with a value !=0) the LED stripe attached to the node which runs this app will be turned on in
@@ -48,7 +47,7 @@ Example:
1.3. MALSOURCEB 1.3. MALSOURCEB
--------------------------- ---------------------------
Source for Birightness Messages: Source for Brightness Messages:
aqhome-tool setdata -N nodes/XXXXXXXX/MALSOURCEB -v VVNN aqhome-tool setdata -N nodes/XXXXXXXX/MALSOURCEB -v VVNN
VVNN is a 16-bit value, lower 8 bit contain the source node address, higher 8 bit contain the VVNN is a 16-bit value, lower 8 bit contain the source node address, higher 8 bit contain the

View File

@@ -16,8 +16,6 @@
appMotionLightDataBegin: appMotionLightDataBegin:
appMotionLightTimer: .byte 2
appMotionLightOnTime: .byte 2
appMotionLightSources: .byte APP_MOTIONLIGHT_SOURCE_NUM*APP_MOTIONLIGHT_SOURCE_SIZE appMotionLightSources: .byte APP_MOTIONLIGHT_SOURCE_NUM*APP_MOTIONLIGHT_SOURCE_SIZE
appMotionLightLSourceAddr: .byte 1 appMotionLightLSourceAddr: .byte 1
appMotionLightLSourceValueId: .byte 1 appMotionLightLSourceValueId: .byte 1

View File

@@ -27,11 +27,6 @@ AppMotionLight_Init:
ldi r17, (appMotionLightDataEnd-appMotionLightDataBegin) ldi r17, (appMotionLightDataEnd-appMotionLightDataBegin)
rcall Utils_FillSram rcall Utils_FillSram
ldi r16, LOW(APP_MOTIONLIGHT_DEFAULT_ONTIME)
sts appMotionLightOnTime, r16
ldi r16, HIGH(APP_MOTIONLIGHT_DEFAULT_ONTIME)
sts appMotionLightOnTime+1, r16
rcall appMotionLightReadConfFromEeprom rcall appMotionLightReadConfFromEeprom
ret ret
@@ -44,31 +39,7 @@ AppMotionLight_Init:
; ;
AppMotionLight_Fini: AppMotionLight_Fini:
clr r16
sts appMotionLightTimer, r16 ; clear timer
sts appMotionLightTimer+1, r16
rjmp appMotionLightTurnOff
; @end
; ---------------------------------------------------------------------------
; @routine AppMotionLight_Every100ms @global
;
; @clobbers r16, r24, r26, (r17, r18, r19, r20, r21, r23)
AppMotionLight_Every100ms:
lds r24, appMotionLightTimer
lds r25, appMotionLightTimer+1
sbiw r25:r24, 1
brcs AppMotionLight_Every100ms_ret
sts appMotionLightTimer, r24
sts appMotionLightTimer+1, r25
breq AppMotionLight_Every100ms_off
AppMotionLight_Every100ms_ret:
ret ret
AppMotionLight_Every100ms_off:
rjmp appMotionLightTurnOff ; (r16, r17, r18, r19, r20, r21, r22, r23, r24, r25, X)
; @end ; @end
@@ -129,71 +100,6 @@ appMotionLightIsBrightnessSource_ret:
; ---------------------------------------------------------------------------
; @routine appMotionLightStartTimer
;
; @clobbers r16
appMotionLightStartTimer:
lds r16, appMotionLightOnTime
sts appMotionLightTimer, r16
lds r16, appMotionLightOnTime+1
sts appMotionLightTimer+1, r16
ret
; @end
; ---------------------------------------------------------------------------
; @routine appMotionLightTurnOn
;
; @clobbers r18 (r16, r17, r19, r20, r21, r22, r23, r24, r25, X)
appMotionLightTurnOn:
ldi r18, 1
rcall SK6812_SetState ; (R16, R17, R18, R19, R20, R21, R23, R24, R25)
ldi r18, 1
rjmp appMotionLightReportState ; (R16, R17, R18, R19, R20, R21, R22, R23, R24, R25, X)
; @end
; ---------------------------------------------------------------------------
; @routine appMotionLightTurnOff
;
; @clobbers r18 (r16, r17, r19, r20, r21, r22, r23, r24, r25, X)
appMotionLightTurnOff:
clr r18
rcall SK6812_SetState ; (R16, R17, R18, R19, R20, R21, R23, R24, R25)
clr r18
rjmp appMotionLightReportState ; (R16, R17, R18, R19, R20, R21, R22, R23, R24, R25, X)
; @end
; ---------------------------------------------------------------------------
; @routine appMotionLightReportState
;
; @param R18 state (0=off, 1=on)
; @clobbers (R16, R17, R18, R19, R20, R21, R22, R23, R24, R25, X)
appMotionLightReportState:
ldi r17, VALUE_ID_MAL_STATE
clr r19
ldi r20, 1
clr r21
ldi r22, AQHOME_VALUETYPE_ONOFF
push yl
push yh
rcall Main_SendValueReport ; (R16, R17, R18, R19, R20, R21, R23, R24, R25, X)
pop yh
pop yl
ret
; @end
; --------------------------------------------------------------------------- ; ---------------------------------------------------------------------------
; @routine appMotionLightReadConfFromEeprom ; @routine appMotionLightReadConfFromEeprom
; ;
@@ -212,8 +118,7 @@ appMotionLightReadConfFromEeprom:
and r16, r18 and r16, r18
cpi r16, 0xff cpi r16, 0xff
breq appMotionLightReadConfFromEeprom_end breq appMotionLightReadConfFromEeprom_end
sts appMotionLightOnTime, r18 bigcall SK6812_SetAutoTimerReload ; (none)
sts appMotionLightOnTime+1, r19
; read source 1 ; read source 1
rcall Utils_ReadEepromIncr ; (R16) rcall Utils_ReadEepromIncr ; (R16)
@@ -265,10 +170,11 @@ appMotionLightWriteConfToEeprom:
ldi xl, LOW(EEPROM_OFFS_MAL_CONF_ONTIME) ldi xl, LOW(EEPROM_OFFS_MAL_CONF_ONTIME)
ldi xh, HIGH(EEPROM_OFFS_MAL_CONF_ONTIME) ldi xh, HIGH(EEPROM_OFFS_MAL_CONF_ONTIME)
lds r16, appMotionLightOnTime rcall SK6812_GetAutoTimerReload
mov r16, r18
rcall Eeprom_WriteByteIfChanged ; (R17) rcall Eeprom_WriteByteIfChanged ; (R17)
adiw xh:xl, 1 adiw xh:xl, 1
lds r16, appMotionLightOnTime+1 mov r16, r19
rcall Eeprom_WriteByteIfChanged ; (R17) rcall Eeprom_WriteByteIfChanged ; (R17)
adiw xh:xl, 1 adiw xh:xl, 1

View File

@@ -21,116 +21,127 @@
; @clobbers any, -X ; @clobbers any, -X
AppMotionLight_OnPacketReceived: AppMotionLight_OnPacketReceived:
adiw xh:xl, 2 ; command ldi zl, LOW(appMotionLightMsgTable*2)
ld r16, X ldi zh, HIGH(appMotionLightMsgTable*2)
sbiw xh:xl, 2 rjmp Main_HandleValueMsg
cpi r16, NETMSG_CMD_VALUE_SET
breq AppMotionLight_OnPacketReceived_set
cpi r16, NETMSG_CMD_VALUE_REPORT
breq AppMotionLight_OnPacketReceived_report ; ---------------------------------------------------------------------------
clc ; unexpected msg ; @routine appMotionLightHandleReport
ret ;
; "report value" message
AppMotionLight_OnPacketReceived_report: appMotionLightHandleReport:
rcall NETMSG_ValueRead ; (none)
rcall appMotionLightIsBrightnessSource ; (R16) rcall appMotionLightIsBrightnessSource ; (R16)
brcs AppMotionLight_OnPacketReceived_isBrightness brcs appMotionLightHandleReport_brightness
; motion source?
mov r16, r18 mov r16, r18
or r16, r19 or r16, r19
breq AppMotionLight_OnPacketReceived_clcRet ; zero value, ignore breq appMotionLightHandleReport_ret ; zero value, ignore
rcall appMotionLightHasSource ; (r16, r24, Y) rcall appMotionLightHasSource ; (r16, r24, Y)
brcs AppMotionLight_OnPacketReceived_turnOn brcc appMotionLightHandleReport_ret
ret
AppMotionLight_OnPacketReceived_turnOn: ; motion detected, check for brightness source
lds r16, appMotionLightLSourceAddr ; do we have a source for brightness value? lds r16, appMotionLightLSourceAddr ; do we have a source for brightness value?
tst r16 tst r16
breq AppMotionLight_OnPacketReceived_checkTimer ; no, jmp and don't check brightness breq appMotionLightHandleReport_trigger ; no, jmp and don't check brightness
; we have a brightness source configured, do we have a value?
lds r16, appMotionLightFlags ; check last brightness reported lds r16, appMotionLightFlags ; check last brightness reported
andi r16, APP_MOTIONLIGHT_FLAGS_HAVELIGHT andi r16, APP_MOTIONLIGHT_FLAGS_HAVELIGHT
breq AppMotionLight_OnPacketReceived_secRet ; no brightness, yet breq appMotionLightHandleReport_ret ; no brightness, yet
AppMotionLight_OnPacketReceived_checkBrightness: appMotionLightHandleReport_checkBrightness:
lds r16, appMotionLightLSourceValue ; check last brightness reported lds r16, appMotionLightLSourceValue ; check last brightness reported
lds r17, appMotionLightLSourceValue+1 lds r17, appMotionLightLSourceValue+1
lds r24, appMotionLightLastBrightness ; r25:r24>r17:r16? lds r24, appMotionLightLastBrightness ; r25:r24>r17:r16?
lds r25, appMotionLightLastBrightness+1 ; appMotionLightLastBrightness>appMotionLightLSourceValue? lds r25, appMotionLightLastBrightness+1 ; appMotionLightLastBrightness>appMotionLightLSourceValue?
sub r16, r24 sub r16, r24
sbc r17, r25 sbc r17, r25
brcs AppMotionLight_OnPacketReceived_secRet ; yes, too bright to turn on brcs appMotionLightHandleReport_ret ; yes, too bright to turn on
AppMotionLight_OnPacketReceived_checkTimer: appMotionLightHandleReport_trigger:
lds r16, appMotionLightTimer ; time up? bigcall SK6812_Trigger
lds r17, appMotionLightTimer+1 rjmp appMotionLightHandleReport_ret
or r16, r17 ; received report is our brightness source
brne AppMotionLight_OnPacketReceived_startTimer ; no, just restart timer (light already on) appMotionLightHandleReport_brightness:
push xl
push xh
rcall appMotionLightTurnOn ; (r16, r17, r18, r19, r20, r21, r22, r23, r24, r25, X)
pop xh
pop xl
AppMotionLight_OnPacketReceived_startTimer:
rcall appMotionLightStartTimer
AppMotionLight_OnPacketReceived_secRet:
sec
ret
AppMotionLight_OnPacketReceived_isBrightness:
sts appMotionLightLastBrightness, r18 sts appMotionLightLastBrightness, r18
sts appMotionLightLastBrightness+1, r19 sts appMotionLightLastBrightness+1, r19
lds r16, appMotionLightFlags lds r16, appMotionLightFlags
ori r16, APP_MOTIONLIGHT_FLAGS_HAVELIGHT ori r16, APP_MOTIONLIGHT_FLAGS_HAVELIGHT
sts appMotionLightFlags, r16 sts appMotionLightFlags, r16
sec appMotionLightHandleReport_ret:
clc
ret ret
; "set value" message ; @end
AppMotionLight_OnPacketReceived_set:
rcall NETMSG_ValueRead ; (none)
cpi r17, VALUE_ID_MAL_ONTIME
breq AppMotionLight_OnPacketReceived_setOnTime ; ---------------------------------------------------------------------------
cpi r17, VALUE_ID_MAL_SOURCE1 ; @routine appMotionLightSetValueOnTime
breq AppMotionLight_OnPacketReceived_setSource1 ;
cpi r17, VALUE_ID_MAL_SOURCE2
breq AppMotionLight_OnPacketReceived_setSource2 appMotionLightSetValueOnTime:
cpi r17, VALUE_ID_MAL_BSOURCE rcall SK6812_SetAutoTimerReload
breq AppMotionLight_OnPacketReceived_setBSource rjmp appMotionLightSetValueReturn
cpi r17, VALUE_ID_MAL_BVALUE ; @end
breq AppMotionLight_OnPacketReceived_setBValue
AppMotionLight_OnPacketReceived_clcRet:
clc ; unexpected message ; ---------------------------------------------------------------------------
ret ; @routine appMotionLightSetValueSource1
AppMotionLight_OnPacketReceived_setBSource: ; setValue "nodes/XXXXXXXX/MALSOURCEB 0xVVNN" (VV=value id, NN=node addr) ;
sts appMotionLightLSourceAddr, r18
sts appMotionLightLSourceValueId, r19 appMotionLightSetValueSource1:
rjmp AppMotionLight_OnPacketReceived_sendAck
AppMotionLight_OnPacketReceived_setBValue: ; "setValue nodes/XXXXXXXX/MALVALUEB n" (n brightness value)
sts appMotionLightLSourceValue, r18
sts appMotionLightLSourceValue+1, r19
rjmp AppMotionLight_OnPacketReceived_sendAck
AppMotionLight_OnPacketReceived_setOnTime: ; "setValue nodes/XXXXXXXX/MALONTIME n" (n in 1/10 secs)
sts appMotionLightOnTime, r18
sts appMotionLightOnTime+1, r19
lds r16, appMotionLightTimer ; timer active?
lds r17, appMotionLightTimer+1
or r16, r17
breq AppMotionLight_OnPacketReceived_sendAck ; nope, just set it and leave
sts appMotionLightTimer, r18 ; restart timer with new value
sts appMotionLightTimer+1, r19
rjmp AppMotionLight_OnPacketReceived_sendAck
AppMotionLight_OnPacketReceived_setSource1: ; setValue "nodes/XXXXXXXX/MALSOURCE1 0xVVNN" (VV=value id, NN=node addr)
sts appMotionLightSources, r18 ; peerAddr sts appMotionLightSources, r18 ; peerAddr
sts appMotionLightSources+1, r19 ; valueId sts appMotionLightSources+1, r19 ; valueId
rjmp AppMotionLight_OnPacketReceived_sendAck rjmp appMotionLightSetValueReturn
AppMotionLight_OnPacketReceived_setSource2: ; setValue "nodes/XXXXXXXX/MALSOURCE2 0xVVNN" (VV=value id, NN=node addr) ; @end
; ---------------------------------------------------------------------------
; @routine appMotionLightSetValueSource2
;
appMotionLightSetValueSource2:
sts appMotionLightSources+APP_MOTIONLIGHT_SOURCE_SIZE, r18 ; peerAddr sts appMotionLightSources+APP_MOTIONLIGHT_SOURCE_SIZE, r18 ; peerAddr
sts appMotionLightSources+APP_MOTIONLIGHT_SOURCE_SIZE+1, r19 ; valueId sts appMotionLightSources+APP_MOTIONLIGHT_SOURCE_SIZE+1, r19 ; valueId
AppMotionLight_OnPacketReceived_sendAck: rjmp appMotionLightSetValueReturn
push xl ; @end
push xh
ldi r23, NETMSG_CMD_VALUE_SET_ACK
rcall Main_SendValueResponse ; (clobbers all, including Y)
rcall appMotionLightWriteConfToEeprom ; (r16, r17, X) ; ---------------------------------------------------------------------------
pop xh ; @routine appMotionLightSetValueBValue
pop xl ;
appMotionLightSetValueBValue:
sts appMotionLightSources, r18 ; peerAddr
sts appMotionLightSources+1, r19 ; valueId
rjmp appMotionLightSetValueReturn
; @end
; ---------------------------------------------------------------------------
; @routine appMotionLightSetValueReturn
;
; Common return code
appMotionLightSetValueReturn:
rcall appMotionLightWriteConfToEeprom
ldi r23, NETMSG_CMD_VALUE_SET_ACK
sec sec
ret ret
; @end ; @end
appMotionLightMsgTable:
.db NETMSG_CMD_VALUE_REPORT, 0, LOW(appMotionLightHandleReport), HIGH(appMotionLightHandleReport)
.db NETMSG_CMD_VALUE_SET, VALUE_ID_MAL_ONTIME, LOW(appMotionLightSetValueOnTime), HIGH(appMotionLightSetValueOnTime)
.db NETMSG_CMD_VALUE_SET, VALUE_ID_MAL_SOURCE1, LOW(appMotionLightSetValueSource1), HIGH(appMotionLightSetValueSource1)
.db NETMSG_CMD_VALUE_SET, VALUE_ID_MAL_SOURCE2, LOW(appMotionLightSetValueSource2), HIGH(appMotionLightSetValueSource2)
.db NETMSG_CMD_VALUE_SET, VALUE_ID_MAL_BVALUE, LOW(appMotionLightSetValueBValue), HIGH(appMotionLightSetValueBValue)
.db 0, 0, 0, 0

View File

@@ -100,7 +100,11 @@ appMotionSendValue:
ldi r17, VALUE_ID_MOTION ; VALUE ID ldi r17, VALUE_ID_MOTION ; VALUE ID
ldi r22, AQHOME_VALUETYPE_MOTION ; VALUE TYPE ldi r22, AQHOME_VALUETYPE_MOTION ; VALUE TYPE
rjmp Main_Send8BitValueReport rcall Main_Send8BitValueReport
#ifdef MODULES_LED_SIMPLE
rcall LedSimple_SignalActivity ; (R18, R19, R20)
#endif
ret
; @end ; @end

View File

@@ -162,6 +162,9 @@ AppNetwork_HandleMsg_handleRebootMsg:
rcall appNetworkHandleRebootRequest rcall appNetworkHandleRebootRequest
ret ret
AppNetwork_HandleMsg_handlePingMsg: AppNetwork_HandleMsg_handlePingMsg:
#ifdef MODULES_LED_SIMPLE
bigcall LedSimple_SignalId ; (R18, R19, R20)
#endif
rjmp appNetworkHandlePingRequest rjmp appNetworkHandlePingRequest
AppNetwork_HandleMsg_clcRet: AppNetwork_HandleMsg_clcRet:
clc clc
@@ -485,14 +488,9 @@ appNetworkResetState_setRangeEnd:
; @clobbers X ; @clobbers X
appNetworkGetAddressFromEeprom: appNetworkGetAddressFromEeprom:
push r15 ldi xl, LOW(EEPROM_OFFS_COMADDR)
in r15, SREG ldi xh, HIGH(EEPROM_OFFS_COMADDR)
cli rcall Eeprom_ReadByte ; r16=addr (none)
ldi xl, LOW(EEPROM_OFFS_COMADDR)
ldi xh, HIGH(EEPROM_OFFS_COMADDR)
bigcall Utils_ReadEepromIncr ; (R16)
out SREG, r15
pop r15
ret ret
; @end ; @end

View File

@@ -117,6 +117,16 @@ AppReportSensors_OnEverySecond_store:
breq AppReportSensors_OnEverySecond_sendBrightness breq AppReportSensors_OnEverySecond_sendBrightness
#endif #endif
#ifdef APPS_DOOR
cpi r16, 100
breq AppReportSensors_OnEverySecond_sendDoorValue
#endif
#ifdef APPS_MOTION
cpi r16, 108
breq AppReportSensors_OnEverySecond_sendMotionValue
#endif
ret ret
#ifdef MODULES_SI7021 #ifdef MODULES_SI7021
@@ -162,6 +172,16 @@ AppReportSensors_OnEverySecond_sendBrightness:
rjmp Brightness_Send rjmp Brightness_Send
#endif #endif
#ifdef APPS_DOOR
AppReportSensors_OnEverySecond_sendDoorValue:
rjmp appDoorSendValue
#endif
#ifdef APPS_MOTION
AppReportSensors_OnEverySecond_sendMotionValue:
rjmp appMotionSendValue
#endif
; @end ; @end

View File

@@ -32,6 +32,8 @@
tree_t.asm tree_t.asm
eeprom-r.asm eeprom-r.asm
eeprom-w.asm eeprom-w.asm
ressource.asm
itoa.asm
</extradist> </extradist>
</gwbuild> </gwbuild>

View File

@@ -25,24 +25,47 @@ DEBUG1:
DEBUG2: DEBUG2:
ldi r19, 50 ldi r19, 50
ldi r20, 1 ldi r20, 5
ldi r21, 1 ldi r21, 5
rcall blinkLed rcall blinkLed
rjmp DEBUG2 rjmp DEBUG2
DEBUG3:
ldi r19, 50
ldi r20, 18
ldi r21, 2
rcall blinkLed
rjmp DEBUG3
DEBUG4:
ldi r19, 50
ldi r20, 18
ldi r21, 2
rcall blinkLed
ldi r19, 50
ldi r20, 1
ldi r21, 9
rcall blinkLed
rjmp DEBUG4
; @param r19 loop count ; @param r19 loop count
; @param r20 on time ; @param r20 on time
; @param r21 off time ; @param r21 off time
; @clobbers (R16, R18, R22, R24, R25) ; @clobbers (R19, R22)
blinkLed: blinkLed:
sbi LED_SIMPLE_DDR, LED_SIMPLE_PINNUM ; out sbi LED_SIMPLE_DDR, LED_SIMPLE_PINNUM ; out
blinkLed_loop: blinkLed_loop:
cbi LED_SIMPLE_PORT, LED_SIMPLE_PINNUM ; on cbi LED_SIMPLE_PORT, LED_SIMPLE_PINNUM ; on
mov r22, r20 mov r22, r20
rcall waitForMultiple100ms ; (R252 rcall waitForMultiple100ms ; (R22)
sbi LED_SIMPLE_PORT, LED_SIMPLE_PINNUM ; off sbi LED_SIMPLE_PORT, LED_SIMPLE_PINNUM ; off
mov r22, r21 mov r22, r21
rcall waitForMultiple100ms ; (R22) rcall waitForMultiple100ms ; (R22)

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