1
0
mirror of https://github.com/binary-kitchen/doorlockd synced 2024-05-31 22:22:34 +02:00

Compare commits

...

214 Commits
v1.3 ... master

Author SHA1 Message Date
e50fe08267 WebApp: Api: "status" command without credentials
This allows the "status" command to be executed without credentials.

Signed-off-by: Thomas Schmid <tom@lfence.de>
2024-01-13 20:49:23 +01:00
ae989da661 Port doorstate to latest libgpiod python bindings
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2024-01-12 23:12:43 +01:00
96fc3ba973 Doorlock.py: make eperm_insults a list
choose_insult fails with a dict, so make it a list.

Signed-off-by: Thomas Schmid <tom@lfence.de>
2023-11-04 22:30:47 +01:00
1c403437b3 Authenticator: Add blacklist
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2022-12-08 21:14:03 +01:00
18264b0449 Makefile: use setup.py instead of pip
Abandons the strange 'dependency_links.txt'-bug.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2021-11-22 22:07:34 +01:00
b086696879 setup: specify requirements 2021-11-22 22:06:12 +01:00
5419d4587a setup.py: remove pending newline
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2021-11-22 22:05:30 +01:00
15ff2c0293 WebApp: remove duplicate function definition 2021-11-22 21:45:11 +01:00
ebb0ea0527 Config: reformat whitespace according to PEP8 2021-11-22 21:45:11 +01:00
94ee9de2ee Authenticator: reformat whitespace accoring to PEP8 2021-11-22 21:45:11 +01:00
aa5999203c WebApp: reformat whitespace according to PEP8 2021-11-22 21:45:11 +01:00
1f27ebafdb WebApp: remove unused import 2021-11-22 21:45:11 +01:00
b7000388bc Door: mark from_string as static method 2021-10-18 18:06:51 +02:00
ed239fb284 Remove socket.io dependency and use http server sent events instead
Signed-off-by: Thomas Schmid <tom@binary-kitchen.de>
2020-11-13 00:05:07 +01:00
f1c267dc94 doorstate: systemd: auto-restart failed doorstate
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2020-06-06 21:44:18 +02:00
8c0ba51e9b doorstate: publish state on startup
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2020-06-06 21:26:59 +02:00
58e93f4e15 systemd: add network-online target
Prevents crashes during bootup.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2020-01-09 01:12:08 +01:00
8b49c2f332 doorstate: automatically reconnect if mqtt broker fails
We had a bug: doorstate didn't reconnect to the broker if it temporarily
disappeared.

Fix this by using start_loop() (which starts a thread that observes the
broker and automatically reconnects) instead of the synchronous loop()
method.

Thomas Basler <github.thomas@familie-basler.net>
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-07-07 23:16:18 +02:00
71e55ec523 doorstate: reduce logging output
Remove unnecessary output

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-07-07 23:03:24 +02:00
c8503f46e8 VERSION: open 2.1 development cycle
Better late than never: open the 2.1 development cycle

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-06-23 23:41:29 +02:00
7500a16fc1 gitignore: remove gpio-wait
gpio-wait is not existing any longer.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-06-23 23:40:29 +02:00
07a0ac5307 doorstate: reimplement doorstate in python
Furthermore, add initial support for the alarm topic.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-06-23 23:17:31 +02:00
8bc75bc0f6 PKGBUILD: add missing dependency pyserial
Signed-off-by: Thomas Schmid <tom@binary-kitchen.de>
2019-06-19 00:26:51 +02:00
9828534eae Config: Move SYSCONFDIR and PREFIX to Config
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-06-15 22:42:01 +02:00
1e5f7c3ec4 Config: allow multiple config topics
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-06-15 22:42:01 +02:00
6fc923a371 doorstate: Use variable instead of constant value
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-06-15 22:42:01 +02:00
caab43f4d3 Introduce etc directory
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-06-15 22:41:59 +02:00
95b6550e49 Makefile: introduce more variables
Instead of repeating things.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-06-15 22:33:31 +02:00
a774262a3c pydoorlock: Config: Only use one config file
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-06-14 22:49:40 +02:00
bdfcf29075 move Config class to pydoorlock module
Signed-off-by: Thomas Schmid <tom@binary-kitchen.de>
2019-06-14 22:01:33 +02:00
263fc0c687 pydoorlock: use definitions in Protocol.py
We now share definitions. Use them.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-06-14 21:37:48 +02:00
e27f6d02da pydoorlock: autogenerate Protocol.py
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-06-14 21:37:47 +02:00
becb21bbbc Makefile: Add a proper clean rule
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-06-14 21:37:38 +02:00
6725555af6 avr: use a own header to define constants
We will soon share them with python

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-06-14 21:30:54 +02:00
b5154faecd Makefile: Add a proper package rule
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-06-14 21:30:53 +02:00
e69c314f97 pydoorlock: Doorlock: use run instead of Popen
Popen created an object that we need to wait for, run won't. Use a
pseudo-shell, and spawn the process in the background.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-06-14 00:49:53 +02:00
0511fac330 improve authentication log messages
Signed-off-by: Thomas Schmid <tom@binary-kitchen.de>
2019-06-14 00:29:03 +02:00
5645ba22c9 Release v2.0
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-02-22 20:15:22 +01:00
209ccb3ba3 doorlockd: update copyright
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-02-22 20:13:32 +01:00
f264fe803b doorlockd.cfg: move comment
It's at the wrong place...

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-02-22 20:11:36 +01:00
a942d2a71c certs: remove BKCA
We now have LE certificates.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2019-02-22 20:07:15 +01:00
eaf2537d83 Authenticator: automatically choose authentication backend
and test other backends, if one fails.

Written-by: Rudolf Mayerhofer <rm@eightyfive.net>
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
[ralf: also remove the authentication method from any other code path]
2018-12-08 01:29:51 +01:00
90ea795c1b configs: fix RUN_HOOKS
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-18 02:56:28 +02:00
af647c0ae4 scripts: remove wol of cashdesk
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 22:05:55 +02:00
11998b9f8b PKGBUILD: add python-pip as dependency
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 22:04:39 +02:00
b3715dc42f doorlockd.default.cfg: fall back to default settings
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:49:48 +02:00
95e578baf6 fix doorlockd config
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:48:13 +02:00
2ffa0e1b67 pydoorlock: install pydoorlock via pip
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:48:13 +02:00
bc23a8c122 Take version from VERSION
Preparation for pip

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:26:15 +02:00
4a0c096331 pydoorlock: Align API requests to latest changes
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:26:05 +02:00
f294617e7f pydoorlock: Add license headers
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:26:04 +02:00
4609fa52f5 pydoorlock: minor stylistic fixups
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:25:56 +02:00
92b791a835 pydoorlock: doorlockd: make callbacks running again
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:25:51 +02:00
9e94d858ec pydoorlock: Doorlock: remove redundant parentheses
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:25:44 +02:00
d6d90e9f70 doorlockd: move DoorHandler to Doorlock.py
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:25:40 +02:00
aa3369baa8 doorlockd: move all scripting stuff to DoorHandler
Use this chance to get rid of most global variables.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:25:37 +02:00
bb65e38640 doorlockd: make {static,templates}_folder local
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:25:35 +02:00
ae358d403c doorlockd: move sounds to DoorHandler
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:25:31 +02:00
00f74d69f9 pydoorlock: Refactor LogicResponse to DoorlockResponse
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:25:23 +02:00
8d37886ee1 doorlockd: propagate cfg through Logic
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:25:19 +02:00
5b296b777d pydoorlock: WebApp: outsource WebApp
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:25:17 +02:00
2cdf9d1b27 pydoorlock: Condense locality of Flask stuff
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:25:13 +02:00
badeb1945b pydoorlock: Authenticator: Move all auth-related logic to Authenticator
By passing the configuration

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:25:10 +02:00
faecb6b98f pydoorlock: switch to new config parser
We want to use doorlockd for more than just serving webapps. Get rid of the
Flask config parser and use python's own config parser.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:25:06 +02:00
aaaad9b6ef pydoorlock: Extract WebApp pt. 1
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:25:02 +02:00
d1855bf77d pydoorlock: extract DoorState
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:24:54 +02:00
55205ad247 doorlockd: First steps towards pydoorlock module
Extract Authenticator platform from doorlockd.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-16 21:24:51 +02:00
7e26fa0cd6 doorlockd: remove superfluous code
No more users, drop it.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-08 00:24:36 +02:00
147c4cff0c Bump version number: 2.0-rc1
Release 2.0-rc1

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-07 03:48:15 +02:00
69cbf48bae Make sounds working again
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-07 03:47:34 +02:00
8b49549876 PKGBUILD: add more dependencies
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-07 02:56:30 +02:00
b9a1bfc566 Publish door state to mqtt
Assisted by nice gpio-wait helper.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-07 01:50:41 +02:00
197397027e update gitignore
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-06 22:33:38 +02:00
128cc2ced2 scripts: update mqtt topics
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-06 22:19:04 +02:00
82fd80b02d PKGBUILD: automatically deploy BKCA certificate
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-06 21:51:37 +02:00
305f89b70b Add TODO list
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-06 04:27:38 +02:00
b9dae15799 Bump version number: 2.0-rc1
Release 2.0-rc1

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-06 04:14:55 +02:00
be33bb71ce ssl: use system certificates
Rely on system certificates instead of pinning them.

On Arch Linux, copy the certificate over to
  /etc/ca-certificates/trust-source/anchors
and run
  trust extract-compat

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-06 04:14:34 +02:00
6a5987e7cb templates: Don't let google try to translate our page
Google tries to autotranslate the page. Avoid this by adding a specific
notranslate meta tag.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-06 04:14:34 +02:00
3b2544d892 avr: reverse bolzen logic
And set initial state

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-06 04:14:34 +02:00
8623d3a78a avr: Add fuse rule to Makefile
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-10-06 04:14:34 +02:00
6370e54ff8 Makefile: install doorlockd-passwd
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-18 01:28:34 +02:00
2aea44496f doorlockd: amend default config path for local credential storage
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-18 01:28:34 +02:00
f5603272d2 doorlockd: frontend: support local authentication
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-18 01:28:34 +02:00
45724f37c8 doorlockd: backend: support local authentication
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-18 01:28:34 +02:00
f6eabcd8cf Parameterise more settings
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-17 19:54:53 +02:00
0bf9679eef doorlockd: improve display content
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-13 01:14:22 +02:00
3dc71db095 doorlockd: templated: improve layout
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-10 01:32:47 +02:00
0b91b84180 doorlockd: templates: replace old QR Token
by a new one, and provide the Google playstore URL.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-10 01:03:34 +02:00
7f84e8e1a0 schematics: Add BOM and Reichelt link
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-10 01:03:32 +02:00
e8717c9e9f PKGBUILD: add python-ldap dependency
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-10 01:03:28 +02:00
1a331ac4fb schematics: board: minor fixups
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-10 01:03:25 +02:00
5d60f1fc45 schematics: add Raspberry Pi Hat EEPROM
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-10 01:03:21 +02:00
a20c0a43b5 schematics: board: minor fixes
Credits go to Noby for the review!

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-09 01:40:12 +02:00
b7ba8ebe18 doorlockd: templates: fix bug in meta tags
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-08 23:25:03 +02:00
2ee9457908 doorlockd: scripts: mqtt: send present status
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-08 23:15:42 +02:00
3df6515808 schematics: board: move RJ45 jack to the left
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-08 23:07:49 +02:00
e0fd93a700 schematics: add door state jumper
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-08 23:07:49 +02:00
ce3d4911ec schematics: board: rearrange LEDs
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-08 22:45:27 +02:00
c70bd29b61 schematics: decouple optocoupler
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-08 22:45:27 +02:00
9b47b260f3 schematics: Update Resistor values
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-08 21:06:11 +02:00
4c7886dc61 Add PKGBUILD for Arch linux
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-08 03:22:38 +02:00
03648bf821 Add Makefile
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-08 03:22:29 +02:00
f857fffac6 doorlockd: remove nasty flask-bootstrap dependency
Package is no longer officially maintainer, remove the dependency.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-08 02:55:14 +02:00
f226f44190 doorlockd: fix logic bug
We don't want to continue if we actually want to close.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-08 00:59:01 +02:00
f7725bba54 doorlockd: hooks: simplify hooks
and add present state

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-08 00:41:21 +02:00
4f95021fcf doorlockd: minor simplification
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-08 00:41:21 +02:00
d5215ea0fc schematics: improve schematics
- Use RJ45 instead for buttons/LEDs
  - Use SMD capacitor

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-08 00:20:06 +02:00
e3491aaa45 doorlockd: Flask: disable use_reloader
This spawns the process twice which duplicates the UART thread, which,
of course, leads to silly races.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-07 22:04:24 +02:00
d5a41a596d avr: choose our own programmer
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-07 22:02:41 +02:00
9f3062340c doorlockd, avr: Don't respond on commands
No need to read back the AVR.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-07 22:02:41 +02:00
95c35e7c06 Let scriptdir depend on PREFIX
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-05 22:31:37 +00:00
179180dc4e Improve systemd unit script
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-05 22:31:37 +00:00
60633bbc49 Flask: specify correct root_path
We need that for package installation.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-05 22:31:37 +00:00
e5fb445287 move sound specification from config to source
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-05 22:18:59 +00:00
c2c6e00651 Rename config.cfg -> doorlockd.cfg
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-05 20:22:16 +00:00
3e8d8a98e3 Rename doorlockd.py -> doorlockd
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-05 18:44:07 +00:00
31afb91f8d Add proper debugging mode
For production, only listen on localhost, in Debug mode,
allow anything from 0.0.0.0

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-05 18:44:07 +00:00
da08f768a1 Design a button case
Signed-off-by: Thomas Schmid <tom@binary-kitchen.de>
2018-09-04 01:21:41 +02:00
9567ca938f doorlockd.py: implement emergency unlock
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-03 23:05:46 +00:00
5fd19def77 avr: support emergency unlock
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-03 23:04:58 +00:00
698a96e46b avr: surpress compiler warning
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-03 22:15:38 +00:00
29d722453d avr: improve logic
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-03 22:13:41 +00:00
25595f18f2 avr: add accessors for emergency buttons
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-03 21:57:03 +00:00
0f6c837eb8 avr: remove superfluous function
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-03 21:29:27 +00:00
2307df2a0b Update gitignore
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-03 21:28:41 +00:00
174c498b22 Remove superfluous files
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-03 21:28:41 +00:00
8face4bccc Remove ISP adapter
Let's flash the device with the Raspberry Pi :-)
... But add an Jumper for the reset line.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-03 21:28:41 +00:00
4f034def73 avr: use linuxspi as programmer
Yes, that's pretty cool :-)

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-03 21:28:41 +00:00
23166516f1 Rework AlreadyLocked and AlreadyUnlocked
Better use the AlreadyActive semantics, which signalises that a state,
no matter which state in particular, is already active.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-03 21:28:41 +00:00
6977c46c5c Add the current state to the response
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-03 21:28:41 +00:00
20c86eb659 Factor out string to DoorState conversion, and support present command
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-03 21:28:41 +00:00
9a29f02449 Prepare doorlock for new states
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-03 21:28:41 +00:00
bd63a57d31 Cleanup AVR project, add new empty stubs
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-03 21:28:41 +00:00
af7b71304b First steps towards Raspberry Pi doorlock hat
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-02 06:03:06 +02:00
201cc2ef9b Split up simulation of LDAP and serial
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-09-02 06:03:06 +02:00
d52de425ca Remove deprecated API
We have a new app -- let's remove this.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-08-28 23:20:15 +02:00
e56622128e Provide addition 'open' field in JSON response
Better than parsing german words.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-08-28 23:16:40 +02:00
201411a65f doorlockd: Implement doorlock API
This will be used in the future Android App.

/api only allows POST requests. POST variables:
  - user
  - pass
  - command

Supported commands: "status", "lock", "unlock"

Only valid API requests (i.e., an API request that has all POST variables set)
will respons a valid JSON response. Otherwise, an html error will be thrown.

JSON response format:

err: Integer, Error code, as defined in LogicResponse
msg: Message
status: Current kitchen status. Only set in case of success.

The LogicResponse stati "Success", "AlreadyLocked" and "AlreadyUnlocked" are
considered successful.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-08-23 20:17:50 +02:00
Thomas Basler
8f0589cbf2 Added Apple specific meta tag
This meta tag leads to a real full screen app.
See https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html
2018-06-05 22:49:25 +02:00
Thomas Basler
8058fda326 Send lock/unlock messages using retain flag 2018-05-21 22:45:59 +02:00
Thomas Schmid
84d61c04db Change minimum length of username string from 4 to 3
because of some users with 3 character usenames

Signed-off-by: Thomas Schmid <thomas.schmid@oth-regensburg.de>
2018-04-12 10:45:02 +02:00
Thomas Schmid
6a018b7290 changed socketio async mode from None to 'threading'
in order to function correctly with python threads
2018-04-03 23:47:09 +02:00
0ae60848cf doorlockd.py: improve startup scripts
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-26 20:50:49 +00:00
04ef5219d3 doorlockd.py: improve front end conversion
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-23 22:52:36 +00:00
47adac2e0f display: increase font size
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-23 22:51:42 +00:00
71bc36a557 remove old code
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-23 22:22:50 +00:00
dc724b4abb doorlockd.py: add systemd service file
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-23 22:22:47 +00:00
b0f4e82914 doorlockd: display: add autorefresh interval
10 minutes sounds reasonable

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-23 22:13:32 +00:00
010563dab4 doorlockd.py: add doorlock sounds
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-23 22:01:02 +00:00
8f8db8950b Remove TODO entry
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-22 23:37:14 +00:00
f72f731b59 doorlockd.py: Add LDAP authentication support
Shamelessly copied from moep's DSS tool.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
Cc: Markus Hauschild <moepman@binary-kitchen.de>
2018-03-22 23:36:46 +00:00
d82272f53a config: don't run hooks
For the moment, don't run hooks

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-22 23:36:46 +00:00
c0e89c1be9 Add Binary Kitchen CA
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-22 23:36:46 +00:00
910ec8ba5b Add more TODOs
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-22 01:40:54 +01:00
4d1682d1bd doorlockd.py: Add config parameter for hooks
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-21 00:58:30 +01:00
6e2655e6e5 doorlockd.py: Add compatibility layer for old doorlockd-app
This commit will be reverted once everyone has updated their app.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-21 00:40:56 +01:00
82d09ddede static: add dummy token image
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-21 00:28:57 +01:00
96de37d27d doorlockd.py: remove Markup import statement
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-20 23:49:11 +01:00
153f066ef7 doorlockd.py: avoid encoding warnings
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-20 00:54:52 +01:00
be0ab6b58a doorlockd.py: Add schnapper support
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-20 00:18:46 +01:00
d17f5c964a correct path
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-19 18:54:18 +01:00
90528bb7d9 doorlockd.py: Add RS232 logic
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-19 18:47:04 +01:00
6961821c04 re-add scripts
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-19 18:42:34 +01:00
79599e2a45 doorlockd.py: Add lock/unlock scripts
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 22:04:14 +01:00
8cdf528032 update TODOs
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 18:36:05 +01:00
67879a659d doorlockd: add simulation mode
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 18:32:15 +01:00
bccd9432af Add TODOs 2018-03-18 18:32:11 +01:00
9cf149c12f doorlockd.py: add proper license header
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 18:32:11 +01:00
56ac823852 display: Add request_status timer
This will cyclically reset the status to Open/Close, independent of incoming
messages

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 18:21:32 +01:00
a6e117ffd9 doorlockd.py: move emit status to Logic class
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 17:48:06 +01:00
93a34b7755 display: improve java script
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 17:48:06 +01:00
78a0cc300f display: reindent
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 17:48:06 +01:00
1268e28a5d layout: improve design
All pages now user twitter bootstrap layout.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 17:22:39 +01:00
84e9440ed6 doorlockd.py: remove argparse dependency
by replacing its logic with flask's config parameter file.

Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 17:22:39 +01:00
3e8fce5b81 display: improve page layout
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 17:22:39 +01:00
5fe83e59f9 config: serve local bootstrap and jquery copies
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 17:22:39 +01:00
a21beefe74 doorlockd.py: switch to config file
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 17:22:39 +01:00
ae0d4f5aa2 socketio: deliver local static copy of scripts
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 17:22:39 +01:00
746b68eaf1 index: don't show labels for buttons
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 17:22:39 +01:00
1b59b273d8 index: refactor render_field to render_field_label
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 17:22:39 +01:00
ef198b74c0 index: reindent lines
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 17:22:39 +01:00
1c13e58f02 Clean up imports
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 15:49:35 +01:00
8bf57eaf54 display: allow only local connections
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 15:49:35 +01:00
eeafa6350f Reimplement doorlockd in python
Signed-off-by: Ralf Ramsauer <ralf@binary-kitchen.de>
2018-03-18 15:49:32 +01:00
Ralf Ramsauer
020ac1b38b scripts: simplify mqtt
Only publish door status topic, rest will be done by the opennhab server.

Username, password and mqtt's hostname are stored in user's local .config.

Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2018-01-06 22:29:30 +01:00
Ralf Ramsauer
9ae9304551 scripts: replace mac
we have a new cash-desk PC

Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2017-01-07 00:29:58 +01:00
Ralf Ramsauer
d04ba6ef63 doorlock-client: add mainwindow.showFullscreen() call
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-08-01 21:51:55 +02:00
Ralf Ramsauer
d1d0d5b160 Fix QT quirk
So far, we used QT interfaces in a wrong way. QT is not thread safe, so
use its signal/slot concept and move network communication stuff to a
separate QThread.

Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-31 15:48:19 +02:00
Ralf Ramsauer
4ccf8e4f24 doorlock-client: refactor run to app_run
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-21 20:57:31 +02:00
Ralf Ramsauer
48d0fcaf6f doorlock-client: be even more verbose
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-21 20:50:32 +02:00
Ralf Ramsauer
42f168ff19 doorlick-client: remove misleading comment line
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-21 20:40:45 +02:00
Ralf Ramsauer
ee481009e1 doorlock-client: Add more error output
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-21 20:40:32 +02:00
Ralf Ramsauer
951e1de987 Door: Redirect output to logfile
And use nohup for detached call.

Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-20 20:48:58 +02:00
Ralf Ramsauer
c058e1e4d0 door, logic: propagate new logfile to Door class
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-20 20:48:54 +02:00
Ralf Ramsauer
7e522e59af Door: Refactor system() to own function
Preparatory work for script logging

Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-20 20:17:00 +02:00
Ralf Ramsauer
573d9e7c65 Door: fix coding style
Consequently use _ for private member variables/functions.

Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-20 20:12:51 +02:00
Ralf Ramsauer
f713ef6124 Doorlockd: Replace logfile by logdir
In future, we might have several different logfiles.

Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-20 20:12:46 +02:00
Ralf Ramsauer
3721259f3e scripts: add nodered twitter sink
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-20 19:22:43 +02:00
Ralf Ramsauer
660a7297ee scripts: Wake up cashdesk PC
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-18 21:17:30 +02:00
Ralf Ramsauer
8503a2114f Mainwindow: Show huge token
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-17 23:00:08 +02:00
Ralf Ramsauer
62ac4f26e0 clientmessage: add token regex and getter
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-17 23:00:05 +02:00
Ralf Ramsauer
474d941918 clientmessage: Refactor token to web_address
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-17 22:57:54 +02:00
Ralf Ramsauer
5355749c6b UI: refactor tokenLabel to address_label
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-17 22:38:01 +02:00
Ralf Ramsauer
367afb4092 doorlockd: Improve error handling
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-17 22:29:31 +02:00
Ralf Ramsauer
34417b8ddb Cmake: Gcc: do not warn on unused results
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-07-17 22:18:17 +02:00
Ralf Ramsauer
f5c67445e9 startup scripts: use mosquitto
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-06-23 19:35:43 +02:00
Ralf Ramsauer
40d871b2d2 Version 1.4
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-04-24 22:16:18 +02:00
Ralf Ramsauer
99feb14036 Use aplay command for playing sounds
This basically reverts 5c8ca78c99.
Using libao + sndfile was a bad idea...

Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-04-24 22:08:21 +02:00
Ralf Ramsauer
88926613e7 Improve version nomenclature
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-04-24 21:32:56 +02:00
Andreas Augustin
b15e7960cb Fix boost include bug
Signed-off-by: Andreas Augustin <andy.augustin@t-online.de>
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-04-24 01:58:08 +02:00
Andreas Augustin
61c5ce5e5e Add debugging output to CMakeLists.txt
Signed-off-by: Andreas Augustin <andy.augustin@t-online.de>
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-04-24 01:58:08 +02:00
Andreas Augustin
9a38ace795 Improve .gitignore
Signed-off-by: Andreas Augustin <andy.augustin@t-online.de>
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-04-24 01:58:08 +02:00
Ralf Ramsauer
eb19aaa47f Adjust homer http port
Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
2016-04-24 01:54:46 +02:00
99 changed files with 11294 additions and 6396 deletions

12
.gitignore vendored
View File

@ -1 +1,11 @@
CMakeLists.txt.user
*.hex
*.elf
*.o
arch/pkg
arch/src
arch/doorlockd
arch/*.xz
arch/BKCA.crt
pydoorlock/Protocol.py
pydoorlock/__pycache__/

38
Makefile Normal file
View File

@ -0,0 +1,38 @@
DESTDIR ?= /
PREFIX ?= /usr
SYSCONFDIR ?= /etc
USR = $(DESTDIR)/$(PREFIX)
BIN = $(USR)/bin/
ETC = $(DESTDIR)/etc/
SHARE = $(USR)/share/
SYSTEMD_UNITS = $(ETC)/systemd/system/
all: pydoorlock/Protocol.py
package:
sed -i -r -e "s@(^SYSCONFDIR = ').*('$$)@\1$(SYSCONFDIR)\2@" pydoorlock/Config.py
sed -i -r -e "s@(^PREFIX = ').*('$$)@\1$(PREFIX)\2@" pydoorlock/Config.py
sed -i -r -e "s@(^__version__ = ').*('$$)@\1$(shell cat VERSION)\2@" doorlockd
pydoorlock/Protocol.py: avr-code/protocol.h
./scripts/gen_protocol.sh $^ > $@
install:
mkdir -p $(BIN)
mkdir -p $(SHARE)
mkdir -p $(SYSTEMD_UNITS)
mkdir -p $(ETC)
install doorlockd doorstate $(BIN)
install doorlockd-passwd $(BIN)
install -m 0644 etc/doorlockd.cfg $(ETC)
install -m 0644 systemd/doorlockd.service systemd/doorstate.service $(SYSTEMD_UNITS)
python setup.py install --root="$(DESTDIR)" --optimize=1
cp -av share/* $(SHARE)
clean:
rm -rf pydoorlock/__pycache__
rm -f pydoorlock/Protocol.py

3
TODO Normal file
View File

@ -0,0 +1,3 @@
- automatically setup doorlock user
- automatically deploy nginx
- unclutter stuff for X (hide cursor)

1
VERSION Normal file
View File

@ -0,0 +1 @@
v2.1

37
arch/PKGBUILD Normal file
View File

@ -0,0 +1,37 @@
# Author: Ralf Ramsauer <ralf@binary-kitchen.de>
pkgname=doorlockd
pkgver=2.1
pkgrel=1
pkgdesc="Binary Kitchen's doorlockd"
arch=('any')
url="https://github.com/Binary-Kitchen/${pkgname}"
license=(GPL)
depends=('python3'
'python-pyserial'
'python-ldap'
'python-pip'
'alsa-utils'
'libgpiod'
'mosquitto'
'mpg123'
'python-flask-wtf'
'python-paho-mqtt'
'chromium'
'xf86-video-fbdev'
'fluxbox'
'nginx'
'slim')
source=("git+https://github.com/Binary-Kitchen/${pkgname}.git#branch=next")
sha256sums=('SKIP')
build() {
cd "$srcdir/$pkgname/"
make
}
package() {
cd "$srcdir/$pkgname/"
make PREFIX=/usr sysconfdir=/etc package
make PREFIX=/usr sysconfdir=/etc DESTDIR="$pkgdir" install
}

View File

@ -1,608 +1,49 @@
# Hey Emacs, this is a -*- makefile -*-
#----------------------------------------------------------------------------
# WinAVR Makefile Template written by Eric B. Weddington, Jörg Wunsch, et al.
# Doorlock AVR - The AVR part of doorlockd
#
# Released to the Public Domain
# Copyright (c) Binary Kitchen e.V., 2018
#
# Additional material for this makefile was written by:
# Peter Fleury
# Tim Henigan
# Colin O'Flynn
# Reiner Patommel
# Markus Pfaff
# Sander Pool
# Frederik Rouleau
# Carlos Lamas
# Authors:
# Ralf Ramsauer <ralf@binary-kitchen.de>
#
#----------------------------------------------------------------------------
# On command line:
#
# make all = Make software.
#
# make clean = Clean out built project files.
#
# make coff = Convert ELF to AVR COFF.
#
# make extcoff = Convert ELF to AVR Extended COFF.
#
# make program = Download the hex file to the device, using avrdude.
# Please customize the avrdude settings below first!
#
# make debug = Start either simulavr or avarice as specified for debugging,
# with avr-gdb or avr-insight as the front end for debugging.
#
# make filename.s = Just compile filename.c into the assembler code only.
#
# make filename.i = Create a preprocessed source file for use in submitting
# bug reports to the GCC project.
#
# To rebuild project do "make clean" then "make all".
#----------------------------------------------------------------------------
# This work is licensed under the terms of the GNU GPL, version 2. See
# the COPYING file in the top-level directory.
# MCU name
MCU = attiny2313
TARGET = doorlock
MCU ?= attiny2313a
F_OSC ?= 4000000
UART_BAUD ?= 9600
AVRDUDE_MCU ?= t2313
# Processor frequency.
# This will define a symbol, F_CPU, in all source code files equal to the
# processor frequency. You can then use this symbol in your source code to
# calculate timings. Do NOT tack on a 'UL' at the end, this will be done
# automatically to create a 32-bit value in your source code.
# Typical values are:
# F_CPU = 1000000
# F_CPU = 1843200
# F_CPU = 2000000
# F_CPU = 3686400
# F_CPU = 4000000
# F_CPU = 7372800
# F_CPU = 8000000
# F_CPU = 11059200
# F_CPU = 14745600
# F_CPU = 16000000
# F_CPU = 18432000
# F_CPU = 20000000
PROGRAMMER=linuxspi
PORT=/dev/spidev0.0:/dev/gpiochip0:25
SPEED=125000
F_CPU = 14745600
#F_CPU = 8000000
OBJS = main.o uart.o
# Output format. (can be srec, ihex, binary)
FORMAT = ihex
# Target file name (without extension).
TARGET = main
# Object files directory
OBJDIR = obj
# List C source files here. (C dependencies are automatically generated.)
SRC = main.c uart.c
# List C++ source files here. (C dependencies are automatically generated.)
CPPSRC =
# List Assembler source files here.
# Make them always end in a capital .S. Files ending in a lowercase .s
# will not be considered source files but generated files (assembler
# output from the compiler), and will be deleted upon "make clean"!
# Even though the DOS/Win* filesystem matches both .s and .S the same,
# it will preserve the spelling of the filenames, and gcc itself does
# care about how the name is spelled on its command-line.
ASRC =
# Optimization level, can be [0, 1, 2, 3, s].
# 0 = turn off optimization. s = optimize for size.
# (Note: 3 is not always the best optimization level. See avr-libc FAQ.)
OPT = s
# Debugging format.
# Native formats for AVR-GCC's -g are dwarf-2 [default] or stabs.
# AVR Studio 4.10 requires dwarf-2.
# AVR [Extended] COFF format requires stabs, plus an avr-objcopy run.
DEBUG =
# List any extra directories to look for include files here.
# Each directory must be seperated by a space.
# Use forward slashes for directory separators.
# For a directory that has spaces, enclose it in quotes.
EXTRAINCDIRS =
# Compiler flag to set the C Standard level.
# c89 = "ANSI" C
# gnu89 = c89 plus GCC extensions
# c99 = ISO C99 standard (not yet fully implemented)
# gnu99 = c99 plus GCC extensions
CSTANDARD = -std=c11
# Place -D or -U options here for C sources
CDEFS = -DF_CPU=$(F_CPU)UL
# Place -D or -U options here for C++ sources
CPPDEFS = -DF_CPU=$(F_CPU)UL
#CPPDEFS += -D__STDC_LIMIT_MACROS
#CPPDEFS += -D__STDC_CONSTANT_MACROS
#---------------- Compiler Options C ----------------
# -g*: generate debugging information
# -O*: optimization level
# -f...: tuning, see GCC manual and avr-libc documentation
# -Wall...: warning level
# -Wa,...: tell GCC to pass this to the assembler.
# -adhlns...: create assembler listing
CFLAGS = -g$(DEBUG)
CFLAGS += $(CDEFS)
CFLAGS += -O$(OPT)
#CFLAGS += -mint8
#CFLAGS += -mshort-calls
CFLAGS += -funsigned-char
CFLAGS += -funsigned-bitfields
CFLAGS += -fpack-struct
CFLAGS += -fshort-enums
#CFLAGS += -fno-unit-at-a-time
CFLAGS += -Wall
CFLAGS += -Wstrict-prototypes
CFLAGS += -Wundef
#CFLAGS += -Wunreachable-code
#CFLAGS += -Wsign-compare
CFLAGS += -Wa,-adhlns=$(<:%.c=$(OBJDIR)/%.lst)
CFLAGS += $(patsubst %,-I%,$(EXTRAINCDIRS))
CFLAGS += $(CSTANDARD)
#---------------- Compiler Options C++ ----------------
# -g*: generate debugging information
# -O*: optimization level
# -f...: tuning, see GCC manual and avr-libc documentation
# -Wall...: warning level
# -Wa,...: tell GCC to pass this to the assembler.
# -adhlns...: create assembler listing
CPPFLAGS = -g$(DEBUG)
CPPFLAGS += $(CPPDEFS)
CPPFLAGS += -O$(OPT)
#CPPFLAGS += -mint8
#CPPFLAGS += -mshort-calls
CPPFLAGS += -funsigned-char
CPPFLAGS += -funsigned-bitfields
CPPFLAGS += -fpack-struct
CPPFLAGS += -fshort-enums
CPPFLAGS += -fno-exceptions
#CPPFLAGS += -fno-unit-at-a-time
CPPFLAGS += -Wall
#CPPFLAGS += -Wstrict-prototypes
CFLAGS += -Wundef
#CPPFLAGS += -Wunreachable-code
#CPPFLAGS += -Wsign-compare
CPPFLAGS += -Wa,-adhlns=$(<:%.cpp=$(OBJDIR)/%.lst)
CPPFLAGS += $(patsubst %,-I%,$(EXTRAINCDIRS))
#CPPFLAGS += $(CSTANDARD)
#---------------- Assembler Options ----------------
# -Wa,...: tell GCC to pass this to the assembler.
# -ahlms: create listing
# -gstabs: have the assembler create line number information; note that
# for use in COFF files, additional information about filenames
# and function names needs to be present in the assembler source
# files -- see avr-libc docs [FIXME: not yet described there]
ASFLAGS = -Wa,-adhlns=$(<:%.S=$(OBJDIR)/%.lst),-gstabs
#---------------- Library Options ----------------
# Minimalistic printf version
PRINTF_LIB_MIN = -Wl,-u,vfprintf -lprintf_min
# Floating point printf version (requires MATH_LIB = -lm below)
PRINTF_LIB_FLOAT = -Wl,-u,vfprintf -lprintf_flt
# If this is left blank, then it will use the Standard printf version.
PRINTF_LIB =
#PRINTF_LIB = $(PRINTF_LIB_MIN)
#PRINTF_LIB = $(PRINTF_LIB_FLOAT)
# Minimalistic scanf version
SCANF_LIB_MIN = -Wl,-u,vfscanf -lscanf_min
# Floating point + %[ scanf version (requires MATH_LIB = -lm below)
SCANF_LIB_FLOAT = -Wl,-u,vfscanf -lscanf_flt
# If this is left blank, then it will use the Standard scanf version.
SCANF_LIB =
#SCANF_LIB = $(SCANF_LIB_MIN)
#SCANF_LIB = $(SCANF_LIB_FLOAT)
MATH_LIB = -lm
#---------------- External Memory Options ----------------
# 64 KB of external RAM, starting after internal RAM (ATmega128!),
# used for variables (.data/.bss) and heap (malloc()).
#EXTMEMOPTS = -Wl,-Tdata=0x801100,--defsym=__heap_end=0x80ffff
# 64 KB of external RAM, starting after internal RAM (ATmega128!),
# only used for heap (malloc()).
#EXTMEMOPTS = -Wl,--defsym=__heap_start=0x801100,--defsym=__heap_end=0x80ffff
EXTMEMOPTS =
#---------------- Linker Options ----------------
# -Wl,...: tell GCC to pass this to linker.
# -Map: create map file
# --cref: add cross reference to map file
LDFLAGS = -Wl,-Map=$(TARGET).map,--cref
LDFLAGS += $(EXTMEMOPTS)
LDFLAGS += $(PRINTF_LIB) $(SCANF_LIB) $(MATH_LIB)
#LDFLAGS += -T linker_script.x
#---------------- Programming Options (avrdude) ----------------
# Programming hardware: alf avr910 avrisp bascom bsd
# dt006 pavr picoweb pony-stk200 sp12 stk200 stk500
#
# Type: avrdude -c ?
# to get a full listing.
#
AVRDUDE_PROGRAMMER = avr910
# com1 = serial port. Use lpt1 to connect to parallel port.
AVRDUDE_PORT = /dev/ttyUSB0 # programmer connected to serial device
AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex
#AVRDUDE_WRITE_EEPROM = -U eeprom:w:$(TARGET).eep
# Uncomment the following if you want avrdude's erase cycle counter.
# Note that this counter needs to be initialized first using -Yn,
# see avrdude manual.
#AVRDUDE_ERASE_COUNTER = -y
# Uncomment the following if you do /not/ wish a verification to be
# performed after programming the device.
#AVRDUDE_NO_VERIFY = -V
# Increase verbosity level. Please use this when submitting bug
# reports about avrdude. See <http://savannah.nongnu.org/projects/avrdude>
# to submit bug reports.
#AVRDUDE_VERBOSE = -v -v
AVRDUDE_FLAGS = -p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER)
AVRDUDE_FLAGS += $(AVRDUDE_NO_VERIFY)
AVRDUDE_FLAGS += $(AVRDUDE_VERBOSE)
AVRDUDE_FLAGS += $(AVRDUDE_ERASE_COUNTER)
#---------------- Debugging Options ----------------
# For simulavr only - target MCU frequency.
DEBUG_MFREQ = $(F_CPU)
# Set the DEBUG_UI to either gdb or insight.
# DEBUG_UI = gdb
DEBUG_UI = insight
# Set the debugging back-end to either avarice, simulavr.
DEBUG_BACKEND = avarice
#DEBUG_BACKEND = simulavr
# GDB Init Filename.
GDBINIT_FILE = __avr_gdbinit
# When using avarice settings for the JTAG
JTAG_DEV = /dev/com1
# Debugging port used to communicate between GDB / avarice / simulavr.
DEBUG_PORT = 4242
# Debugging host used to communicate between GDB / avarice / simulavr, normally
# just set to localhost unless doing some sort of crazy debugging when
# avarice is running on a different computer.
DEBUG_HOST = localhost
#============================================================================
# Define programs and commands.
SHELL = sh
CC = avr-gcc
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
SIZE = avr-size
NM = avr-nm
AVRDUDE = avrdude
REMOVE = rm -f
REMOVEDIR = rm -rf
COPY = cp
WINSHELL = cmd
CFLAGS := -g -O2 -mmcu=$(MCU)
CFLAGS += -Wall -Wextra -Wstrict-prototypes
CFLAGS += -DF_OSC=$(F_OSC) -DF_CPU=F_OSC -DUART_BAUD=$(UART_BAUD)UL -DMCU=$(MCU)
# Define Messages
# English
MSG_ERRORS_NONE = Errors: none
MSG_BEGIN = -------- begin --------
MSG_END = -------- end --------
MSG_SIZE_BEFORE = Size before:
MSG_SIZE_AFTER = Size after:
MSG_COFF = Converting to AVR COFF:
MSG_EXTENDED_COFF = Converting to AVR Extended COFF:
MSG_FLASH = Creating load file for Flash:
MSG_EEPROM = Creating load file for EEPROM:
MSG_EXTENDED_LISTING = Creating Extended Listing:
MSG_SYMBOL_TABLE = Creating Symbol Table:
MSG_LINKING = Linking:
MSG_COMPILING = Compiling C:
MSG_COMPILING_CPP = Compiling C++:
MSG_ASSEMBLING = Assembling:
MSG_CLEANING = Cleaning project:
MSG_CREATING_LIBRARY = Creating library:
all: $(TARGET).hex
$(TARGET).elf: $(OBJS)
$(CC) $(CFLAGS) -o $(TARGET).elf $^
$(TARGET).hex: $(TARGET).elf
$(OBJCOPY) -O ihex -R .eeprom $^ $@
program: $(TARGET).hex
$(AVRDUDE) -p $(AVRDUDE_MCU) -c $(PROGRAMMER) -P $(PORT) -U flash:w:$^
# Define all object files.
OBJ = $(SRC:%.c=$(OBJDIR)/%.o) $(CPPSRC:%.cpp=$(OBJDIR)/%.o) $(ASRC:%.S=$(OBJDIR)/%.o)
# Define all listing files.
LST = $(SRC:%.c=$(OBJDIR)/%.lst) $(CPPSRC:%.cpp=$(OBJDIR)/%.lst) $(ASRC:%.S=$(OBJDIR)/%.lst)
# Compiler flags to generate dependency files.
GENDEPFLAGS = -MD -MP -MF .dep/$(@F).d
# Combine all necessary flags and optional flags.
# Add target processor to flags.
ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS) $(GENDEPFLAGS)
ALL_CPPFLAGS = -mmcu=$(MCU) -I. -x c++ $(CPPFLAGS) $(GENDEPFLAGS)
ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS)
# Default target.
all: begin gccversion sizebefore build sizeafter end
# Change the build target to build a HEX file or a library.
build: elf hex eep lss sym
#build: lib
elf: $(TARGET).elf
hex: $(TARGET).hex
eep: $(TARGET).eep
lss: $(TARGET).lss
sym: $(TARGET).sym
LIBNAME=lib$(TARGET).a
lib: $(LIBNAME)
# Eye candy.
# AVR Studio 3.x does not check make's exit code but relies on
# the following magic strings to be generated by the compile job.
begin:
@echo
@echo $(MSG_BEGIN)
end:
@echo $(MSG_END)
@echo
# Display size of file.
HEXSIZE = $(SIZE) --target=$(FORMAT) $(TARGET).hex
ELFSIZE = $(SIZE) -A $(TARGET).elf
AVRMEM = avr-mem.sh $(TARGET).elf $(MCU)
sizebefore:
@if test -f $(TARGET).elf; then echo; echo $(MSG_SIZE_BEFORE); $(ELFSIZE); \
$(AVRMEM) 2>/dev/null; echo; fi
sizeafter:
@if test -f $(TARGET).elf; then echo; echo $(MSG_SIZE_AFTER); $(ELFSIZE); \
$(AVRMEM) 2>/dev/null; echo; fi
# Display compiler version information.
gccversion :
@$(CC) --version
# Program the device.
program: $(TARGET).hex $(TARGET).eep
$(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) $(AVRDUDE_WRITE_EEPROM)
# Generate avr-gdb config/init file which does the following:
# define the reset signal, load the target file, connect to target, and set
# a breakpoint at main().
gdb-config:
@$(REMOVE) $(GDBINIT_FILE)
@echo define reset >> $(GDBINIT_FILE)
@echo SIGNAL SIGHUP >> $(GDBINIT_FILE)
@echo end >> $(GDBINIT_FILE)
@echo file $(TARGET).elf >> $(GDBINIT_FILE)
@echo target remote $(DEBUG_HOST):$(DEBUG_PORT) >> $(GDBINIT_FILE)
ifeq ($(DEBUG_BACKEND),simulavr)
@echo load >> $(GDBINIT_FILE)
endif
@echo break main >> $(GDBINIT_FILE)
debug: gdb-config $(TARGET).elf
ifeq ($(DEBUG_BACKEND), avarice)
@echo Starting AVaRICE - Press enter when "waiting to connect" message displays.
@$(WINSHELL) /c start avarice --jtag $(JTAG_DEV) --erase --program --file \
$(TARGET).elf $(DEBUG_HOST):$(DEBUG_PORT)
@$(WINSHELL) /c pause
else
@$(WINSHELL) /c start simulavr --gdbserver --device $(MCU) --clock-freq \
$(DEBUG_MFREQ) --port $(DEBUG_PORT)
endif
@$(WINSHELL) /c start avr-$(DEBUG_UI) --command=$(GDBINIT_FILE)
# Convert ELF to COFF for use in debugging / simulating in AVR Studio or VMLAB.
COFFCONVERT = $(OBJCOPY) --debugging
COFFCONVERT += --change-section-address .data-0x800000
COFFCONVERT += --change-section-address .bss-0x800000
COFFCONVERT += --change-section-address .noinit-0x800000
COFFCONVERT += --change-section-address .eeprom-0x810000
coff: $(TARGET).elf
@echo
@echo $(MSG_COFF) $(TARGET).cof
$(COFFCONVERT) -O coff-avr $< $(TARGET).cof
extcoff: $(TARGET).elf
@echo
@echo $(MSG_EXTENDED_COFF) $(TARGET).cof
$(COFFCONVERT) -O coff-ext-avr $< $(TARGET).cof
# Create final output files (.hex, .eep) from ELF output file.
%.hex: %.elf
@echo
@echo $(MSG_FLASH) $@
$(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@
%.eep: %.elf
@echo
@echo $(MSG_EEPROM) $@
-$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \
--change-section-lma .eeprom=0 -O $(FORMAT) $< $@
# Create extended listing file from ELF output file.
%.lss: %.elf
@echo
@echo $(MSG_EXTENDED_LISTING) $@
$(OBJDUMP) -h -S $< > $@
# Create a symbol table from ELF output file.
%.sym: %.elf
@echo
@echo $(MSG_SYMBOL_TABLE) $@
$(NM) -n $< > $@
# Create library from object files.
.SECONDARY : $(TARGET).a
.PRECIOUS : $(OBJ)
%.a: $(OBJ)
@echo
@echo $(MSG_CREATING_LIBRARY) $@
$(AR) $@ $(OBJ)
# Link: create ELF output file from object files.
.SECONDARY : $(TARGET).elf
.PRECIOUS : $(OBJ)
%.elf: $(OBJ)
@echo
@echo $(MSG_LINKING) $@
$(CC) $(ALL_CFLAGS) $^ --output $@ $(LDFLAGS)
# Compile: create object files from C source files.
$(OBJDIR)/%.o : %.c
@echo
@echo $(MSG_COMPILING) $<
$(CC) -c $(ALL_CFLAGS) $< -o $@
# Compile: create object files from C++ source files.
$(OBJDIR)/%.o : %.cpp
@echo
@echo $(MSG_COMPILING_CPP) $<
$(CC) -c $(ALL_CPPFLAGS) $< -o $@
# Compile: create assembler files from C source files.
%.s : %.c
$(CC) -S $(ALL_CFLAGS) $< -o $@
# Compile: create assembler files from C++ source files.
%.s : %.cpp
$(CC) -S $(ALL_CPPFLAGS) $< -o $@
# Assemble: create object files from assembler source files.
$(OBJDIR)/%.o : %.S
@echo
@echo $(MSG_ASSEMBLING) $<
$(CC) -c $(ALL_ASFLAGS) $< -o $@
# Create preprocessed source for use in sending a bug report.
%.i : %.c
$(CC) -E -mmcu=$(MCU) -I. $(CFLAGS) $< -o $@
# Target: clean project.
clean: begin clean_list end
clean_list :
@echo
@echo $(MSG_CLEANING)
$(REMOVE) $(TARGET).hex
$(REMOVE) $(TARGET).eep
$(REMOVE) $(TARGET).cof
$(REMOVE) $(TARGET).elf
$(REMOVE) $(TARGET).map
$(REMOVE) $(TARGET).sym
$(REMOVE) $(TARGET).lss
$(REMOVEDIR) $(OBJDIR)
$(REMOVE) $(SRC:.c=.s)
$(REMOVE) $(SRC:.c=.d)
$(REMOVEDIR) .dep
# Create object files directory
$(shell mkdir $(OBJDIR) 2>/dev/null)
# Include the dependency files.
-include $(shell mkdir .dep 2>/dev/null) $(wildcard .dep/*)
# Listing of phony targets.
.PHONY : all begin finish end sizebefore sizeafter gccversion \
build elf hex eep lss sym coff extcoff \
clean clean_list program debug gdb-config
fuse:
$(AVRDUDE) -p $(AVRDUDE_MCU) -c $(PROGRAMMER) -P $(PORT) -b $(SPEED) -U lfuse:w:0xfd:m -U hfuse:w:0xdf:m -U efuse:w:0xff:m
clean:
rm -f $(OBJS)
rm -f $(TARGET).elf $(TARGET).hex

View File

@ -1,20 +0,0 @@
RS232 (female)
Innenschalter:
1 - GND <-> gelbgruen
2 - Schalter Notauf (active LOW) <-> Ader 1
3 - Schalter Notzu (active LOW) <-> Ader 2
Tuersystem:
1 - GND <-> Ader 1
2 - Notsperre <-> Ader 3
3 - Bolzen (12VDC) <-> gelbgruen
4 - Schnapper (12VAC) <-> Ader 2
Stromversorgung:
1 - Status
2 - +12VDC
3 - 12VAC
4 - +5VDC
5 - GND

View File

@ -1,106 +0,0 @@
#ifndef IO_H
#define IO_H
#include <stdbool.h>
#include <avr/io.h>
/*
* Macros
*/
#define PIN(x) (*(&x - 2)) // Address Of Data Direction Register Of Port x
#define DDR(x) (*(&x - 1)) // Address Of Input Register Of Port x
/*
* Outputs
*/
#define PORT_BOLZEN PORTB
#define PIN_BOLZEN PB0
#define PORT_SCHNAPPER PORTB
#define PIN_SCHNAPPER PB1
#define PORT_STATUS PORTB
#define PIN_STATUS PB2
/*
* Inputs
*/
#define PORT_BUTTON_LOCK PORTD
#define PIN_BUTTON_LOCK PD2
#define PORT_BUTTON_UNLOCK PORTD
#define PIN_BUTTON_UNLOCK PD3
#define PORT_EMERGENCY_UNLOCK PORTD
#define PIN_EMERGENCY_UNLOCK PD4
static inline bool is_emergency_unlock(void)
{
return !(PIN(PORT_EMERGENCY_UNLOCK) & (1<<PIN_EMERGENCY_UNLOCK));
}
static inline bool is_button_unlock(void)
{
return !(PIN(PORT_BUTTON_UNLOCK) & (1<<PIN_BUTTON_UNLOCK));
}
static inline bool is_button_lock(void)
{
return !(PIN(PORT_BUTTON_LOCK) & (1<<PIN_BUTTON_LOCK));
}
static inline void schnapper_off(void)
{
PORT_SCHNAPPER &= ~(1<<PIN_SCHNAPPER);
}
static inline void schnapper_on(void)
{
PORT_SCHNAPPER |= (1<<PIN_SCHNAPPER);
}
static inline void bolzen_off(void)
{
PORT_BOLZEN &= ~(1<<PIN_BOLZEN);
}
static inline void bolzen_on(void)
{
PORT_BOLZEN |= (1<<PIN_BOLZEN);
}
static inline void status_off(void)
{
PORT_STATUS &= ~(1<<PIN_STATUS);
}
static inline void status_on(void)
{
PORT_STATUS |= (1<<PIN_STATUS);
}
static inline void io_init(void)
{
// Set output directions
DDR(PORT_SCHNAPPER) |= (1<<PIN_SCHNAPPER);
schnapper_off();
DDR(PORT_SCHNAPPER) |= (1<<PIN_BOLZEN);
bolzen_off();
DDR(PORT_STATUS) |= (1<<PIN_STATUS);
status_off();
// Set input directions and activate pull-ups
DDR(PORT_BUTTON_UNLOCK) &= ~(1<<PIN_BUTTON_UNLOCK);
PORT_BUTTON_UNLOCK |= (1<<PIN_BUTTON_UNLOCK);
DDR(PORT_BUTTON_LOCK) &= ~(1<<PIN_BUTTON_LOCK);
PORT_BUTTON_LOCK |= (1<<PIN_BUTTON_LOCK);
DDR(PORT_EMERGENCY_UNLOCK) &= ~(1<<PIN_EMERGENCY_UNLOCK);
PORT_EMERGENCY_UNLOCK |= (1<<PIN_EMERGENCY_UNLOCK);
}
#endif

View File

@ -1,30 +1,53 @@
/* 2015, Ralf Ramsauer
* ralf@binary-kitchen.de
/*
* doorlock-avr, AVR code of Binary Kitchen's doorlock
*
* Copyright (c) Binary Kitchen, 2018
*
* Authors:
* Ralf Ramsauer <ralf@binary-kitchen.de>
*
* This work is licensed under the terms of the GNU GPL, version 2. See
* the COPYING file in the top-level directory.
*/
#include <stdbool.h>
#include "uart.h"
#include <ctype.h>
#include <stdbool.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "io.h"
#include "uart.h"
#include "protocol.h"
#include "../doorcmds.h"
#define RED 0x1
#define GREEN 0x2
#define YELLOW 0x4
static volatile enum {LOCKED, UNLOCKED} state = LOCKED;
static volatile bool schnapper = false;
#define SET_CONDITIONAL(predicate, port, pin) \
if ((predicate)) \
port |= (1 << pin); \
else \
port &= ~(1 << pin);
static inline void timer_init(void)
/* can either be red, green, or yellow */
static unsigned char state = RED;
enum state_source {
BUTTON,
COMM,
TIMEOUT,
EMERGENCY,
};
static inline void set_schnapper(bool state)
{
// Config the timer
// The 16bit Timer1 is used for resetting the lock state,
// if the UART stops receiving the unlock command
TIMSK |= (1<<TOIE1);
TIFR |= (1<<TOV1);
TCCR1A = 0;
TCCR1B = (1<<CS12);
SET_CONDITIONAL(state, PORTB, PB0);
}
static inline void set_bolzen(bool state)
{
SET_CONDITIONAL(state, PORTB, PB1);
}
static inline void reset_timeout(void)
@ -32,155 +55,176 @@ static inline void reset_timeout(void)
TCNT1 = 0;
}
static inline void extint_init(void)
static void set_leds(void)
{
// Configure external interrupts
// External interrupts are used for Button Unlock an Lock
MCUCR = (1<<ISC11)|(1<<ISC01);
GIMSK |= (1<<INT0)|(1<<INT1);
}
static unsigned int counter = 0;
bool pwm_cycle = ++counter % 20;
void uart_handler(const unsigned char c)
{
char retval = c;
switch ((char)c) {
case DOOR_CMD_UNLOCK:
state = UNLOCKED;
reset_timeout();
break;
case DOOR_CMD_LOCK:
state = LOCKED;
break;
case DOOR_CMD_PING:
break;
case DOOR_CMD_SCHNAPER:
if (state == UNLOCKED)
schnapper = true;
else
retval = '?';
break;
case DOOR_CMD_STATUS:
retval = (state == LOCKED) ? 'l' : 'u';
break;
default:
retval = '?';
break;
if (pwm_cycle) {
PORTD &= ~((1 << PD5) | (1 << PD6));
PORTB &= ~(1 << PB4);
}
uart_putc(retval);
switch (state) {
case RED:
PORTD |= (1 << PD5);
break;
case YELLOW:
PORTD |= (1 << PD6);
break;
case GREEN:
PORTB |= (1 << PB4);
break;
}
if (pwm_cycle)
return;
switch (state) {
case RED:
PORTD ^= (1 << PD6);
PORTB ^= (1 << PB4);
break;
case YELLOW:
PORTD ^= (1 << PD5);
PORTB ^= (1 << PB4);
break;
case GREEN:
PORTD ^= (1 << PD5);
PORTD ^= (1 << PD6);
break;
}
}
static void update_state(unsigned char new_state, enum state_source source)
{
char ret = 0;
reset_timeout();
if (new_state == state)
return;
state = new_state;
switch (state) {
case RED:
set_bolzen(true);
set_schnapper(false);
ret = AVR_STATE_SWITCH_RED;
break;
case YELLOW:
set_bolzen(false);
set_schnapper(false);
ret = AVR_STATE_SWITCH_YELLOW;
break;
case GREEN:
set_bolzen(false);
set_schnapper(true);
ret = AVR_STATE_SWITCH_GREEN;
break;
}
switch (source) {
case BUTTON:
uart_putc(toupper(ret));
break;
case EMERGENCY:
uart_putc(AVR_EMERGENCY);
break;
case TIMEOUT:
uart_putc(ret);
break;
case COMM:
default:
break;
}
}
ISR(USART_RX_vect)
{
unsigned char c = UDR;
switch (c) {
case AVR_STATE_SWITCH_RED:
update_state(RED, COMM);
break;
case AVR_STATE_SWITCH_YELLOW:
update_state(YELLOW, COMM);
break;
case AVR_STATE_SWITCH_GREEN:
update_state(GREEN, COMM);
break;
}
}
// Timeroverflow interrupts occurs each 1.137 seconds
// UART receive interrupts is used to prevent timer overflows
ISR(TIMER1_OVF_vect)
{
state = LOCKED;
reset_timeout();
update_state(RED, TIMEOUT);
}
// Button Lock
ISR(INT0_vect)
static inline void timer_init(void)
{
cli();
// This code is used to prevent spurious interrupts
_delay_ms(50);
if (!is_button_lock())
goto out;
uart_putc(DOOR_BUTTON_LOCK);
state = LOCKED;
out:
sei();
TIMSK |= (1 << TOIE1);
TIFR |= (1 << TOV1);
TCCR1A = 0;
TCCR1B = (1 << CS12);
}
// Button Unlock
ISR(INT1_vect)
static inline void setup_ports(void)
{
cli();
PORTB = 0;
DDRB = (1 << PB4) | (1 << PB1) | (1 << PB0);
// This code is used to prevent spurious interrupts
_delay_ms(50);
if (!is_button_unlock())
goto out;
PORTD = 0;
DDRD = (1 << PD5) | (1 << PD6);
}
uart_putc(DOOR_BUTTON_UNLOCK);
static inline bool is_emergency(void)
{
return !(PINB & (1 << PB3));
}
if (state == LOCKED) {
bolzen_off();
_delay_ms(3000);
}
static inline bool is_door_open(void)
{
return !(PINB & (1 << PB2));
}
out:
sei();
static unsigned char get_keys(void)
{
unsigned char ret = 0;
if (!(PIND & (1 << PD2))) ret |= RED;
if (!(PIND & (1 << PD3))) ret |= YELLOW;
if (!(PIND & (1 << PD4))) ret |= GREEN;
return ret;
}
int main(void)
{
// Disable all interrupts
cli();
// Init IO
io_init();
unsigned char i;
// Wait a bit to settle down
_delay_ms(1000);
// Init Uart
uart_init();
uart_set_recv_handler(uart_handler);
// Init Timer
setup_ports();
timer_init();
uart_init();
set_bolzen(true);
reset_timeout();
// Init external interrupts
extint_init();
// Enable all interrupts
sei();
for(;;) {
if (state == LOCKED) {
bolzen_on();
status_off();
schnapper = false;
// Check if someone used the emergency unlock
if (is_emergency_unlock()) {
// If so, wait 200ms and double check
_delay_ms(200);
if (is_emergency_unlock()) {
uart_putc(DOOR_EMERGENCY_UNLOCK);
cli();
bolzen_off();
schnapper_on();
_delay_ms(3000);
schnapper_off();
bolzen_on();
sei();
}
}
} else if (state == UNLOCKED) {
bolzen_off();
status_on();
if (schnapper == true) {
schnapper = false;
schnapper_on();
_delay_ms(2000);
schnapper_off();
}
for (;;) {
if (is_emergency()) {
update_state(GREEN, EMERGENCY);
} else {
i = get_keys();
if (i & GREEN)
update_state(GREEN, BUTTON);
else if (i & YELLOW)
update_state(YELLOW, BUTTON);
else if (i & RED)
update_state(RED, BUTTON);
}
set_leds();
}
return 0;
}

16
avr-code/protocol.h Normal file
View File

@ -0,0 +1,16 @@
/*
* doorlock-avr, AVR code of Binary Kitchen's doorlock
*
* Copyright (c) Binary Kitchen, 2019
*
* Authors:
* Ralf Ramsauer <ralf@binary-kitchen.de>
*
* This work is licensed under the terms of the GNU GPL, version 2. See
* the COPYING file in the top-level directory.
*/
#define AVR_STATE_SWITCH_RED 'r'
#define AVR_STATE_SWITCH_YELLOW 'y'
#define AVR_STATE_SWITCH_GREEN 'g'
#define AVR_EMERGENCY 'E'

View File

@ -1,27 +1,30 @@
/*
* doorlock-avr, AVR code of Binary Kitchen's doorlock
*
* Copyright (c) Binary Kitchen, 2018
*
* Authors:
* Ralf Ramsauer <ralf@binary-kitchen.de>
*
* This work is licensed under the terms of the GNU GPL, version 2. See
* the COPYING file in the top-level directory.
*/
#include <stddef.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include "uart.h"
#define UBRR_VAL ((F_CPU+BAUD*8)/(BAUD*16)-1)
#define BAUD_REAL (F_CPU/(16*(UBRR_VAL+1)))
#define BAUD_ERROR ((BAUD_REAL*1000)/BAUD)
#if ((BAUD_ERROR<990) || (BAUD_ERROR>1010))
#define UBRR_VAL ((F_OSC+UART_BAUD*8)/(UART_BAUD*16)-1)
#define BAUD_REAL (F_OSC/(16*(UBRR_VAL+1)))
#define BAUD_ERROR ((BAUD_REAL*1000)/UART_BAUD)
#if ((BAUD_ERROR<985) || (BAUD_ERROR>1010))
#warn BAUD_ERROR
#error Choose another crystal. Baud error too high.
#endif
static void (*recv_handler)(unsigned char c) = NULL;
ISR(USART_RX_vect)
{
cli();
if(recv_handler)
recv_handler(UDR);
sei();
}
void uart_init()
{
// Enable receive and transmit
@ -31,12 +34,7 @@ void uart_init()
UCSRC = (1<<UCSZ1)|(1<<UCSZ0);
UBRRH = UBRR_VAL >> 8;
UBRRL = UBRR_VAL & 0xFF;
}
void uart_set_recv_handler(void (*handler)(unsigned char c))
{
recv_handler = handler;
UBRRL = UBRR_VAL & 0xFF;
}
void uart_putc(const char c)

View File

@ -1,7 +1,14 @@
#ifndef UART_H
#define UART_H
#define BAUD 9600UL
/*
* doorlock-avr, AVR code of Binary Kitchen's doorlock
*
* Copyright (c) Binary Kitchen, 2018
*
* Authors:
* Ralf Ramsauer <ralf@binary-kitchen.de>
*
* This work is licensed under the terms of the GNU GPL, version 2. See
* the COPYING file in the top-level directory.
*/
void uart_set_recv_handler(void (*handler)(unsigned char c));
@ -9,5 +16,3 @@ void uart_init(void);
void uart_putc(const char c);
void uart_puts(const char* str);
#endif

BIN
cad/cable_guide.par Normal file

Binary file not shown.

BIN
cad/case.asm Normal file

Binary file not shown.

BIN
cad/case.cfg Normal file

Binary file not shown.

BIN
cad/case_bottom.par Normal file

Binary file not shown.

BIN
cad/case_bottom.stl Normal file

Binary file not shown.

BIN
cad/case_top.par Normal file

Binary file not shown.

BIN
cad/case_top.stl Normal file

Binary file not shown.

Binary file not shown.

BIN
cad/switch.par Normal file

Binary file not shown.

BIN
cad/switch_nut.par Normal file

Binary file not shown.

View File

@ -1,14 +0,0 @@
#ifndef DOORCMD_H
#define DOORCMD_H
#define DOOR_CMD_UNLOCK 'u'
#define DOOR_CMD_LOCK 'l'
#define DOOR_CMD_SCHNAPER 's'
#define DOOR_CMD_PING 'p'
#define DOOR_CMD_STATUS 'i'
#define DOOR_BUTTON_UNLOCK 'U'
#define DOOR_BUTTON_LOCK 'L'
#define DOOR_EMERGENCY_UNLOCK 'E'
#endif

78
doorlockd Executable file
View File

@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""
Doorlockd -- Binary Kitchen's smart door opener
Copyright (c) Binary Kitchen e.V., 2018-2019
Author:
Ralf Ramsauer <ralf@binary-kitchen.de>
This work is licensed under the terms of the GNU GPL, version 2. See
the LICENSE file in the top-level directory.
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.
"""
import logging
import sys
from configparser import ConfigParser
from os.path import abspath, join
from pydoorlock.Authenticator import Authenticator
from pydoorlock.WebApp import webapp_run, emit_doorstate
from pydoorlock.Doorlock import DoorlockResponse, DoorHandler
from pydoorlock.Config import Config, root_prefix, sounds_prefix
__author__ = 'Ralf Ramsauer'
__copyright = 'Copyright (c) Ralf Ramsauer, 2018-2019'
__license__ = 'GPLv2'
__email__ = 'ralf@binary-kitchen.de'
__status__ = 'Development'
__maintainer__ = 'Ralf Ramsauer'
__version__ = '0.0'
log_level = logging.DEBUG
date_fmt = '%Y-%m-%d %H:%M:%S'
log_fmt = '%(asctime)-15s %(levelname)-8s %(message)s'
log = logging.getLogger()
cfg = Config('doorlockd')
class Logic:
def __init__(self, cfg, sounds_prefix, scripts_prefix, callback):
self.auth = Authenticator(cfg)
self.door_handler = DoorHandler(cfg, sounds_prefix, scripts_prefix)
self.door_handler.register_callback(callback)
def request(self, state, credentials):
err = self.auth.try_auth(credentials)
if err != DoorlockResponse.Success:
return err
return self.door_handler.request(state)
@property
def state(self):
return self.door_handler.state
if __name__ == '__main__':
logging.basicConfig(level=log_level, stream=sys.stdout,
format=log_fmt, datefmt=date_fmt)
log.info('Starting doorlockd')
scripts_prefix = join(root_prefix, 'scripts')
logic = Logic(cfg, sounds_prefix, scripts_prefix, emit_doorstate)
static_folder = abspath(join(root_prefix, 'static'))
template_folder = abspath(join(root_prefix, 'templates'))
webapp_run(cfg, logic, __status__, __version__, template_folder,
static_folder)
sys.exit(0)

40
doorlockd-passwd Executable file
View File

@ -0,0 +1,40 @@
#!/usr/bin/env python3
"""
Doorlockd -- Binary Kitchen's smart door opener
Copyright (c) Binary Kitchen e.V., 2018
Author:
Ralf Ramsauer <ralf@binary-kitchen.de>
This work is licensed under the terms of the GNU GPL, version 2. See
the LICENSE file in the top-level directory.
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.
"""
import getpass
import hashlib
import uuid
import sys
if len(sys.argv) != 3:
print('Usage: %s db username' % sys.argv[0])
quit(-1)
username = sys.argv[2]
try:
password = getpass.getpass()
except Exception as error:
print('ERROR', error)
quit(-1)
salt = uuid.uuid4().hex
password = hashlib.sha256(salt.encode() + password.encode()).hexdigest() + ':' + salt
with open(sys.argv[1], 'a') as file:
file.write('%s %s\n' % (username, password))

View File

@ -1,124 +0,0 @@
cmake_minimum_required(VERSION 2.8)
project(doorlockd)
option(USE_COLORIZED_LOGS "Colorized logging" ON)
set(DOORLOCK_VERSION_MAJOR 1)
set(DOORLOCK_VERSION_MINOR 3)
set(DOORLOCK_VERSION_PATCH 0)
set(DOORLOCK_VERSION "${DOORLOCK_VERSION_MAJOR}.${DOORLOCK_VERSION_MINOR}-${DOORLOCK_VERSION_PATCH}")
# Instruct CMake to run moc automatically when needed.
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
MESSAGE(STATUS "doorlockd version: ${DOORLOCK_VERSION}")
# Get the current working branch
execute_process(
COMMAND git rev-parse --abbrev-ref HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_BRANCH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(GIT_BRANCH "\"${GIT_BRANCH}\"")
# Get the latest abbreviated commit hash of the working branch
execute_process(
COMMAND git log -1 --format=%h
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_COMMIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(GIT_COMMIT_HASH "\"${GIT_COMMIT_HASH}\"")
add_definitions(-std=c++11)
configure_file (
"${PROJECT_SOURCE_DIR}/config.h.in"
"${PROJECT_BINARY_DIR}/config.h"
)
include_directories(${PROJECT_BINARY_DIR})
include_directories(${Boost_INCLUDE_DIRS})
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -ggdb -Wall -pedantic -Weffc++ -Wextra")
set(CMAKE_CXX_FLAGS "-O2 -Wall -pedantic -Wextra -Weffc++")
set(CMAKE_C_FLAGS_DEBUG "-O0 -ggdb -Wall -pedantic -Wextra")
set(CMAKE_C_FLAGS "-O2 -Wall -pedantic -Wextra")
find_package(Boost 1.55.0 COMPONENTS program_options system REQUIRED)
set(JSON_INCLUDE_DIR "/usr/include/jsoncpp" CACHE PATH "path to jsoncpp includes")
include_directories(${JSON_INCLUDE_DIR})
find_package (Threads)
find_package(Qt5Widgets)
set(LIBDOORLOCK_SRCS
lib/clientmessage.cpp
lib/clientmessage.h
lib/door.cpp
lib/door.h
lib/doormessage.cpp
lib/doormessage.h
lib/logger.cpp
lib/logger.h
lib/logic.cpp
lib/logic.h
lib/request.cpp
lib/request.h
lib/response.cpp
lib/response.h
lib/util.cpp
lib/util.h)
set(DOORLOCKD_SRCS
daemon/doorlockd.cpp)
set(DOORLOCK_CLIENT_SRCS
client/qrwidget.cpp
client/qrwidget.h
client/doorlock-client.cpp
client/mainwindow.h
client/mainwindow.cpp
client/mainwindow.ui
client/wave.h
client/wave.cpp)
add_library(doorlock STATIC ${LIBDOORLOCK_SRCS})
target_link_libraries(doorlock jsoncpp ldap ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
add_executable(doorlockd ${DOORLOCKD_SRCS})
target_link_libraries(doorlockd doorlock)
add_executable(doorlock-client ${DOORLOCK_CLIENT_SRCS})
target_link_libraries(doorlock-client doorlock qrencode ao sndfile Qt5::Widgets)
target_include_directories(doorlock-client PRIVATE ${CMAKE_SOURCE_DIR})
install(TARGETS doorlockd DESTINATION sbin/)
install(TARGETS doorlock-client DESTINATION bin/)
install(DIRECTORY images/ DESTINATION share/doorlockd/images
FILES_MATCHING PATTERN "images/*.png"
PERMISSIONS WORLD_READ OWNER_READ GROUP_READ)
install(DIRECTORY sounds/ DESTINATION share/doorlockd/sounds
FILES_MATCHING PATTERN "sounds/*.wav"
PERMISSIONS WORLD_READ OWNER_READ GROUP_READ)
install(FILES scripts/doorlockd.service DESTINATION /etc/systemd/system/)
install(DIRECTORY scripts/ DESTINATION etc/doorlockd/
FILES_MATCHING PATTERN "scripts/pre_*lock"
PERMISSIONS WORLD_EXECUTE WORLD_READ OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE OWNER_WRITE)
install(DIRECTORY scripts/ DESTINATION etc/doorlockd/
FILES_MATCHING PATTERN "scripts/post_*lock"
PERMISSIONS WORLD_EXECUTE WORLD_READ OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE OWNER_WRITE)
install(DIRECTORY scripts/ DESTINATION etc/doorlockd/
FILES_MATCHING PATTERN "scripts/emergency_unlock"
PERMISSIONS WORLD_EXECUTE WORLD_READ OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE OWNER_WRITE)

View File

@ -1,217 +0,0 @@
#include <exception>
#include <iostream>
#include <string>
#include <thread>
#include <ao/ao.h>
#include <boost/asio.hpp>
#include <boost/program_options.hpp>
#include <QApplication>
#include "config.h"
#include "../lib/clientmessage.h"
#include "../lib/logger.h"
#include "../lib/response.h"
#include "mainwindow.h"
// Info about doorlock-client version
const static std::string version =
"doorlock-client-" DOORLOCK_VERSION;
const static std::string gitversion =
DOORLOCK_GIT_BRANCH "-" DOORLOCK_GIT_COMMIT_HASH;
static Logger &l = Logger::get();
namespace po = boost::program_options;
namespace ba = boost::asio;
using ba::ip::tcp;
static ba::io_service io_service;
const static std::string subscriptionCommand =
"{ \"command\": \"subscribe\"}";
// The receive buffer length of the TCP socket
constexpr static int SOCKET_BUFFERLENGTH = 2048;
static volatile bool run = true;
static std::unique_ptr<MainWindow> mainWindow = nullptr;
static void onDoorlockUpdate(const Clientmessage &msg)
{
const auto& doormessage = msg.doormessage();
l("Received message", LogLevel::info);
l((std::string)" token: " + msg.token(),
LogLevel::info);
l((std::string)" open: " + std::to_string(msg.isOpen()),
LogLevel::info);
l((std::string)" button lock: " + std::to_string(doormessage.isLockButton),
LogLevel::info);
l((std::string)" button unlock: " + std::to_string(doormessage.isUnlockButton),
LogLevel::info);
l((std::string)" emergency open: " + std::to_string(doormessage.isEmergencyUnlock),
LogLevel::info);
if (mainWindow) {
mainWindow->setClientmessage(msg);
}
}
static int doorlock_client(const std::string &hostname,
const unsigned short port)
{
int retval = 0;
try {
tcp::resolver resolver(io_service);
tcp::socket socket(io_service);
tcp::resolver::query query(hostname, std::to_string(port));
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::resolver::iterator end;
boost::system::error_code error = ba::error::host_not_found;
std::vector<char> data;
while (error && endpoint_iterator != end) {
socket.close();
socket.connect(*endpoint_iterator++, error);
}
if (error)
throw boost::system::system_error(error);
// After connection is established, send the subscription command
socket.write_some(ba::buffer(subscriptionCommand), error);
if (error)
throw boost::system::system_error(error);
data.resize(SOCKET_BUFFERLENGTH);
std::function<void(void)> receiveMessage = [&] () {
socket.async_read_some(ba::buffer(data),
[&] (const boost::system::error_code &ec,
const size_t length)
{
if (ec) {
throw boost::system::system_error(ec);
}
const auto message = Clientmessage::fromString(
std::string(data.begin(), data.begin()+length));
onDoorlockUpdate(message);
receiveMessage();
});
};
receiveMessage();
io_service.run();
}
catch(const Response &err) {
l(err.message, LogLevel::error);
retval = -1;
}
catch(const std::exception &err) {
l(LogLevel::error, err.what());
retval = -1;
}
io_service.reset();
return retval;
}
int main(int argc, char** argv)
{
std::string hostname;
unsigned short port;
l((std::string)"Hello, this is " + version + " built on " + gitversion,
LogLevel::info);
try {
po::options_description desc("doorlockd (" + version + " built on " + gitversion + ")");
desc.add_options()
("help,h",
"print help")
("port,p",
po::value<unsigned short>(&port)->default_value(DEFAULT_PORT),
"Port")
("host,c",
po::value<std::string>(&hostname)->default_value("localhost"),
"IP or name of host running doorlockd");
po::variables_map vm;
po::store(po::command_line_parser(argc, argv).options(desc).run(), vm);
if (vm.count("help"))
{
std::cout << desc << std::endl;
exit(-1);
}
po::notify(vm);
}
catch(const std::exception &e)
{
l(LogLevel::error, e.what());
exit(-1);
}
l(LogLevel::notice, "Starting doorlock-client");
QApplication app(argc, argv);
app.setOrganizationName("Binary Kitchen");
app.setApplicationName("doorlock-client");
ao_initialize();
try {
mainWindow = std::unique_ptr<MainWindow>(new MainWindow);
mainWindow->showFullScreen();
}
catch(const std::exception &e)
{
l(LogLevel::error, e.what());
exit(-1);
}
// Start the TCP client as thread
std::thread clientThread = std::thread([&] () {
// If the TCP client returns, an error has occured
// In normal operation, it never returns
while (run) {
doorlock_client(hostname, port);
if (run) {
l(LogLevel::error, "client aborted, retrying in 5 seconds");
// Todo: Write message to QT frontend
sleep(5);
}
}
// This will stop the Qapplication
mainWindow->hide();
mainWindow->close();
});
// This routine will never return in normal operation
app.exec();
run = false;
// Stop the IO service
io_service.stop();
clientThread.join();
if (mainWindow)
mainWindow.reset();
ao_shutdown();
l(LogLevel::notice, "Stopping doorlock-client");
return 0;
}

View File

@ -1,75 +0,0 @@
#include "config.h"
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QWidget(parent),
ui(new Ui::MainWindow),
_soundLock(Wave::fromFile(SOUND_LOCK)),
_soundUnlock(Wave::fromFile(SOUND_UNLOCK)),
_soundEmergencyUnlock(Wave::fromFile(SOUND_EMERGENCY_UNLOCK)),
_soundZonk(Wave::fromFile(SOUND_ZONK)),
_soundLockButton(Wave::fromFile(SOUND_LOCK_BUTTON)),
_soundUnlockButton(Wave::fromFile(SOUND_UNLOCK_BUTTON))
{
ui->setupUi(this);
_LED(false);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::setClientmessage(const Clientmessage &msg)
{
ui->qrwidget->setQRData(msg.token());
ui->tokenLabel->setText(QString::fromStdString(msg.token()));
QString statusMessage("");
const auto &doormsg = msg.doormessage();
_LED(msg.isOpen());
if (_oldMessage.isOpen()
&& !msg.isOpen()
&& !doormsg.isLockButton) {
// regular close
statusMessage = "Bye bye. See you next time!";
_soundLock.playAsync();
} else if (!_oldMessage.isOpen() && msg.isOpen()) {
// regular open
statusMessage = "Come in! Happy hacking!";
_soundUnlock.playAsync();
} else {
// no change
}
if (doormsg.isEmergencyUnlock) {
_soundEmergencyUnlock.playAsync();
statusMessage = "!! EMERGENCY UNLOCK !!";
} else if (doormsg.isLockButton) {
_soundLockButton.playAsync();
statusMessage = "!! LOCK BUTTON !!";
} else if (doormsg.isUnlockButton) {
statusMessage = "!! UNLOCK BUTTON !!";
if (msg.isOpen()) {
_soundZonk.playAsync();
} else {
_soundUnlockButton.playAsync();
}
}
ui->message->setText(statusMessage);
_oldMessage = msg;
}
void MainWindow::_LED(const bool on)
{
if (on)
ui->LED->setPixmap(QPixmap(IMAGE_LED_GREEN));
else
ui->LED->setPixmap(QPixmap(IMAGE_LED_RED));
}

View File

@ -1,42 +0,0 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QWidget>
#include "../lib/clientmessage.h"
#include "wave.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QWidget
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
MainWindow(const MainWindow &rhs);
MainWindow &operator =(const MainWindow &rhs);
~MainWindow();
void setClientmessage(const Clientmessage &msg);
private:
Ui::MainWindow *ui;
Clientmessage _oldMessage = { };
const Wave _soundLock;
const Wave _soundUnlock;
const Wave _soundEmergencyUnlock;
const Wave _soundZonk;
const Wave _soundLockButton;
const Wave _soundUnlockButton;
void _LED(const bool on);
};
#endif // MAINWINDOW_H

View File

@ -1,143 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QWidget" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>400</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" alignment="Qt::AlignHCenter">
<widget class="QLabel" name="welcomeLabel">
<property name="font">
<font>
<pointsize>36</pointsize>
</font>
</property>
<property name="text">
<string>Willkommen in der Binary Kitchen!</string>
</property>
</widget>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QRWidget" name="qrwidget" native="true"/>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item alignment="Qt::AlignHCenter">
<widget class="QLabel" name="LED">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item alignment="Qt::AlignHCenter">
<widget class="QLabel" name="message">
<property name="font">
<font>
<pointsize>28</pointsize>
</font>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="tokenLabel">
<property name="font">
<font>
<pointsize>18</pointsize>
</font>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QRWidget</class>
<extends>QWidget</extends>
<header>client/qrwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -1,63 +0,0 @@
#include <exception>
#include <memory>
#include <QPainter>
#include <QDebug>
#include <qrencode.h>
#include "qrwidget.h"
QRWidget::QRWidget(QWidget* parent) :
QWidget(parent),
_data(" ")
{
}
void QRWidget::setQRData(const std::string &data)
{
_data = data;
update();
}
void QRWidget::paintEvent(QPaintEvent*)
{
QPainter painter(this);
std::unique_ptr<QRcode, void(*)(QRcode*)> qr(
QRcode_encodeString(_data.c_str(), 1, QR_ECLEVEL_L, QR_MODE_8, 1),
[] (QRcode* ptr) {
if (ptr)
QRcode_free(ptr);
});
if (!qr)
throw std::runtime_error("Error generating QR Code");
QColor fg("black");
QColor bg("white");
painter.setBrush(bg);
painter.setPen(Qt::NoPen);
painter.drawRect(0,0,width(),height());
painter.setBrush(fg);
const int s=qr->width>0?qr->width:1;
const double w=width();
const double h=height();
const double aspect=w/h;
const double scale=((aspect>1.0)?h:w)/s;
for(int y=0;y<s;y++) {
const int yy=y*s;
for(int x=0;x<s;x++) {
const int xx=yy+x;
const unsigned char b=qr->data[xx];
if(b &0x01) {
const double rx1=x*scale, ry1=y*scale;
QRectF r(rx1, ry1, scale, scale);
painter.drawRects(&r,1);
}
}
}
}

View File

@ -1,23 +0,0 @@
#ifndef QRWIDGET_H
#define QRWIDGET_H
#include <string>
#include <QWidget>
class QRWidget : public QWidget
{
Q_OBJECT
public:
explicit QRWidget(QWidget *parent = nullptr);
void setQRData(const std::string &data);
private:
std::string _data;
protected:
void paintEvent(QPaintEvent *);
};
#endif

View File

@ -1,95 +0,0 @@
#include <cstring>
#include <fstream>
#include <stdexcept>
#include <thread>
#include <sndfile.h>
#include "wave.h"
Wave::Wave(int bits,
int channels,
int rate,
Raw data) :
_format(new ao_sample_format),
_data(data)
{
_format->bits = bits;
_format->rate = rate;
_format->channels = channels;
_format->byte_format = AO_FMT_LITTLE;
_format->matrix = nullptr;
int default_driver;
default_driver = ao_default_driver_id();
_device = ao_open_live(default_driver,
_format.get(),
nullptr);
}
Wave::~Wave()
{
if (_device != nullptr)
ao_close(_device);
}
Wave Wave::fromFile(const std::string &filename)
{
SF_INFO sfinfo;
SNDFILE *file = sf_open(filename.c_str(), SFM_READ, &sfinfo);
if (file == nullptr)
throw std::runtime_error("Unable to open soundfile " + filename);
size_t rawSize = sfinfo.channels * sfinfo.frames * sizeof(short);
Raw data;
data.resize(rawSize);
sf_read_raw(file, &data.front(), data.size());
sf_close(file);
int bits;
switch (sfinfo.format & SF_FORMAT_SUBMASK) {
case SF_FORMAT_PCM_16:
bits = 16;
break;
case SF_FORMAT_PCM_24:
bits = 24;
break;
case SF_FORMAT_PCM_32:
bits = 32;
break;
case SF_FORMAT_PCM_S8:
bits = 8;
break;
case SF_FORMAT_PCM_U8:
bits = 8;
break;
default:
bits = 16;
break;
}
return Wave(bits,
sfinfo.channels,
sfinfo.samplerate,
data);
}
void Wave::play() const
{
std::lock_guard<std::mutex> l(_playMutex);
if (_device == nullptr) {
return;
}
ao_play(_device,
(char*)&_data.front(),
_data.size());
}
void Wave::playAsync() const
{
std::thread([this] () {
play();
}).detach();
}

View File

@ -1,39 +0,0 @@
#ifndef WAVE_H
#define WAVE_H
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include <ao/ao.h>
class Wave {
public:
using Raw = std::vector<char>;
static Wave fromFile(const std::string &filename);
Wave(int bits,
int channels,
int rate,
Raw data);
Wave(const Wave &rhs);
~Wave();
Wave &operator=(const Wave &rhs);
void play() const;
void playAsync() const;
private:
mutable std::mutex _playMutex = { };
std::unique_ptr<ao_sample_format> _format;
ao_device* _device = { nullptr };
const Raw _data;
};
#endif

View File

@ -1,51 +0,0 @@
#ifndef CONFIG_H
#define CONFIG_H
#cmakedefine USE_COLORIZED_LOGS
#define DOORLOCK_VERSION_MAJOR "@DOORLOCK_VERSION_MAJOR@"
#define DOORLOCK_VERSION_MINOR "@DOORLOCK_VERSION_MINOR@"
#define DOORLOCK_VERSION_PATCH "@DOORLOCK_VERSION_PATCH@"
#define DOORLOCK_VERSION "@DOORLOCK_VERSION@"
#define DOORLOCK_GIT_BRANCH @GIT_BRANCH@
#define DOORLOCK_GIT_COMMIT_HASH @GIT_COMMIT_HASH@
#ifdef DEBUG
#define DEFAULT_LOG_LEVEL LogLevel::debug2
#else
#define DEFAULT_LOG_LEVEL LogLevel::info
#endif
#define DEFAULT_TOKEN_TIMEOUT (60*5)
#define DEFAULT_PORT 5555
#define DEFAULT_WEB_PREFIX "https://lock.binary.kitchen/"
#define DEFAULT_LDAP_URI "ldaps://ldap1.binary.kitchen/ ldaps://ldap2.binary.kitchen/ ldaps://ldapm.binary.kitchen/"
#define DEFAULT_BINDDN "cn=%s,ou=people,dc=binary-kitchen,dc=de"
#define DEFAULT_TOKEN_LENGTH 6
#define DEFAULT_LOG_FILE "/var/log/doorlockd.log"
#define DEFAULT_SERIAL_DEVICE "/dev/ttyAMA0"
#define DEFAULT_SERIAL_BAUDRATE 9600UL
#define SHARED_LOCATION "@CMAKE_INSTALL_PREFIX@/share/doorlockd/"
#define IMAGE_LOCATION SHARED_LOCATION "images/"
#define IMAGE_LED_GREEN IMAGE_LOCATION "led-green.png"
#define IMAGE_LED_RED IMAGE_LOCATION "led-red.png"
#define SOUNDS_LOCATION SHARED_LOCATION "sounds/"
#define SOUND_LOCK SOUNDS_LOCATION "lock.wav"
#define SOUND_UNLOCK SOUNDS_LOCATION "unlock.wav"
#define SOUND_EMERGENCY_UNLOCK SOUNDS_LOCATION "emergency_unlock.wav"
#define SOUND_ZONK SOUNDS_LOCATION "zonk.wav"
#define SOUND_LOCK_BUTTON SOUNDS_LOCATION "lock_button.wav"
#define SOUND_UNLOCK_BUTTON SOUNDS_LOCATION "unlock_button.wav"
#define PRE_LOCK_SCRIPT "@CMAKE_INSTALL_PREFIX@/etc/doorlockd/pre_lock &"
#define POST_LOCK_SCRIPT "@CMAKE_INSTALL_PREFIX@/etc/doorlockd/post_lock &"
#define PRE_UNLOCK_SCRIPT "@CMAKE_INSTALL_PREFIX@/etc/doorlockd/pre_unlock &"
#define POST_UNLOCK_SCRIPT "@CMAKE_INSTALL_PREFIX@/etc/doorlockd/post_unlock &"
#define EMERGENCY_UNLOCK_SCRIPT "@CMAKE_INSTALL_PREFIX@/etc/doorlockd/emergency_unlock &"
#endif

View File

@ -1,262 +0,0 @@
#include <csignal>
#include <iostream>
#include <boost/program_options.hpp>
#include <boost/asio.hpp>
#include <json/json.h>
#include "../lib/logic.h"
#include "config.h"
namespace po = boost::program_options;
namespace ba = boost::asio;
using ba::ip::tcp;
// Info about doorlockd version
const static std::string version =
"doorlockd-" DOORLOCK_VERSION;
const static std::string gitversion =
DOORLOCK_GIT_BRANCH "-" DOORLOCK_GIT_COMMIT_HASH;
// The receive buffer length of the TCP socket
const int constexpr SOCKET_BUFFERLENGTH = 2048;
static Logger &l = Logger::get();
static std::unique_ptr<Logic> logic = nullptr;
static ba::io_service io_service;
static std::mutex mutex;
static std::condition_variable onClientMessage;
static volatile bool run = true;
static void signal_handler(int signum)
{
l((std::string)"Received Signal " + std::to_string(signum),
LogLevel::warning);
io_service.stop();
run = false;
onClientMessage.notify_all();
}
static Response subscribe(tcp::socket &sock)
{
sock.write_some(ba::buffer(logic->getClientMessage().toJson()));
while (run) {
std::unique_lock<std::mutex> lock(mutex);
onClientMessage.wait(lock);
if (run) {
sock.write_some(ba::buffer(logic->getClientMessage().toJson()));
}
};
return Response(Response::Code::Success);
}
static void session(tcp::socket &&sock)
{
ba::ip::address remoteAddress;
unsigned short remotePort = 0;
Response response;
try {
std::vector<char> data;
data.resize(SOCKET_BUFFERLENGTH);
remoteAddress = sock.remote_endpoint().address();
remotePort = sock.remote_endpoint().port();
l("Incoming TCP connection from " + remoteAddress.to_string() + "("
+ std::to_string(remotePort) + ")",
LogLevel::notice);
size_t length = sock.read_some(ba::buffer(data));
// Get Request
const std::string requestString(data.begin(), data.begin()+length);
l(" Parsing request...", LogLevel::info);
Request request = Request::fromString(requestString);
switch (request.command) {
case Request::Command::Lock:
case Request::Command::Unlock:
response = logic->request(request);
break;
case Request::Command::Subscribe:
if (remoteAddress.is_loopback() == false) {
response.code = Response::Code::AccessDenied;
response.message = "Subscriptions are only allowed from localhost";
} else {
response = subscribe(sock);
}
break;
case Request::Command::Unknown:
default:
response.code = Response::Code::UnknownCommand;
response.message = "Received unknown command ";
break;
}
throw response;
}
catch (const Response &err) {
response = err;
}
catch (const std::exception &err) {
response.code = Response::Code::Fail;
response.message = "Exception in session " + remoteAddress.to_string()
+ ":" + std::to_string(remotePort) + " : " + err.what();
}
catch (...) {
response.code = Response::Code::Fail;
response.message = "Unhandled doorlockd error";
}
if (!response) {
l(response.message, LogLevel::warning);
}
if (sock.is_open()) {
boost::system::error_code ec;
sock.write_some(ba::buffer(response.toJson()), ec);
}
l("Closing TCP connection from " + remoteAddress.to_string(), LogLevel::notice);
}
static void server(unsigned short port)
{
l(LogLevel::info, "Starting TCP Server");
const auto endpoint = tcp::endpoint(ba::ip::address::from_string("127.0.0.1"), port);
tcp::acceptor a(io_service, endpoint);
tcp::socket sock(io_service);
std::function<void(void)> accept_connection = [&] () {
a.async_accept(sock,
[&] (boost::system::error_code ec) {
if (ec)
{
return;
}
std::thread(session, std::move(sock)).detach();
accept_connection();
});
};
accept_connection();
io_service.run();
l(LogLevel::info, "Stopped TCP Server");
}
int main(int argc, char** argv)
{
int retval = -1;
short port;
std::chrono::seconds tokenTimeout;
std::string ldapUri;
std::string bindDN;
std::string lockPagePrefix;
std::string logfile;
unsigned int tokenLength;
std::string serDev;
unsigned int baudrate;
try {
unsigned int timeout;
po::options_description desc("doorlockd (" + version + " built on " + gitversion + ")");
desc.add_options()
("help,h",
"print help")
("tokentimeout,t",
po::value<unsigned int>(&timeout)->default_value(DEFAULT_TOKEN_TIMEOUT),
"Token timeout in seconds")
("port,p",
po::value<short>(&port)->default_value(DEFAULT_PORT),
"Port")
("ldap,s",
po::value<std::string>(&ldapUri)->default_value(DEFAULT_LDAP_URI),
"Ldap Server")
("bidndn,b",
po::value<std::string>(&bindDN)->default_value(DEFAULT_BINDDN),
"Bind DN, %s means username")
("web,w",
po::value<std::string>(&lockPagePrefix)->default_value(DEFAULT_WEB_PREFIX),
"Prefix of the webpage")
("tokenLength,t",
po::value<unsigned int>(&tokenLength)->default_value(DEFAULT_TOKEN_LENGTH),
"Token length")
("logfile,l",
po::value<std::string>(&logfile)->default_value(DEFAULT_LOG_FILE),
"Log file")
("serial,i",
po::value<std::string>(&serDev)->default_value(DEFAULT_SERIAL_DEVICE),
"Serial port")
("baud,r",
po::value<unsigned int>(&baudrate)->default_value((DEFAULT_SERIAL_BAUDRATE)),
"Serial baudrate");
po::variables_map vm;
po::store(po::command_line_parser(argc, argv).options(desc).run(), vm);
if (vm.count("help"))
{
std::cout << desc << std::endl;
retval = 0;
exit(0);
}
po::notify(vm);
tokenTimeout = std::chrono::seconds(timeout);
l.setLogFile(logfile);
l.logFile(true);
}
catch(const std::exception &e)
{
std::cerr << "Error: " << e.what() << "\n";
exit(-1);
}
l((std::string)"Hello, this is " + version + " built on " + gitversion,
LogLevel::info);
l(LogLevel::notice, "Starting doorlockd");
signal(SIGINT, signal_handler);
signal(SIGKILL, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGUSR1, signal_handler);
signal(SIGUSR2, signal_handler);
l(LogLevel::info, "Starting Doorlock Logic");
retval = 0;
try {
logic = std::unique_ptr<Logic>(new Logic(tokenTimeout,
ldapUri,
bindDN,
lockPagePrefix,
tokenLength,
serDev,
baudrate,
onClientMessage));
server(port);
}
catch (...) {
l(LogLevel::error, "Fatal error, shutting down");
retval = -1;
}
if (logic) {
l(LogLevel::info, "Stopping Doorlock Logic");
logic.reset();
}
l(LogLevel::notice, "Doorlockd stopped");
return retval;
}

View File

@ -1,100 +0,0 @@
#include "clientmessage.h"
#include "util.h"
#include "response.h"
const std::string Clientmessage::_tokenKey = "token";
const std::string Clientmessage::_unlockButtonKey = "unlockButton";
const std::string Clientmessage::_lockButtonKey = "lockButton";
const std::string Clientmessage::_emergencyUnlockKey = "emergencyUnlock";
const std::string Clientmessage::_isOpenKey = "isOpen";
Clientmessage::Clientmessage(std::string token,
bool isOpen,
Doormessage doormessage) :
_token(token),
_isOpen(isOpen),
_doormessage(doormessage)
{
}
Clientmessage::Clientmessage() :
_token(),
_isOpen(false),
_doormessage()
{
}
Clientmessage &Clientmessage::operator=(const Clientmessage &rhs)
{
// Protect against self assignement
if (this == &rhs) {
return *this;
}
this->_token = rhs._token;
this->_isOpen = rhs._isOpen;
this->_doormessage = rhs._doormessage;
return *this;
}
std::string Clientmessage::toJson() const
{
Json::StyledWriter writer;
Json::Value message;
message[_tokenKey] = _token;
message[_unlockButtonKey] = _doormessage.isUnlockButton;
message[_lockButtonKey] = _doormessage.isLockButton;
message[_emergencyUnlockKey] = _doormessage.isEmergencyUnlock;
message[_isOpenKey] = _isOpen;
return writer.write(message);
}
const std::string& Clientmessage::token() const
{
return _token;
}
const Doormessage& Clientmessage::doormessage() const
{
return _doormessage;
}
Clientmessage Clientmessage::fromJson(const Json::Value &root)
{
std::string token;
bool isOpen;
Doormessage doormessage;
try {
token = getJsonOrFail<std::string>(root, _tokenKey);
doormessage.isLockButton = getJsonOrFail<bool>(root, _lockButtonKey);
doormessage.isUnlockButton = getJsonOrFail<bool>(root, _unlockButtonKey);
doormessage.isEmergencyUnlock = getJsonOrFail<bool>(root, _emergencyUnlockKey);
isOpen = getJsonOrFail<bool>(root, _isOpenKey);
}
catch (const std::exception &ex) {
throw Response(Response::Code::JsonError, ex.what());
}
return Clientmessage(token, isOpen, doormessage);
}
Clientmessage Clientmessage::fromString(const std::string &string)
{
Json::Reader reader;
Json::Value root;
if (reader.parse(string, root) == false)
throw Response(Response::Code::NotJson,
"No valid JSON");
return fromJson(root);
}
bool Clientmessage::isOpen() const
{
return _isOpen;
}

View File

@ -1,41 +0,0 @@
#ifndef CLIENTMESSAGE_H
#define CLIENTMESSAGE_H
#include <string>
#include <json/json.h>
#include "doormessage.h"
class Clientmessage
{
public:
Clientmessage(std::string token,
bool isOpen,
Doormessage doormessage);
Clientmessage();
Clientmessage &operator=(const Clientmessage& rhs);
static Clientmessage fromJson(const Json::Value &root);
static Clientmessage fromString(const std::string &json);
std::string toJson() const;
const std::string& token() const;
bool isOpen() const;
const Doormessage& doormessage() const;
private:
std::string _token;
bool _isOpen;
Doormessage _doormessage;
const static std::string _tokenKey;
const static std::string _unlockButtonKey;
const static std::string _lockButtonKey;
const static std::string _emergencyUnlockKey;
const static std::string _isOpenKey;
};
#endif

View File

@ -1,200 +0,0 @@
#include "config.h"
#include "door.h"
#include "../../doorcmds.h"
Door::Door(const std::string &serDev,
unsigned int baudrate) :
_baudrate(baudrate),
_port(_ioService, serDev),
_logger(Logger::get())
{
// Configure serial port
_port.set_option(boost::asio::serial_port_base::baud_rate(baudrate));
_port.set_option(boost::asio::serial_port_base::character_size(8));
_port.set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one));
_port.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none));
_port.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none));
_asyncRead();
_ioThread = std::thread([this] () {
_ioService.run();
});
// TODO Ping device
}
Door::~Door()
{
lock();
_ioService.stop();
_ioService.reset();
_port.cancel();
_port.close();
_ioThread.join();
}
bool Door::readByte(char &byte, std::chrono::milliseconds timeout)
{
std::unique_lock<std::mutex> lock(_receiveLock);
_receivedCondition.wait_for(lock, timeout);
if (_byteReady) {
byte = recvBuf;
_byteReady = false;
return true;
}
return false;
}
void Door::_asyncRead()
{
_port.async_read_some(
boost::asio::buffer(&recvBuf, sizeof(recvBuf)),
[this] (const boost::system::error_code &ec, size_t bytes_transferred) {
if (ec) {
// Operation canceled occurs on system shutdown
// So we return without invoking an additional asyncRead()
if (ec == boost::system::errc::operation_canceled)
return;
_logger(LogLevel::error, "Serialport error: %s", ec.message().c_str());
goto out;
}
if (bytes_transferred != 1) {
_logger(LogLevel::error, "Fatal serial error");
goto out;
}
if (recvBuf == DOOR_BUTTON_UNLOCK) {
// In case that someone pushed the unlock button - just log it.
// No further actions required
_logger(LogLevel::notice, "Someone pushed the unlock button");
if (_doorCallback) {
_doorCallback(Doormessage(true, false, false));
}
goto out;
} else if (recvBuf == DOOR_BUTTON_LOCK) {
_logger(LogLevel::notice, "Someone pushed the lock button");
_logger(LogLevel::notice, "Locking...");
lock();
if (_doorCallback) {
_doorCallback(Doormessage(false, true, false));
}
goto out;
} else if (recvBuf == DOOR_EMERGENCY_UNLOCK) {
_logger(LogLevel::warning, "Someone did an emergency unlock!");
system(EMERGENCY_UNLOCK_SCRIPT);
if (_doorCallback) {
_doorCallback(Doormessage(false, false, true));
}
goto out;
}
_byteReady = true;
_receivedCondition.notify_one();
out:
_asyncRead();
});
}
Door::State Door::state() const
{
return _state;
}
void Door::lock()
{
_stateMutex.lock();
_logger(LogLevel::notice, "Executing Pre Lock Script");
system(PRE_LOCK_SCRIPT);
if (_state == State::Locked) {
_stateMutex.unlock();
_logger(LogLevel::info, "Door already closed");
goto out;
}
_state = State::Locked;
_stateMutex.unlock();
_heartbeatCondition.notify_one();
_heartbeatThread.join();
_logger(LogLevel::notice , "Door closed");
out:
_logger(LogLevel::notice, "Executing Post Lock Script");
system(POST_LOCK_SCRIPT);
}
void Door::unlock()
{
_stateMutex.lock();
_schnapper = true;
_logger(LogLevel::notice, "Executing Pre Unlock Script");
system(PRE_UNLOCK_SCRIPT);
if(_state == State::Unlocked) {
_stateMutex.unlock();
_logger(LogLevel::info, "Door already opened");
goto out;
}
_state = State::Unlocked;
_stateMutex.unlock();
_heartbeatThread = std::thread([this] () {
std::unique_lock<std::mutex> lock(_heartbeatMutex);
while (_state == State::Unlocked) {
if (_state == State::Unlocked) {
writeCMD(DOOR_CMD_UNLOCK);
if (_schnapper) {
_schnapper = false;
writeCMD(DOOR_CMD_SCHNAPER);
}
}
_heartbeatCondition.wait_for(lock, Milliseconds(400));
}
writeCMD(DOOR_CMD_LOCK);
});
_logger(LogLevel::notice, "Door opened");
out:
_logger(LogLevel::notice, "Executing Post Unlock Script");
system(POST_UNLOCK_SCRIPT);
}
bool Door::writeCMD(char c)
{
std::lock_guard<std::mutex> l(_serialMutex);
_port.write_some(boost::asio::buffer(&c, sizeof(c)));
char response;
if (readByte(response, Milliseconds(100)))
{
if (c != response) {
_logger(LogLevel::error, "Sent command '%c' but got response '%c'", c, response);
return false;
}
return true;
}
_logger(LogLevel::error, "Sent Serial command, but got no response!");
return false;
}
void Door::setDoorCallback(DoorCallback doorCallback)
{
_doorCallback = doorCallback;
}

View File

@ -1,77 +0,0 @@
#ifndef DOOR_H
#define DOOR_H
#include <chrono>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <string>
#include <thread>
#include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp>
#include "logger.h"
#include "doormessage.h"
class Door final
{
public:
using DoorCallback = std::function<void(Doormessage)>;
enum class State {Unlocked, Locked};
Door(const std::string &serDev,
unsigned int baudrate);
~Door();
State state() const;
void setDoorCallback(DoorCallback doorCallback);
void lock();
void unlock();
private:
using Milliseconds = std::chrono::milliseconds;
const unsigned int _baudrate;
// To prevent concurrent writes
std::mutex _serialMutex = { };
boost::asio::io_service _ioService = { };
boost::asio::serial_port _port;
std::mutex _stateMutex = { };
volatile State _state = { State::Locked };
std::thread _heartbeatThread = { };
std::mutex _heartbeatMutex = { };
std::condition_variable _heartbeatCondition = { };
std::thread _ioThread = { };
void _asyncRead();
volatile bool _schnapper = { false };
// Indicates if recvBuf contains a valid response from AVR Board
volatile bool _byteReady = { false };
// Actual response
char recvBuf = { };
std::condition_variable _receivedCondition = { };
std::mutex _receiveLock = { };
DoorCallback _doorCallback = { };
Logger &_logger;
// Writed command to AVR board
bool writeCMD(char c);
// Receives one byte and returns true or returns false on timeout
bool readByte(char &byte, Milliseconds timeout);
};
#endif

View File

@ -1,12 +0,0 @@
#include "doormessage.h"
Doormessage::Doormessage()
{
}
Doormessage::Doormessage(bool isUnlockButton, bool isLockButton, bool isEmergencyUnlock) :
isUnlockButton(isUnlockButton),
isLockButton(isLockButton),
isEmergencyUnlock(isEmergencyUnlock)
{
}

View File

@ -1,17 +0,0 @@
#ifndef DOORMESSAGE_H
#define DOORMESSAGE_H
struct Doormessage
{
Doormessage(bool isUnlockButton,
bool isLockButton,
bool isEmergencyUnlock);
Doormessage();
bool isUnlockButton = { false };
bool isLockButton = { false };
bool isEmergencyUnlock = { false };
};
#endif

View File

@ -1,232 +0,0 @@
// (c) 2015 Ralf Ramsauer - Logger class from the sdfs project
#include <iostream>
#include <cstdarg>
#include <ctime>
#include <iomanip>
#include <sstream>
#include <map>
#include <mutex>
#ifdef _WIN32
#include <Windows.h>
#endif
#include "config.h"
#include "logger.h"
#ifdef USE_COLORIZED_LOGS
#ifdef _WIN32 // If Windows, Use Windows Terminal coloring
const static map<LogLevel, WORD> colorAttribute = {
{LogLevel::error, FOREGROUND_RED },
{LogLevel::warning, FOREGROUND_RED | FOREGROUND_GREEN },
{LogLevel::notice, FOREGROUND_BLUE },
{LogLevel::info, FOREGROUND_GREEN },
{LogLevel::debug, FOREGROUND_BLUE | FOREGROUND_RED },
{LogLevel::debug2, FOREGROUND_BLUE | FOREGROUND_RED },
};
#else // Use ANSI Escape sequences
const static std::map<LogLevel, std::string> prefix_ansicolor = {
{LogLevel::error, "\x1b[1m\x1b[31m" },
{LogLevel::warning, "\x1b[1m\x1b[33m" },
{LogLevel::notice, "\x1b[1m\x1b[34m" },
{LogLevel::info, "\x1b[1m\x1b[32m" },
{LogLevel::debug, "\x1b[1m\x1b[35m" },
{LogLevel::debug2, "\x1b[1m\x1b[36m" },
};
const static std::string suffix_ansireset = "\x1b[0m";
#endif
#endif
const static std::map<LogLevel, std::string> logLevel = {
{LogLevel::error, "ERROR " },
{LogLevel::warning, "WARNING" },
{LogLevel::notice, "NOTICE " },
{LogLevel::info, "INFO " },
{LogLevel::debug, "DEBUG " },
{LogLevel::debug2, "DEBUG2 " },
};
Logger::Logger(const LogLevel level) :
_level(level),
_ostreamMutex()
{
}
Logger::~Logger()
{
if (_logFile.is_open())
_logFile.close();
}
Logger &Logger::get()
{
static Logger l(DEFAULT_LOG_LEVEL);
return l;
}
void Logger::operator ()(const std::string &message,
const LogLevel level)
{
if(level > _level)
{
return;
}
std::ostringstream prefix;
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
#if defined(USE_COLORIZED_LOGS) && !defined(_WIN32)
prefix << prefix_ansicolor.at(level);
#endif
// GCC does not support put_time :-(
/*stringstream ss;
ss << std::put_time(&tm, "%d-%m-%Y %H-%M-%S");
prefix = "[" + ss.str() + "] ";*/
const size_t BUFFER_SIZE = 80;
char timeBuffer[BUFFER_SIZE];
std::strftime(timeBuffer, BUFFER_SIZE, "%Y-%m-%d %H:%M:%S", &tm);
prefix << "[" << timeBuffer << "] -- " << logLevel.at(level) << " :: ";
// Critical section
{
std::lock_guard<std::mutex> lock(_ostreamMutex);
#if defined(USE_COLORIZED_LOGS) && !defined(_WIN32)
if (_consoleActive) {
std::cerr << prefix.str() << message
<< suffix_ansireset << std::endl;
std::cerr.flush();
}
if (_logFileActive && _logFile.is_open()) {
_logFile << prefix.str() << message
<< suffix_ansireset << std::endl;
}
#elif defined(USE_COLORIZED_LOGS) && defined(_WIN32)
if (_consoleActive) {
// taken from GTEST
const HANDLE stdout_handle = GetStdHandle(STD_ERROR_HANDLE);
// Gets the current text color.
CONSOLE_SCREEN_BUFFER_INFO buffer_info;
GetConsoleScreenBufferInfo(stdout_handle, &buffer_info);
const WORD old_color_attrs = buffer_info.wAttributes;
// We need to flush the stream buffers into the console before each
// SetConsoleTextAttribute call lest it affect the text that is already
// printed but has not yet reached the console.
std::cerr.flush();
SetConsoleTextAttribute(stdout_handle,
colorAttribute.at(level) | FOREGROUND_INTENSITY);
std::cerr << prefix.str() << message << std::endl;
std::cerr.flush();
// Restores the text color.
SetConsoleTextAttribute(stdout_handle, old_color_attrs);
}
if (_logFileActive && _logFile.is_open()) {
_logFile << prefix.str() << message << std::endl;
}
#else
if (_consoleActive) {
std::cerr << prefix.str() << message << std::endl;
std::cerr.flush();
}
if (_logFileActive && _logFile.is_open()) {
_logFile << prefix.str() << message << std::endl;
}
#endif
}
}
void Logger::operator ()(const std::ostringstream &message, const LogLevel level)
{
(*this)(message.str(), level);
}
void Logger::operator ()(const LogLevel level, const char* format, ...)
{
if(level > _level)
{
return;
}
va_list argp;
char* message = nullptr;
// determine buffer length
va_start(argp, format);
int size = vsnprintf(nullptr, 0, format, argp) + 1;
va_end(argp);
if (size >= 0)
{
message = (char*)malloc(size);
if (message == nullptr)
{
(*this)("[LOGGER] CRITICAL: MEMORY ALLOCATION ERROR",
LogLevel::error);
}
va_start(argp,format);
vsnprintf(message, size, format, argp);
va_end(argp);
(*this)(std::string(message), level);
free(message);
} else {
(*this)("[LOGGER] CRITICAL: VSNPRINTF ERROR",
LogLevel::error);
}
}
void Logger::level(const LogLevel level)
{
_level = level;
}
LogLevel Logger::level() const
{
return _level;
}
bool Logger::console() const
{
return _consoleActive;
}
void Logger::console(const bool active)
{
std::lock_guard<std::mutex> lock(_ostreamMutex);
_consoleActive = active;
}
bool Logger::logFile() const
{
return _logFileActive;
}
void Logger::logFile(const bool active)
{
std::lock_guard<std::mutex> lock(_ostreamMutex);
_logFileActive = active;
}
void Logger::setLogFile(const std::string &logFile)
{
if (_logFile.is_open())
_logFile.close();
_logFile.open(logFile, std::ofstream::out | std::ofstream::app);
if (!_logFile.is_open())
throw std::runtime_error("Unable to open " + logFile);
}

View File

@ -1,75 +0,0 @@
#ifndef LOGGER_H
#define LOGGER_H
#include <fstream>
#include <ostream>
#include <string>
#include <sstream>
#include <mutex>
/**
* @brief The LogLevel enum
*/
enum class LogLevel : unsigned char
{
off, /// disable logging
error, /// error conditions
warning, /// warning conditions
notice, /// normal but significant conditions
info, /// informational messages
debug, /// debug-level messages
debug2 /// more debug-level messages
};
/**
* @brief The Logger class
*
* The logger class is a thread-safe class which is used for formatting and forwarding
* log messages.
*/
class Logger final
{
public:
static Logger &get();
/// Log a string
void operator() (const std::string &message, const LogLevel level = LogLevel::debug);
/// Log a ostringstream
void operator() (const std::ostringstream &message, const LogLevel level = LogLevel::debug);
/// Log a format string
void operator() (const LogLevel level, const char* format, ...);
/// Set minimum required log level to generate output
void level(const LogLevel level);
/// Get minimum required log level to generate output
LogLevel level() const;
/// Getter/Setter for console output
bool console() const;
void console(const bool active);
/// Getter/Setter for logfile output
bool logFile() const;
void logFile(const bool active);
void setLogFile(const std::string &logFile);
private:
/**
* @brief Constructor
* @param level Minimum required log level to generate output
*/
Logger(const LogLevel level);
~Logger();
bool _consoleActive = { true };
bool _logFileActive = { false };
std::ofstream _logFile = {};
LogLevel _level;
mutable std::mutex _ostreamMutex;
};
#endif

View File

@ -1,262 +0,0 @@
#include <errno.h>
#define LDAP_DEPRECATED 1
#include <ldap.h>
#include "logic.h"
#include "util.h"
Logic::Logic(const std::chrono::seconds tokenTimeout,
const std::string &ldapUri,
const std::string &bindDN,
const std::string &webPrefix,
const unsigned int tokenLength,
const std::string &serDev,
const unsigned int baudrate,
std::condition_variable &onClientUpdate) :
_logger(Logger::get()),
_door(serDev, baudrate),
_tokenTimeout(tokenTimeout),
_onClientUpdate(onClientUpdate),
_ldapUri(ldapUri),
_bindDN(bindDN),
_webPrefix(webPrefix),
_tokenLength(tokenLength)
{
srand(time(NULL));
_createNewToken(false);
_door.setDoorCallback(std::bind(&Logic::_doorCallback,
this,
std::placeholders::_1));
_tokenUpdater = std::thread([this] () {
while (_run)
{
std::unique_lock<std::mutex> l(_mutex);
_tokenCondition.wait_for(l, _tokenTimeout);
if (_run == false)
{
break;
} else {
_createNewToken(true);
}
}
});
}
Logic::~Logic()
{
_run = false;
_tokenCondition.notify_one();
_tokenUpdater.join();
}
Response Logic::processDoor(const DoorCommand &doorCommand)
{
Response response;
switch (doorCommand) {
case DoorCommand::Lock:
response = _lock();
break;
case DoorCommand::Unlock:
response = _unlock();
break;
default:
response.code = Response::Code::UnknownCommand;
response.message = "Unknown DoorCommand";
break;
}
return response;
}
Response Logic::request(const Request &request)
{
std::unique_lock<std::mutex> l(_mutex);
Response response;
DoorCommand doorCommand;
switch (request.command) {
case Request::Command::Lock:
doorCommand = DoorCommand::Lock;
break;
case Request::Command::Unlock:
doorCommand = DoorCommand::Unlock;
break;
default:
response.code = Response::Code::UnknownCommand;
response.message = "Unknown Command";
goto out;
}
response = _checkToken(request.token);
if (!response) {
goto out;
}
_logger(LogLevel::info, " -> Token check successful");
response = _checkLDAP(request.user, request.password);
if (!response) {
goto out;
}
_logger(LogLevel::info, " -> LDAP check successful");
response = processDoor(doorCommand);
_logger(LogLevel::info, " -> Door Command successful");
out:
return response;
}
Response Logic::_lock()
{
Response response;
if (_door.state() == Door::State::Locked)
{
response.code = Response::Code::AlreadyLocked;
response.message = "Unable to lock: already closed";
} else {
_createNewToken(false);
response.code = Response::Code::Success;
response.message = "Success";
}
_door.lock();
return response;
}
Response Logic::_unlock()
{
Response response;
const auto oldState = _door.state();
_door.unlock();
_createNewToken(false);
if (oldState == Door::State::Unlocked)
{
response.code = Response::Code::AlreadyUnlocked;
response.message = "Unable to unlock: already unlocked";
_logger(response.message, LogLevel::info);
} else {
response.code = Response::Code::Success;
response.message = "Success";
}
return response;
}
Response Logic::_checkToken(std::string token) const
{
std::transform(token.begin(),
token.end(),
token.begin(),
::toupper);
if (token == _curToken)
return Response(Response::Code::Success);
if (_prevValid == true && token == _prevToken)
return Response(Response::Code::Success);
_logger("Check Token failed: got \"" + token
+ "\", expected \"" + _curToken +"\"",
LogLevel::error);
return Response(Response::InvalidToken,
"User provided invalid token");
}
Response Logic::_checkLDAP(const std::string &user,
const std::string &password)
{
constexpr int BUFFERSIZE = 1024;
char buffer[BUFFERSIZE];
Response retval;
int rc = -1;
LDAP* ld = nullptr;
unsigned long version = LDAP_VERSION3;
_logger(LogLevel::info, " Trying to authenticate as user \"%s\"", user.c_str());
snprintf(buffer, BUFFERSIZE, _bindDN.c_str(), user.c_str());
rc = ldap_initialize(&ld, _ldapUri.c_str());
if(rc != LDAP_SUCCESS)
{
retval.message = (std::string)"LDAP initialize error: "
+ ldap_err2string(rc);
retval.code = Response::Code::LDAPInit;
goto out2;
}
rc = ldap_set_option(ld,
LDAP_OPT_PROTOCOL_VERSION,
(void*)&version);
if (rc != LDAP_SUCCESS)
{
retval.code = Response::Code::LDAPInit;
retval.message = "LDAP set version failed";
goto out;
}
rc = ldap_simple_bind_s(ld, buffer, password.c_str());
if (rc != LDAP_SUCCESS)
{
retval = Response::Code::InvalidCredentials;
retval.message = "Credential check for user \"" + user
+ "\" failed: " + ldap_err2string(rc);
goto out;
}
retval.code = Response::Code::Success;
retval.message = "";
out:
ldap_unbind(ld);
ld = nullptr;
out2:
return retval;
}
void Logic::_createNewToken(const bool stillValid)
{
// Todo Mutex einführen
_prevToken = _curToken;
_prevValid = stillValid;
_curToken = randHexString(_tokenLength);
std::ostringstream message;
message << "New token: " << _curToken
<< " old token: " << _prevToken << " is "
<< (_prevValid?"still":"not") << " valid";
_logger(message, LogLevel::notice);
_onClientUpdate.notify_all();
}
Clientmessage Logic::getClientMessage()
{
std::lock_guard<std::mutex> l(_mutex);
Clientmessage retval(_webPrefix + _curToken,
_door.state() == Door::State::Unlocked,
_doormessage);
// Reset doormessage
_doormessage = Doormessage();
return retval;
}
void Logic::_doorCallback(Doormessage doormessage)
{
std::lock_guard<std::mutex> l(_mutex);
_doormessage = doormessage;
_onClientUpdate.notify_all();
}

View File

@ -1,104 +0,0 @@
#ifndef LOGIC_H
#define LOGIC_H
#include <condition_variable>
#include <mutex>
#include <string>
#include <thread>
#include "config.h"
#include "clientmessage.h"
#include "door.h"
#include "logger.h"
#include "request.h"
#include "response.h"
/* The "Logic" class
*
* This class is initilized by all settings.
*
* It handles incoming requests and allows modifications of the door
*/
class Logic
{
public:
Logic(const std::chrono::seconds tokenTimeout,
const std::string &ldapUri,
const std::string &bindDN,
const std::string &webPrefix,
const unsigned int tokenLength,
const std::string &serDev,
const unsigned int baudrate,
std::condition_variable &onClientUpdate);
~Logic();
// Parse incoming JSON Requests
Response request(const Request &request);
// Send direct command to door without credential checks
enum class DoorCommand { Lock, Unlock };
Response processDoor(const DoorCommand &doorCommand);
// Returns the current Token
Clientmessage getClientMessage();
private:
// Internal lock wrapper
Response _lock();
// Internal unlock wrapper
Response _unlock();
// Checks if the incoming token is valid
Response _checkToken(std::string token) const;
// Checks if incoming credentials against LDAP
Response _checkLDAP(const std::string &user,
const std::string &password);
// Creates a new random token and draws it on the epaper.
// stillValid indicates whether the old (previous) token is still valid
void _createNewToken(const bool stillValid);
void _doorCallback(Doormessage doormessage);
Logger &_logger;
// The door
Door _door;
// The current token
std::string _curToken = {};
// The previous token
std::string _prevToken = {};
// Indicates whether the previous token is valid
bool _prevValid = { false };
// Tokens are refreshed all tokenTimout seconds
const std::chrono::seconds _tokenTimeout;
// Thread for asynchronosly updating tokens
std::thread _tokenUpdater = {};
// Thread can be force-triggered for updates using the condition variable
std::condition_variable _tokenCondition = {};
// stop indicator for the thread
bool _run = true;
// General mutex for concurrent data access
mutable std::mutex _mutex = {};
Doormessage _doormessage = {};
// This variable gets notified on token updates
std::condition_variable &_onClientUpdate;
// The URI of the ldap server
const std::string _ldapUri;
// LDAP bindDN
const std::string _bindDN;
// Prefix of the website
const std::string _webPrefix;
// Length of the token in bytes
const unsigned int _tokenLength;
};
#endif

View File

@ -1,69 +0,0 @@
#include "request.h"
#include "util.h"
#include "logger.h"
const std::string Request::_commandKey = "command";
Request::Command Request::_commandFromString(const std::string &command)
{
Command retval = Command::Unknown;
if (command == "lock")
retval = Command::Lock;
else if (command == "unlock")
retval = Command::Unlock;
else if (command == "subscribe")
retval = Command::Subscribe;
else
retval = Command::Unknown;
return retval;
}
Request Request::fromJson(const Json::Value &root)
{
Request retval;
auto &l = Logger::get();
try {
const auto commandStr =
getJsonOrFail<std::string>(root, _commandKey);
l(" command: " + commandStr, LogLevel::info);
retval.command = _commandFromString(commandStr);
// Stop parsing, if command is unknown
if (retval.command == Command::Unknown)
return retval;
if (retval.command == Command::Lock ||
retval.command == Command::Unlock) {
retval.user = getJsonOrFail<std::string>(root, "user");
l(" user: " + retval.user, LogLevel::info);
retval.password = getJsonOrFail<std::string>(root, "password");
l(" password: XXX", LogLevel::info);
retval.token = getJsonOrFail<std::string>(root, "token");
l(" token: " + retval.token, LogLevel::info);
}
if (retval.command == Command::Subscribe) {
// Nothing to do in this case
}
}
catch (const std::exception &ex) {
throw Response(Response::Code::JsonError, ex.what());
}
return retval;
}
Request Request::fromString(const std::string &string)
{
Json::Reader reader;
Json::Value root;
if (reader.parse(string, root) == false)
throw Response(Response::Code::NotJson,
"No valid JSON");
return fromJson(root);
}

View File

@ -1,29 +0,0 @@
#ifndef REQUEST_H
#define REQUEST_H
#include <string>
#include <json/json.h>
#include "response.h"
class Request
{
public:
static Request fromJson(const Json::Value &root);
static Request fromString(const std::string &string);
enum class Command { Lock, Unlock, Subscribe, Unknown }
command = { Command::Unknown };
std::string user = { };
std::string password = { };
std::string token = { };
private:
static Command _commandFromString(const std::string &command);
const static std::string _commandKey;
};
#endif // REQUEST_H

View File

@ -1,69 +0,0 @@
#include <exception>
#include <json/json.h>
#include "response.h"
#include "util.h"
const std::string Response::_codeKey = "code";
const std::string Response::_messageKey = "message";
Response::Response():
code(Fail),
message("General failure")
{
}
Response::Response(Response::Code code, const std::string &what) :
code(code),
message(what)
{
}
Response::operator bool() const
{
return code == Response::Code::Success;
}
std::string Response::toJson() const
{
Json::Value response;
Json::StyledWriter writer;
response[_codeKey] = code;
response[_messageKey] = message;
return writer.write(response);
}
Response Response::fromJson(const Json::Value &root)
{
Response retval;
try {
retval.message = getJsonOrFail<std::string>(root, _messageKey);
const auto code = getJsonOrFail<int>(root, _codeKey);
if (code > Code::RESPONSE_NUM_ITEMS)
throw std::runtime_error("Error code out of range");
retval.code = static_cast<Code>(code);
}
catch (const std::exception &ex) {
throw Response(Response::Code::JsonError, ex.what());
}
return retval;
}
Response Response::fromString(const std::string &json)
{
Json::Reader reader;
Json::Value root;
if (reader.parse(json, root) == false)
throw Response(Response::Code::NotJson,
"No valid JSON");
return fromJson(root);
}

View File

@ -1,44 +0,0 @@
#ifndef RESPONSE_H
#define RESPONSE_H
#include <string>
#include <json/json.h>
class Response
{
public:
enum Code {
Success = 0, // Request successful
Fail, // General non-specified error
AlreadyUnlocked, // Authentication successful, but door is already unlocked
AlreadyLocked, // Authentication successful, but door is already locked
NotJson, // Request is not a valid JSON object
JsonError, // Request is valid JSON, but does not contain necessary material
InvalidToken, // Request contains invalid token
InvalidCredentials, // Invalid LDAP credentials
InvalidIP, // IP check failure
UnknownCommand, // Unknown action
LDAPInit, // Ldap initialization failed
AccessDenied, // Access denied
RESPONSE_NUM_ITEMS
} code;
std::string message;
Response();
Response(Response::Code code, const std::string &message = "");
static Response fromJson(const Json::Value &root);
static Response fromString(const std::string &json);
std::string toJson() const;
// Returns true if code is success
operator bool() const;
private:
const static std::string _codeKey;
const static std::string _messageKey;
};
#endif // RESPONSE_H

View File

@ -1,76 +0,0 @@
#include "util.h"
template <>
int getJson(const Json::Value &root, const std::string &key)
{
auto val = root.get(key, Json::Value());
if (val.isInt())
return val.asInt();
throw std::runtime_error("Json Type error");
}
template <>
std::string getJson(const Json::Value &root, const std::string &key)
{
auto val = root.get(key, Json::Value());
if (val.isString())
return val.asString();
throw std::runtime_error("Json Type error");
}
template <>
size_t getJson(const Json::Value &root, const std::string &key)
{
auto val = root.get(key, Json::Value());
if (val.isInt())
return val.asUInt64();
throw std::runtime_error("Json Type error");
}
template <>
bool getJson(const Json::Value &root, const std::string &key)
{
auto val = root.get(key, Json::Value());
if (val.isBool())
return val.asBool();
throw std::runtime_error("Json Type error");
}
template <>
Json::Value getJson(const Json::Value &root, const std::string &key)
{
auto val = root.get(key, Json::Value());
return val;
}
static char nibble2hex(unsigned char input)
{
input &= 0xf;
if(input <= 9)
{
return input + '0';
}
return input - 0xA + 'A';
}
std::string randHexString(unsigned int len)
{
std::string retval;
while (len--)
retval += nibble2hex(rand() & 0xF);
return retval;
}
unsigned char hex2uchar(const char input)
{
if(input >= '0' && input <= '9') {
return input - '0';
} else if(input >= 'A' && input <= 'F') {
return input - 'A' + 10;
} else if(input >= 'a' && input <= 'f') {
return input - 'a' + 10;
}
throw std::runtime_error("Malformed hexadecimal input");
}

View File

@ -1,28 +0,0 @@
#ifndef UTIL_H
#define UTIL_H
#include <algorithm>
#include <exception>
#include <json/json.h>
template <typename T>
T getJson(const Json::Value &root, const std::string &key);
template <typename T>
static T getJsonOrFail(const Json::Value &root, const std::string &key)
{
if (root.isObject() == false)
{
throw std::runtime_error("Invalid Json Object");
}
if (root.isMember(key) == false) {
throw std::runtime_error("Json key \"" + key + "\" not existing");
}
return getJson<T>(root, key);
}
std::string randHexString(unsigned int len);
#endif

View File

@ -1 +0,0 @@
#!/bin/bash

View File

@ -1,3 +0,0 @@
#!/bin/bash
wget -O /dev/null --timeout 3 "http://homer.binary.kitchen:8080/set?color=000000" > /dev/null 2>&1

View File

@ -1,3 +0,0 @@
#!/bin/bash
wget -O /dev/null --timeout 3 "http://homer.binary.kitchen:8080/set?color=0000FF" > /dev/null 2>&1

View File

@ -1 +0,0 @@
#!/bin/bash

View File

@ -1 +0,0 @@
#!/bin/bash

View File

@ -1,23 +0,0 @@
#!/bin/bash
echo -n "Cmd: "
read CMD
echo -n "User: "
read USER
echo -n "Password: "
read -s PW
echo
echo -n "Token: "
read TOKEN
cat << EOF | nc localhost 5555
{
"command" : "$CMD",
"user" : "$USER",
"password" : "$PW",
"token" : "$TOKEN"
}
EOF

198
doorstate Executable file
View File

@ -0,0 +1,198 @@
#!/usr/bin/env python3
"""
Doorlockd -- Binary Kitchen's smart door opener
Copyright (c) Binary Kitchen e.V., 2019
Author:
Ralf Ramsauer <ralf@binary-kitchen.de>
This work is licensed under the terms of the GNU GPL, version 2. See
the LICENSE file in the top-level directory.
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.
"""
import gpiod
import os
import sys
from subprocess import Popen
from threading import Timer, Thread, Event
import paho.mqtt.client as mqtt
from pydoorlock.Config import Config, sounds_prefix
prog = sys.argv[0]
global is_locked
is_locked = None
class AudioPlayer(Thread):
def __init__(self, filename):
super(AudioPlayer, self).__init__()
self._filename = filename
self._event = Event()
self._shutdown = False
self._process = None
self._state = False
def set(self, state):
self._state = state
self._event.set()
def shutdown(self):
self._state = False
self._shutdown = True
self._event.set()
def run(self):
while not self._shutdown:
self._event.wait(1)
if self._event.is_set():
print('Came by event')
self._event.clear()
if self._process:
if self._process.poll() is not None:
print('Cleaning up...')
self._process.wait()
self._process = None
self._state = False
elif not self._state:
print('Killing...')
self._process.terminate()
self._process.wait()
self._process = None
self._state = False
else:
if self._state:
print('Starting...')
self._process = Popen(['mpg123', self._filename])
def mqtt_on_connect(client, userdata, flags, rc):
global mqtt_connected
if rc == 0:
print("MqTT: Connection returned result: " + mqtt.connack_string(rc))
client.subscribe(topic_alarm)
client.subscribe(topic_lockstate)
mqtt_connected = True
else:
print("MqTT: Bad connection Returned code=", rc)
def mqtt_on_disconnect(client, userdata, rc):
global mqtt_connected
if rc != 0:
print("Unexpected MQTT disconnection")
mqtt_connected = False
def mqtt_on_lockstate(client, userdata, message):
global is_locked
is_locked = message.payload.decode('utf-8') == 'locked'
print('is_locked: %s' % is_locked)
def mqtt_on_alarm(client, userdata, message):
alarm_set = message.payload.decode('utf-8') == '1'
print('MQTT: alarm: %u' % alarm_set)
player_alarm.set(alarm_set)
def publish_doorstate():
global door_open
message = 'open' if door_open else 'closed'
mqttc.publish(topic_doorstate, message, retain=True)
cfg = Config('dooralarm')
topic_alarm = cfg.str('TOPIC_ALARM')
topic_doorstate = cfg.str('TOPIC_DOORSTATE')
topic_lockstate = cfg.str('TOPIC_LOCKSTATE')
doorstate_alarm_timeout = int(cfg.str('DOORSTATE_ALARM_TIMEOUT'))
f_alarm = os.path.join(sounds_prefix, 'alarm.mp3')
f_door_call = os.path.join(sounds_prefix, 'door_call.mp3')
mqttc = mqtt.Client(client_id=prog)
mqttc.username_pw_set(cfg.str('MQTT_USERNAME'), cfg.str('MQTT_PASSWORD'))
mqttc.on_connect = mqtt_on_connect
mqttc.on_disconnect = mqtt_on_disconnect
mqttc.message_callback_add(topic_alarm, mqtt_on_alarm)
mqttc.message_callback_add(topic_lockstate, mqtt_on_lockstate)
mqttc.connect(cfg.str('MQTT_HOST'))
pin = cfg.int('GPIO_PIN')
request = gpiod.request_lines(
cfg.str('GPIO_CHIP'),
consumer = prog,
config = {
pin: gpiod.LineSettings(direction=gpiod.line.Direction.INPUT,
edge_detection=gpiod.line.Edge.BOTH),
},
)
player_alarm = AudioPlayer(f_alarm)
player_alarm.start()
player_door_call = AudioPlayer(f_door_call)
player_door_call.start()
global door_open
door_open = request.get_value(pin) == gpiod.line.Value.ACTIVE
door_alarm_set = False
global mqtt_connected
mqtt_connected = False
mqttc.loop_start()
publish_doorstate()
while True:
# Synchronous loop: GPIO
ev_line = request.wait_edge_events(1)
if ev_line:
request.read_edge_events()
door_open = request.get_value(pin) == gpiod.line.Value.ACTIVE
print('door_open: %s' % door_open)
publish_doorstate()
if not mqtt_connected:
print('No MQTT connection!')
if door_alarm_set:
player_door_call.set(False)
door_alarm_set = False
player_alarm.set(False)
continue
# Door State stuff
if is_locked is not None:
if is_locked is False:
if door_alarm_set:
player_door_call.set(False)
door_alarm_set = False
elif door_open is True:
if not door_alarm_set:
door_alarm_set = True
player_door_call.set(True)
elif door_open is False:
if door_alarm_set:
player_door_call.set(False)
door_alarm_set = False
mqttc.loop_stop()
player_alarm.shutdown()
player_alarm.join()
player_door_call.shutdown()
player_door_call.join()

2
etc/doorlockd.blacklist Normal file
View File

@ -0,0 +1,2 @@
# Place blacklisted usernames here, separated by newlines. Blacklist applies to
# all authentication backends.

41
etc/doorlockd.cfg Normal file
View File

@ -0,0 +1,41 @@
[doorlockd]
DEBUG = False
SIMULATE_SERIAL = False
SIMULATE_AUTH = False
RUN_HOOKS = True
SOUNDS = True
# LDAP
# LDAP_URI = ldaps://ldap1.binary.kitchen
# LDAP_BINDDN = cn=%%s,ou=people,dc=binary-kitchen,dc=de
# Authentication Backends
# Local
# LOCAL_USER_DB = /etc/doorlockd.passwd
# USER_BLACKLIST = /etc/doorlockd.blacklist
TITLE = Binary Kitchen Doorlock
ROOM = Hauptraum
WELCOME = Willkommen in der Binary Kitchen
SERIAL_PORT = /dev/ttyAMA0
SECRET_KEY = foobar
[dooralarm]
GPIO_CHIP = /dev/gpiochip0
GPIO_PIN = 22
TOPIC_ALARM = kitchen/alarm
TOPIC_DOORSTATE = kitchen/doorlock/frontdoor/doorstate
TOPIC_LOCKSTATE = kitchen/doorlock/frontdoor/lockstate
DOORSTATE_ALARM_TIMEOUT = 2
MQTT_HOST = pizza.binary.kitchen
MQTT_USERNAME = doorlock
MQTT_PASSWORD =

132
pydoorlock/Authenticator.py Normal file
View File

@ -0,0 +1,132 @@
"""
Doorlockd -- Binary Kitchen's smart door opener
Copyright (c) Binary Kitchen e.V., 2018
Author:
Ralf Ramsauer <ralf@binary-kitchen.de>
This work is licensed under the terms of the GNU GPL, version 2. See
the LICENSE file in the top-level directory.
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.
"""
import hashlib
import ldap
import logging
from enum import Enum
from .Doorlock import DoorlockResponse
log = logging.getLogger()
class AuthMethod(Enum):
LDAP_USER_PW = 1
LOCAL_USER_DB = 2
def __str__(self):
if self == AuthMethod.LDAP_USER_PW:
return 'LDAP'
elif self == AuthMethod.LOCAL_USER_DB:
return 'Local'
return 'Error'
class Authenticator:
def __init__(self, cfg):
self._simulate = cfg.boolean('SIMULATE_AUTH')
self._backends = set()
f_blacklist = cfg.str('USER_BLACKLIST')
self._user_blacklist = set()
if f_blacklist:
with open(f_blacklist, 'r') as f:
for line in f:
line = line.strip()
if line.startswith('#'):
continue
if line:
self._user_blacklist.add(line)
if self._simulate:
return
self._ldap_uri = cfg.str('LDAP_URI')
self._ldap_binddn = cfg.str('LDAP_BINDDN')
if self._ldap_uri and self._ldap_binddn:
log.info('Initialising LDAP auth backend')
self._backends.add(AuthMethod.LDAP_USER_PW)
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
ldap.set_option(ldap.OPT_REFERRALS, 0)
file_local_db = cfg.str('LOCAL_USER_DB')
if file_local_db:
log.info('Initialising local auth backend')
self._local_db = dict()
with open(file_local_db, 'r') as f:
for line in f:
line = line.split()
user = line[0]
pwd = line[1].split(':')
self._local_db[user] = pwd
self._backends.add(AuthMethod.LOCAL_USER_DB)
@property
def backends(self):
return self._backends
def _try_auth_local(self, user, password):
log.info(' Trying to local auth (user, password) as user %s', user)
if user not in self._local_db:
log.info(' No user %s in local database', user)
return DoorlockResponse.Perm
stored_pw = self._local_db[user][0]
stored_salt = self._local_db[user][1]
if stored_pw == hashlib.sha256(stored_salt.encode() + password.encode()).hexdigest():
log.info(' Authenticated as user %s', user)
return DoorlockResponse.Success
log.info(' Invalid credentials')
return DoorlockResponse.Perm
def _try_auth_ldap(self, user, password):
log.info(' Trying to LDAP auth (user, password) as user %s', user)
ldap_username = self._ldap_binddn % user
try:
l = ldap.initialize(self._ldap_uri)
l.simple_bind_s(ldap_username, password)
l.unbind_s()
except ldap.INVALID_CREDENTIALS:
log.info(' Invalid credentials')
return DoorlockResponse.Perm
except ldap.LDAPError as e:
log.info(' LDAP Error: %s' % e)
return DoorlockResponse.InternalError
log.info(' Authenticated as user %s', user)
return DoorlockResponse.Success
def try_auth(self, credentials):
user, password = credentials
if user in self._user_blacklist:
return DoorlockResponse.Perm
if self._simulate:
log.info('SIMULATION MODE! ACCEPTING ANYTHING!')
return DoorlockResponse.Success
if AuthMethod.LDAP_USER_PW in self._backends:
retval = self._try_auth_ldap(user, password)
if retval == DoorlockResponse.Success:
return retval
if AuthMethod.LOCAL_USER_DB in self._backends:
return self._try_auth_local(user, password)
return DoorlockResponse.Perm

57
pydoorlock/Config.py Normal file
View File

@ -0,0 +1,57 @@
"""
Doorlockd -- Binary Kitchen's smart door opener
Copyright (c) Binary Kitchen e.V., 2018-2019
Author:
Ralf Ramsauer <ralf@binary-kitchen.de>
Thomas Schmid <tom@binary-kitchen.de>
This work is licensed under the terms of the GNU GPL, version 2. See
the LICENSE file in the top-level directory.
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.
"""
import functools
from configparser import ConfigParser
from os.path import join
SYSCONFDIR = './etc'
PREFIX = '.'
root_prefix = join(PREFIX, 'share', 'doorlockd')
sounds_prefix = join(root_prefix, 'sounds')
def check_exists(func):
@functools.wraps(func)
def decorator(*args, **kwargs):
config = args[0]
if not config.config.has_option(config.config_topic, args[1]):
return None
return func(*args, **kwargs)
return decorator
class Config:
def __init__(self, config_topic):
self.config_topic = config_topic
self.config = ConfigParser()
self.config.read(join(SYSCONFDIR, 'doorlockd.cfg'))
@check_exists
def boolean(self, key):
return self.config.getboolean(self.config_topic, key)
@check_exists
def str(self, key):
return self.config.get(self.config_topic, key)
@check_exists
def int(self, key):
return self.config.getint(self.config_topic, key)

55
pydoorlock/Door.py Normal file
View File

@ -0,0 +1,55 @@
"""
Doorlockd -- Binary Kitchen's smart door opener
Copyright (c) Binary Kitchen e.V., 2018
Author:
Ralf Ramsauer <ralf@binary-kitchen.de>
This work is licensed under the terms of the GNU GPL, version 2. See
the LICENSE file in the top-level directory.
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.
"""
from enum import Enum
class DoorState(Enum):
Open = 0
Present = 1
Closed = 2
@staticmethod
def from_string(string):
if string == 'lock':
return DoorState.Closed
elif string == 'unlock':
return DoorState.Open
elif string == 'present':
return DoorState.Present
return None
def is_open(self):
if self != DoorState.Closed:
return True
return False
def to_img(self):
led = 'red'
if self == DoorState.Present:
led = 'yellow'
elif self == DoorState.Open:
led = 'green'
return 'static/led-%s.png' % led
def __str__(self):
if self == DoorState.Open:
return 'Offen'
elif self == DoorState.Present:
return 'Jemand da'
return 'Geschlossen'

253
pydoorlock/Doorlock.py Normal file
View File

@ -0,0 +1,253 @@
"""
Doorlockd -- Binary Kitchen's smart door opener
Copyright (c) Binary Kitchen e.V., 2018-2019
Author:
Ralf Ramsauer <ralf@binary-kitchen.de>
This work is licensed under the terms of the GNU GPL, version 2. See
the LICENSE file in the top-level directory.
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.
"""
import logging
from enum import Enum
from random import sample
from subprocess import run
from serial import Serial
from threading import Thread
from time import sleep
from os.path import join
from .Door import DoorState
from .Protocol import Protocol
log = logging.getLogger()
# copied from sudo
eperm_insults = [
'Wrong! You cheating scum!',
'And you call yourself a Rocket Scientist!',
'No soap, honkie-lips.',
'Where did you learn to type?',
'Are you on drugs?',
'My pet ferret can type better than you!',
'You type like i drive.',
'Do you think like you type?',
'Your mind just hasn\'t been the same since the electro-shock, has it?',
'Maybe if you used more than just two fingers...',
'BOB says: You seem to have forgotten your passwd, enter another!',
'stty: unknown mode: doofus',
'I can\'t hear you -- I\'m using the scrambler.',
'The more you drive -- the dumber you get.',
'Listen, broccoli brains, I don\'t have time to listen to this trash.',
'I\'ve seen penguins that can type better than that.',
'Have you considered trying to match wits with a rutabaga?',
'You speak an infinite deal of nothing',
]
def choose_insult():
return sample(eperm_insults, 1)[0]
def run_background(cmd):
run('%s &' % cmd, shell=True)
class DoorlockResponse(Enum):
Success = 0
Perm = 1
AlreadyActive = 2
# don't break old apps, value 3 is reserved now
RESERVED = 3
Inval = 4
EmergencyUnlock = 10,
ButtonLock = 11,
ButtonUnlock = 12,
ButtonPresent = 13,
def __str__(self):
if self == DoorlockResponse.Success:
return 'Yo, passt.'
elif self == DoorlockResponse.Perm:
return choose_insult()
elif self == DoorlockResponse.AlreadyActive:
return 'Zustand bereits aktiv'
elif self == DoorlockResponse.Inval:
return 'Das was du willst geht nicht.'
elif self == DoorlockResponse.EmergencyUnlock:
return '!!! Emergency Unlock !!!'
elif self == DoorlockResponse.ButtonLock:
return 'Closed by button'
elif self == DoorlockResponse.ButtonUnlock:
return 'Opened by button'
elif self == DoorlockResponse.ButtonPresent:
return 'Present by button'
return 'Error'
class DoorHandler:
state = DoorState.Closed
do_close = False
wave_lock = 'lock.wav'
wave_lock_button = 'lock_button.wav'
wave_present = 'present.wav'
wave_present_button = 'present.wav'
wave_unlock = 'unlock.wav'
wave_unlock_button = 'unlock_button.wav'
wave_zonk = 'zonk.wav'
def __init__(self, cfg, sounds_prefix, scripts_prefix):
self._callback = None
self.sounds = cfg.boolean('SOUNDS')
if self.sounds:
self.sounds_prefix = sounds_prefix
self.scripts_prefix = scripts_prefix
self.run_hooks = cfg.boolean('RUN_HOOKS')
if cfg.boolean('SIMULATE_SERIAL'):
return
device = cfg.str('SERIAL_PORT')
log.info('Using serial port: %s' % device)
self.serial = Serial(device, baudrate=9600, bytesize=8, parity='N',
stopbits=1, timeout=0)
self.thread = Thread(target=self.thread_worker)
log.debug('Spawning RS232 Thread')
self.thread.start()
def thread_worker(self):
while True:
sleep(0.4)
while True:
rx = self.serial.read(1)
if len(rx) == 0:
break
old_state = self.state
if rx == Protocol.STATE_SWITCH_RED.value.upper():
self.close()
log.info('Closed due to Button press')
self.invoke_callback(DoorlockResponse.ButtonLock)
elif rx == Protocol.STATE_SWITCH_GREEN.value.upper():
self.open()
log.info('Opened due to Button press')
self.invoke_callback(DoorlockResponse.ButtonUnlock)
elif rx == Protocol.STATE_SWITCH_YELLOW.value.upper():
self.present()
log.info('Present due to Button press')
self.invoke_callback(DoorlockResponse.ButtonPresent)
elif rx == Protocol.EMERGENCY.value:
log.warning('Emergency unlock')
self.invoke_callback(DoorlockResponse.EmergencyUnlock)
else:
log.error('Received unknown message "%s" from AVR' % rx)
self.sound_helper(old_state, self.state, True)
if self.do_close:
tx = Protocol.STATE_SWITCH_RED.value
self.do_close = False
elif self.state == DoorState.Present:
tx = Protocol.STATE_SWITCH_YELLOW.value
elif self.state == DoorState.Open:
tx = Protocol.STATE_SWITCH_GREEN.value
else:
continue
self.serial.write(tx)
self.serial.flush()
def open(self):
if self.state == DoorState.Open:
return DoorlockResponse.AlreadyActive
self.state = DoorState.Open
self.run_hook('post_unlock')
return DoorlockResponse.Success
def present(self):
if self.state == DoorState.Present:
return DoorlockResponse.AlreadyActive
self.state = DoorState.Present
self.run_hook('post_present')
return DoorlockResponse.Success
def close(self):
if self.state == DoorState.Closed:
return DoorlockResponse.AlreadyActive
self.do_close = True
self.state = DoorState.Closed
self.run_hook('post_lock')
return DoorlockResponse.Success
def request(self, state):
old_state = self.state
if state == DoorState.Closed:
err = self.close()
elif state == DoorState.Present:
err = self.present()
elif state == DoorState.Open:
err = self.open()
self.sound_helper(old_state, self.state, False)
self.invoke_callback(err)
return err
def sound_helper(self, old_state, new_state, button):
if not self.sounds:
return
# TBD: Emergency Unlock
# wave_emergency = 'emergency_unlock.wav'
if old_state == new_state:
filename = self.wave_zonk
elif button:
if new_state == DoorState.Open:
filename = self.wave_unlock_button
elif new_state == DoorState.Present:
filename = self.wave_present_button
elif new_state == DoorState.Closed:
filename = self.wave_lock_button
else:
if new_state == DoorState.Open:
filename = self.wave_unlock
elif new_state == DoorState.Present:
filename = self.wave_present
elif new_state == DoorState.Closed:
filename = self.wave_lock
run_background('aplay %s' % join(self.sounds_prefix, filename))
def run_hook(self, script):
if not self.run_hooks:
log.info('Hooks disabled: not starting %s' % script)
return
log.info('Starting hook %s' % script)
run_background(join(self.scripts_prefix, script))
def register_callback(self, callback):
self._callback = callback
def invoke_callback(self, val):
if self._callback:
self._callback(val)

205
pydoorlock/WebApp.py Normal file
View File

@ -0,0 +1,205 @@
"""
Doorlockd -- Binary Kitchen's smart door opener
Copyright (c) Binary Kitchen e.V., 2020
Author:
Ralf Ramsauer <ralf@binary-kitchen.de>
This work is licensed under the terms of the GNU GPL, version 2. See
the LICENSE file in the top-level directory.
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.
"""
import logging
import json
import threading
from flask import abort, Flask, jsonify, render_template, request, Response
from flask_wtf import FlaskForm
from wtforms import PasswordField, StringField, SubmitField
from wtforms.validators import DataRequired, Length
from .Door import DoorState
from .Doorlock import DoorlockResponse
log = logging.getLogger()
webapp = Flask(__name__)
evt = threading.Event()
json_push_state = ""
def emit_doorstate(response=None):
global json_push_state
if response:
message = str(response)
else:
message = str(logic.state)
json_push_state = json.dumps({
'message': message,
'img': logic.state.to_img()})
# Notify push clients
evt.set()
evt.clear()
def push_state():
def event_str():
return "data: {}\n\n".format(json_push_state)
try:
yield event_str()
while True:
evt.wait()
yield event_str()
except GeneratorExit:
return
class AuthenticationForm(FlaskForm):
username = StringField('Username', [Length(min=3, max=25)])
password = PasswordField('Password', [DataRequired()])
open = SubmitField('Open')
present = SubmitField('Present')
close = SubmitField('Close')
def __init__(self, *args, **kwargs):
FlaskForm.__init__(self, *args, **kwargs)
self.desired_state = DoorState.Closed
def validate(self):
if not FlaskForm.validate(self):
return False
if self.open.data:
self.desired_state = DoorState.Open
elif self.present.data:
self.desired_state = DoorState.Present
return True
@webapp.route('/display')
def display():
return render_template('display.html',
room=room,
title=title,
welcome=welcome)
@webapp.route('/push')
def push():
emit_doorstate()
resp = Response(push_state(), mimetype="text/event-stream")
resp.headers['X-Accel-Buffering'] = 'no'
resp.headers['Cache-Control'] = 'no-cache'
return resp
@webapp.route('/api', methods=['POST'])
def api():
def json_response(response, msg=None):
json_dict = dict()
json_dict['err'] = response.value
json_dict['msg'] = str(response) if msg is None else msg
if response == DoorlockResponse.Success or \
response == DoorlockResponse.AlreadyActive:
# TBD: Remove 'open'. No more users. Still used in App Version 2.1.1!
json_dict['open'] = logic.state.is_open()
json_dict['status'] = logic.state.value
return jsonify(json_dict)
user = request.form.get('user')
password = request.form.get('pass')
command = request.form.get('command')
if (command is not None) and (command == 'status'):
return json_response(DoorlockResponse.Success)
if any(v is None for v in [user, password, command]):
log.warning('Incomplete API request')
abort(400)
if not user or not password:
log.warning('Invalid username or password format')
return json_response(DoorlockResponse.Inval,
'Invalid username or password format')
credentials = user, password
desired_state = DoorState.from_string(command)
if not desired_state:
return json_response(DoorlockResponse.Inval, "Invalid command requested")
log.info('Incoming API request from %s' % user.encode('utf-8'))
log.info(' desired state: %s' % desired_state)
log.info(' current state: %s' % logic.state)
response = logic.request(desired_state, credentials)
return json_response(response)
@webapp.route('/', methods=['GET', 'POST'])
def home():
authentication_form = AuthenticationForm()
response = None
if request.method == 'POST' and authentication_form.validate():
user = authentication_form.username.data
password = authentication_form.password.data
credentials = user, password
log.info('Incoming request from %s' % user.encode('utf-8'))
desired_state = authentication_form.desired_state
log.info(' desired state: %s' % desired_state)
log.info(' current state: %s' % logic.state)
response = logic.request(desired_state, credentials)
log.info(' response: %s' % response)
# Don't trust python, zero credentials
user = password = credentials = None
return render_template('index.html',
authentication_form=authentication_form,
response=response,
state_text=str(logic.state),
led=logic.state.to_img(),
banner='%s - %s' % (title, room))
def webapp_run(cfg, my_logic, status, version, template_folder, static_folder):
global logic
logic = my_logic
debug = cfg.boolean('DEBUG')
host = 'localhost'
if debug:
host = '0.0.0.0'
global room
room = cfg.str('ROOM')
global title
title = cfg.str('TITLE')
global welcome
welcome = cfg.str('WELCOME')
global html_title
html_title = '%s (%s - v%s)' % (title, status, version)
webapp.config['SECRET_KEY'] = cfg.str('SECRET_KEY')
webapp.template_folder = template_folder
webapp.static_folder = static_folder
webapp.debug = debug
webapp.run(host=host, port=8080)

0
pydoorlock/__init__.py Normal file
View File

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

29
schematics/Doorlock.txt Normal file
View File

@ -0,0 +1,29 @@
Qty Value Device Package Parts Reichelt
2 BC817-16-NPN-SOT23-BEC SOT23-BEC T1, T2 BC 816-16 SMD
1 CRYSTALHC49UP HC49UP Q1 4,0000-HC49-SMD
2 LEDCHIP-LED0805 CHIP-LED0805 LED1, LED2 SLO SMD-R0805-0
1 PINHD-1X2 1X02 JP1 BKL 10120183
1 SJ2W SJ_2 SJ1 JUMPER 2,54GL SW
1 0 R-EU_R0805 R0805 R14 SMD-0805 0,00
2 100 R-EU_R0805 R0805 R16, R17 SMD-0805 100
6 100n C-EUC0805 C0805 C3, C5, C6, C8, C9, C11 X7R 0805 CD 120N
5 10k R-EU_R0805 R0805 R2, R6, R7, R11, R15 SMD-0805 10,0K
7 1k R-EU_R0805 R0805 R1, R3, R4, R5, R10, R18, R19 SMD-0805 1,00K
2 220 R-EU_R0805 R0805 R12, R13 SMD-0805 220
2 22p C-EUC0805 C0805 C1, C2 NPO 0805 BG 22P
1 24C32ASN 24C32ASN SO-08 IC2 ST 24C32 MN6
2 3k3 R-EU_R0805 R0805 R8, R9 SMD-0805 3,30K
3 3k9 R-EU_R0805 R0805 R20, R21, R22 SMD-0805 3,90K
1 AT90S2313S AT90S2313S SO20L IC1 ATTINY 2313A-SU
2 CGRM4002-G CGRM4002-G SOD-123_MINI-SMA D1, D2 RND 1N4148W
2 G5V1 G5V1 G5V1 K1, K2 G5V-1 5V
1 ILD205 ILD205 SOIC08 OK1 ILD 217T
1 RASPI_BOARD_B+#_H RASPI_BOARD_B+#_H RASPI_BOARD_B+HAT X1 /
1 RIA182-04 RIA182-04 RIA182-04 X2 AKL 169-04 / AKL 182-04
1 RIA182-05 RIA182-05 RIA182-05 X4 AKL 169-05 / AKL 182-05
1 RJ45 RJ45-8 RJ45-8 J1 MEBP 8-8S
1 VF1000/10K-G CPOL-EUUD-10X10 UD-10X10_NICHICON C4 VF 1000/10K-G
Buchsenleiste: MPE 094-2-040
https://www.reichelt.de/my/1503673

35
scripts/gen_protocol.sh Executable file
View File

@ -0,0 +1,35 @@
#!/bin/bash
#
# doorlock-avr, AVR code of Binary Kitchen's doorlock
#
# Copyright (c) Binary Kitchen, 2019
#
# Authors:
# Ralf Ramsauer <ralf@binary-kitchen.de>
#
# This work is licensed under the terms of the GNU GPL, version 2. See
# the COPYING file in the top-level directory.
PROTOCOL_H=$1
function find_defines() {
header=$1
prefix=$2
search="#define\s\+${prefix}\(\S*\)\s\+\(\S*\).*"
replace=" \1\t= b\2"
grep $prefix $header | sed -e "s/$search/$replace/"
}
AVR_COMM=$(find_defines $PROTOCOL_H AVR_)
cat <<END
# This file is autogenerated. If you need to change it, edit
# scripts/gen_protocol.sh instead.
from enum import Enum
class Protocol(Enum):
${AVR_COMM}
END

25
setup.py Normal file
View File

@ -0,0 +1,25 @@
#
# pydoorlock, the python library for doorlock
#
# Author:
# Ralf Ramsauer <ralf@binary-kitchen.de>
#
# Copyright (c) Binary Kitchen, 2018-2021
#
# This work is licensed under the terms of the GNU GPL, version 2. See
# the COPYING file in the top-level directory.
#
from setuptools import setup, find_packages
with open("VERSION") as version_file:
version = version_file.read().lstrip("v").rstrip()
setup(name="pydoorlock", version=version,
description="A Python interface for doorlock",
license="GPLv2",
url="https://github.com/Binary-Kitchen/doorlockd/",
author_email="ralf@binary-kitchen.de",
packages=find_packages(),
install_requires=["Flask", "Flask-WTF", "pyserial", "python-ldap"],
)

View File

@ -0,0 +1,3 @@
#!/bin/bash
mosquitto_pub -r -t kitchen/doorlock/frontdoor/lockstate -m "locked"

View File

@ -0,0 +1,3 @@
#!/bin/bash
mosquitto_pub -r -t kitchen/doorlock/frontdoor/lockstate -m "present"

View File

@ -0,0 +1,3 @@
#!/bin/bash
mosquitto_pub -r -t kitchen/doorlock/frontdoor/lockstate -m "unlocked"

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -0,0 +1,532 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Created with qrencode 4.0.2 (https://fukuchi.org/works/qrencode/index.html) -->
<svg width="4.34cm" height="4.34cm" viewBox="0 0 41 41" preserveAspectRatio="none" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g id="QRcode">
<rect x="0" y="0" width="41" height="41" fill="#ffffff"/>
<g id="Pattern" transform="translate(4,4)">
<rect x="0" y="0" width="1" height="1" fill="#000000"/>
<rect x="1" y="0" width="1" height="1" fill="#000000"/>
<rect x="2" y="0" width="1" height="1" fill="#000000"/>
<rect x="3" y="0" width="1" height="1" fill="#000000"/>
<rect x="4" y="0" width="1" height="1" fill="#000000"/>
<rect x="5" y="0" width="1" height="1" fill="#000000"/>
<rect x="6" y="0" width="1" height="1" fill="#000000"/>
<rect x="8" y="0" width="1" height="1" fill="#000000"/>
<rect x="9" y="0" width="1" height="1" fill="#000000"/>
<rect x="13" y="0" width="1" height="1" fill="#000000"/>
<rect x="14" y="0" width="1" height="1" fill="#000000"/>
<rect x="18" y="0" width="1" height="1" fill="#000000"/>
<rect x="21" y="0" width="1" height="1" fill="#000000"/>
<rect x="26" y="0" width="1" height="1" fill="#000000"/>
<rect x="27" y="0" width="1" height="1" fill="#000000"/>
<rect x="28" y="0" width="1" height="1" fill="#000000"/>
<rect x="29" y="0" width="1" height="1" fill="#000000"/>
<rect x="30" y="0" width="1" height="1" fill="#000000"/>
<rect x="31" y="0" width="1" height="1" fill="#000000"/>
<rect x="32" y="0" width="1" height="1" fill="#000000"/>
<rect x="0" y="1" width="1" height="1" fill="#000000"/>
<rect x="6" y="1" width="1" height="1" fill="#000000"/>
<rect x="12" y="1" width="1" height="1" fill="#000000"/>
<rect x="14" y="1" width="1" height="1" fill="#000000"/>
<rect x="18" y="1" width="1" height="1" fill="#000000"/>
<rect x="19" y="1" width="1" height="1" fill="#000000"/>
<rect x="21" y="1" width="1" height="1" fill="#000000"/>
<rect x="26" y="1" width="1" height="1" fill="#000000"/>
<rect x="32" y="1" width="1" height="1" fill="#000000"/>
<rect x="0" y="2" width="1" height="1" fill="#000000"/>
<rect x="2" y="2" width="1" height="1" fill="#000000"/>
<rect x="3" y="2" width="1" height="1" fill="#000000"/>
<rect x="4" y="2" width="1" height="1" fill="#000000"/>
<rect x="6" y="2" width="1" height="1" fill="#000000"/>
<rect x="9" y="2" width="1" height="1" fill="#000000"/>
<rect x="10" y="2" width="1" height="1" fill="#000000"/>
<rect x="12" y="2" width="1" height="1" fill="#000000"/>
<rect x="13" y="2" width="1" height="1" fill="#000000"/>
<rect x="14" y="2" width="1" height="1" fill="#000000"/>
<rect x="15" y="2" width="1" height="1" fill="#000000"/>
<rect x="20" y="2" width="1" height="1" fill="#000000"/>
<rect x="21" y="2" width="1" height="1" fill="#000000"/>
<rect x="22" y="2" width="1" height="1" fill="#000000"/>
<rect x="23" y="2" width="1" height="1" fill="#000000"/>
<rect x="26" y="2" width="1" height="1" fill="#000000"/>
<rect x="28" y="2" width="1" height="1" fill="#000000"/>
<rect x="29" y="2" width="1" height="1" fill="#000000"/>
<rect x="30" y="2" width="1" height="1" fill="#000000"/>
<rect x="32" y="2" width="1" height="1" fill="#000000"/>
<rect x="0" y="3" width="1" height="1" fill="#000000"/>
<rect x="2" y="3" width="1" height="1" fill="#000000"/>
<rect x="3" y="3" width="1" height="1" fill="#000000"/>
<rect x="4" y="3" width="1" height="1" fill="#000000"/>
<rect x="6" y="3" width="1" height="1" fill="#000000"/>
<rect x="10" y="3" width="1" height="1" fill="#000000"/>
<rect x="13" y="3" width="1" height="1" fill="#000000"/>
<rect x="17" y="3" width="1" height="1" fill="#000000"/>
<rect x="19" y="3" width="1" height="1" fill="#000000"/>
<rect x="21" y="3" width="1" height="1" fill="#000000"/>
<rect x="23" y="3" width="1" height="1" fill="#000000"/>
<rect x="24" y="3" width="1" height="1" fill="#000000"/>
<rect x="26" y="3" width="1" height="1" fill="#000000"/>
<rect x="28" y="3" width="1" height="1" fill="#000000"/>
<rect x="29" y="3" width="1" height="1" fill="#000000"/>
<rect x="30" y="3" width="1" height="1" fill="#000000"/>
<rect x="32" y="3" width="1" height="1" fill="#000000"/>
<rect x="0" y="4" width="1" height="1" fill="#000000"/>
<rect x="2" y="4" width="1" height="1" fill="#000000"/>
<rect x="3" y="4" width="1" height="1" fill="#000000"/>
<rect x="4" y="4" width="1" height="1" fill="#000000"/>
<rect x="6" y="4" width="1" height="1" fill="#000000"/>
<rect x="13" y="4" width="1" height="1" fill="#000000"/>
<rect x="14" y="4" width="1" height="1" fill="#000000"/>
<rect x="16" y="4" width="1" height="1" fill="#000000"/>
<rect x="17" y="4" width="1" height="1" fill="#000000"/>
<rect x="18" y="4" width="1" height="1" fill="#000000"/>
<rect x="19" y="4" width="1" height="1" fill="#000000"/>
<rect x="20" y="4" width="1" height="1" fill="#000000"/>
<rect x="21" y="4" width="1" height="1" fill="#000000"/>
<rect x="26" y="4" width="1" height="1" fill="#000000"/>
<rect x="28" y="4" width="1" height="1" fill="#000000"/>
<rect x="29" y="4" width="1" height="1" fill="#000000"/>
<rect x="30" y="4" width="1" height="1" fill="#000000"/>
<rect x="32" y="4" width="1" height="1" fill="#000000"/>
<rect x="0" y="5" width="1" height="1" fill="#000000"/>
<rect x="6" y="5" width="1" height="1" fill="#000000"/>
<rect x="9" y="5" width="1" height="1" fill="#000000"/>
<rect x="11" y="5" width="1" height="1" fill="#000000"/>
<rect x="17" y="5" width="1" height="1" fill="#000000"/>
<rect x="20" y="5" width="1" height="1" fill="#000000"/>
<rect x="23" y="5" width="1" height="1" fill="#000000"/>
<rect x="24" y="5" width="1" height="1" fill="#000000"/>
<rect x="26" y="5" width="1" height="1" fill="#000000"/>
<rect x="32" y="5" width="1" height="1" fill="#000000"/>
<rect x="0" y="6" width="1" height="1" fill="#000000"/>
<rect x="1" y="6" width="1" height="1" fill="#000000"/>
<rect x="2" y="6" width="1" height="1" fill="#000000"/>
<rect x="3" y="6" width="1" height="1" fill="#000000"/>
<rect x="4" y="6" width="1" height="1" fill="#000000"/>
<rect x="5" y="6" width="1" height="1" fill="#000000"/>
<rect x="6" y="6" width="1" height="1" fill="#000000"/>
<rect x="8" y="6" width="1" height="1" fill="#000000"/>
<rect x="10" y="6" width="1" height="1" fill="#000000"/>
<rect x="12" y="6" width="1" height="1" fill="#000000"/>
<rect x="14" y="6" width="1" height="1" fill="#000000"/>
<rect x="16" y="6" width="1" height="1" fill="#000000"/>
<rect x="18" y="6" width="1" height="1" fill="#000000"/>
<rect x="20" y="6" width="1" height="1" fill="#000000"/>
<rect x="22" y="6" width="1" height="1" fill="#000000"/>
<rect x="24" y="6" width="1" height="1" fill="#000000"/>
<rect x="26" y="6" width="1" height="1" fill="#000000"/>
<rect x="27" y="6" width="1" height="1" fill="#000000"/>
<rect x="28" y="6" width="1" height="1" fill="#000000"/>
<rect x="29" y="6" width="1" height="1" fill="#000000"/>
<rect x="30" y="6" width="1" height="1" fill="#000000"/>
<rect x="31" y="6" width="1" height="1" fill="#000000"/>
<rect x="32" y="6" width="1" height="1" fill="#000000"/>
<rect x="8" y="7" width="1" height="1" fill="#000000"/>
<rect x="9" y="7" width="1" height="1" fill="#000000"/>
<rect x="12" y="7" width="1" height="1" fill="#000000"/>
<rect x="13" y="7" width="1" height="1" fill="#000000"/>
<rect x="14" y="7" width="1" height="1" fill="#000000"/>
<rect x="18" y="7" width="1" height="1" fill="#000000"/>
<rect x="20" y="7" width="1" height="1" fill="#000000"/>
<rect x="22" y="7" width="1" height="1" fill="#000000"/>
<rect x="0" y="8" width="1" height="1" fill="#000000"/>
<rect x="1" y="8" width="1" height="1" fill="#000000"/>
<rect x="3" y="8" width="1" height="1" fill="#000000"/>
<rect x="4" y="8" width="1" height="1" fill="#000000"/>
<rect x="6" y="8" width="1" height="1" fill="#000000"/>
<rect x="9" y="8" width="1" height="1" fill="#000000"/>
<rect x="12" y="8" width="1" height="1" fill="#000000"/>
<rect x="14" y="8" width="1" height="1" fill="#000000"/>
<rect x="16" y="8" width="1" height="1" fill="#000000"/>
<rect x="17" y="8" width="1" height="1" fill="#000000"/>
<rect x="18" y="8" width="1" height="1" fill="#000000"/>
<rect x="19" y="8" width="1" height="1" fill="#000000"/>
<rect x="20" y="8" width="1" height="1" fill="#000000"/>
<rect x="22" y="8" width="1" height="1" fill="#000000"/>
<rect x="26" y="8" width="1" height="1" fill="#000000"/>
<rect x="32" y="8" width="1" height="1" fill="#000000"/>
<rect x="1" y="9" width="1" height="1" fill="#000000"/>
<rect x="3" y="9" width="1" height="1" fill="#000000"/>
<rect x="4" y="9" width="1" height="1" fill="#000000"/>
<rect x="5" y="9" width="1" height="1" fill="#000000"/>
<rect x="8" y="9" width="1" height="1" fill="#000000"/>
<rect x="10" y="9" width="1" height="1" fill="#000000"/>
<rect x="11" y="9" width="1" height="1" fill="#000000"/>
<rect x="14" y="9" width="1" height="1" fill="#000000"/>
<rect x="15" y="9" width="1" height="1" fill="#000000"/>
<rect x="17" y="9" width="1" height="1" fill="#000000"/>
<rect x="19" y="9" width="1" height="1" fill="#000000"/>
<rect x="21" y="9" width="1" height="1" fill="#000000"/>
<rect x="22" y="9" width="1" height="1" fill="#000000"/>
<rect x="23" y="9" width="1" height="1" fill="#000000"/>
<rect x="27" y="9" width="1" height="1" fill="#000000"/>
<rect x="28" y="9" width="1" height="1" fill="#000000"/>
<rect x="30" y="9" width="1" height="1" fill="#000000"/>
<rect x="31" y="9" width="1" height="1" fill="#000000"/>
<rect x="1" y="10" width="1" height="1" fill="#000000"/>
<rect x="5" y="10" width="1" height="1" fill="#000000"/>
<rect x="6" y="10" width="1" height="1" fill="#000000"/>
<rect x="7" y="10" width="1" height="1" fill="#000000"/>
<rect x="8" y="10" width="1" height="1" fill="#000000"/>
<rect x="9" y="10" width="1" height="1" fill="#000000"/>
<rect x="11" y="10" width="1" height="1" fill="#000000"/>
<rect x="15" y="10" width="1" height="1" fill="#000000"/>
<rect x="16" y="10" width="1" height="1" fill="#000000"/>
<rect x="17" y="10" width="1" height="1" fill="#000000"/>
<rect x="20" y="10" width="1" height="1" fill="#000000"/>
<rect x="22" y="10" width="1" height="1" fill="#000000"/>
<rect x="23" y="10" width="1" height="1" fill="#000000"/>
<rect x="25" y="10" width="1" height="1" fill="#000000"/>
<rect x="28" y="10" width="1" height="1" fill="#000000"/>
<rect x="31" y="10" width="1" height="1" fill="#000000"/>
<rect x="32" y="10" width="1" height="1" fill="#000000"/>
<rect x="0" y="11" width="1" height="1" fill="#000000"/>
<rect x="7" y="11" width="1" height="1" fill="#000000"/>
<rect x="8" y="11" width="1" height="1" fill="#000000"/>
<rect x="14" y="11" width="1" height="1" fill="#000000"/>
<rect x="15" y="11" width="1" height="1" fill="#000000"/>
<rect x="16" y="11" width="1" height="1" fill="#000000"/>
<rect x="17" y="11" width="1" height="1" fill="#000000"/>
<rect x="20" y="11" width="1" height="1" fill="#000000"/>
<rect x="24" y="11" width="1" height="1" fill="#000000"/>
<rect x="25" y="11" width="1" height="1" fill="#000000"/>
<rect x="26" y="11" width="1" height="1" fill="#000000"/>
<rect x="27" y="11" width="1" height="1" fill="#000000"/>
<rect x="29" y="11" width="1" height="1" fill="#000000"/>
<rect x="30" y="11" width="1" height="1" fill="#000000"/>
<rect x="3" y="12" width="1" height="1" fill="#000000"/>
<rect x="4" y="12" width="1" height="1" fill="#000000"/>
<rect x="5" y="12" width="1" height="1" fill="#000000"/>
<rect x="6" y="12" width="1" height="1" fill="#000000"/>
<rect x="9" y="12" width="1" height="1" fill="#000000"/>
<rect x="10" y="12" width="1" height="1" fill="#000000"/>
<rect x="16" y="12" width="1" height="1" fill="#000000"/>
<rect x="20" y="12" width="1" height="1" fill="#000000"/>
<rect x="23" y="12" width="1" height="1" fill="#000000"/>
<rect x="26" y="12" width="1" height="1" fill="#000000"/>
<rect x="27" y="12" width="1" height="1" fill="#000000"/>
<rect x="31" y="12" width="1" height="1" fill="#000000"/>
<rect x="32" y="12" width="1" height="1" fill="#000000"/>
<rect x="0" y="13" width="1" height="1" fill="#000000"/>
<rect x="2" y="13" width="1" height="1" fill="#000000"/>
<rect x="3" y="13" width="1" height="1" fill="#000000"/>
<rect x="4" y="13" width="1" height="1" fill="#000000"/>
<rect x="8" y="13" width="1" height="1" fill="#000000"/>
<rect x="10" y="13" width="1" height="1" fill="#000000"/>
<rect x="14" y="13" width="1" height="1" fill="#000000"/>
<rect x="15" y="13" width="1" height="1" fill="#000000"/>
<rect x="16" y="13" width="1" height="1" fill="#000000"/>
<rect x="18" y="13" width="1" height="1" fill="#000000"/>
<rect x="23" y="13" width="1" height="1" fill="#000000"/>
<rect x="25" y="13" width="1" height="1" fill="#000000"/>
<rect x="27" y="13" width="1" height="1" fill="#000000"/>
<rect x="0" y="14" width="1" height="1" fill="#000000"/>
<rect x="3" y="14" width="1" height="1" fill="#000000"/>
<rect x="4" y="14" width="1" height="1" fill="#000000"/>
<rect x="5" y="14" width="1" height="1" fill="#000000"/>
<rect x="6" y="14" width="1" height="1" fill="#000000"/>
<rect x="7" y="14" width="1" height="1" fill="#000000"/>
<rect x="9" y="14" width="1" height="1" fill="#000000"/>
<rect x="10" y="14" width="1" height="1" fill="#000000"/>
<rect x="14" y="14" width="1" height="1" fill="#000000"/>
<rect x="15" y="14" width="1" height="1" fill="#000000"/>
<rect x="19" y="14" width="1" height="1" fill="#000000"/>
<rect x="20" y="14" width="1" height="1" fill="#000000"/>
<rect x="21" y="14" width="1" height="1" fill="#000000"/>
<rect x="22" y="14" width="1" height="1" fill="#000000"/>
<rect x="0" y="15" width="1" height="1" fill="#000000"/>
<rect x="1" y="15" width="1" height="1" fill="#000000"/>
<rect x="2" y="15" width="1" height="1" fill="#000000"/>
<rect x="3" y="15" width="1" height="1" fill="#000000"/>
<rect x="7" y="15" width="1" height="1" fill="#000000"/>
<rect x="9" y="15" width="1" height="1" fill="#000000"/>
<rect x="11" y="15" width="1" height="1" fill="#000000"/>
<rect x="12" y="15" width="1" height="1" fill="#000000"/>
<rect x="14" y="15" width="1" height="1" fill="#000000"/>
<rect x="15" y="15" width="1" height="1" fill="#000000"/>
<rect x="18" y="15" width="1" height="1" fill="#000000"/>
<rect x="20" y="15" width="1" height="1" fill="#000000"/>
<rect x="23" y="15" width="1" height="1" fill="#000000"/>
<rect x="25" y="15" width="1" height="1" fill="#000000"/>
<rect x="27" y="15" width="1" height="1" fill="#000000"/>
<rect x="28" y="15" width="1" height="1" fill="#000000"/>
<rect x="30" y="15" width="1" height="1" fill="#000000"/>
<rect x="32" y="15" width="1" height="1" fill="#000000"/>
<rect x="0" y="16" width="1" height="1" fill="#000000"/>
<rect x="1" y="16" width="1" height="1" fill="#000000"/>
<rect x="2" y="16" width="1" height="1" fill="#000000"/>
<rect x="4" y="16" width="1" height="1" fill="#000000"/>
<rect x="6" y="16" width="1" height="1" fill="#000000"/>
<rect x="8" y="16" width="1" height="1" fill="#000000"/>
<rect x="12" y="16" width="1" height="1" fill="#000000"/>
<rect x="13" y="16" width="1" height="1" fill="#000000"/>
<rect x="14" y="16" width="1" height="1" fill="#000000"/>
<rect x="16" y="16" width="1" height="1" fill="#000000"/>
<rect x="19" y="16" width="1" height="1" fill="#000000"/>
<rect x="20" y="16" width="1" height="1" fill="#000000"/>
<rect x="22" y="16" width="1" height="1" fill="#000000"/>
<rect x="24" y="16" width="1" height="1" fill="#000000"/>
<rect x="25" y="16" width="1" height="1" fill="#000000"/>
<rect x="26" y="16" width="1" height="1" fill="#000000"/>
<rect x="27" y="16" width="1" height="1" fill="#000000"/>
<rect x="28" y="16" width="1" height="1" fill="#000000"/>
<rect x="29" y="16" width="1" height="1" fill="#000000"/>
<rect x="31" y="16" width="1" height="1" fill="#000000"/>
<rect x="32" y="16" width="1" height="1" fill="#000000"/>
<rect x="1" y="17" width="1" height="1" fill="#000000"/>
<rect x="7" y="17" width="1" height="1" fill="#000000"/>
<rect x="9" y="17" width="1" height="1" fill="#000000"/>
<rect x="13" y="17" width="1" height="1" fill="#000000"/>
<rect x="17" y="17" width="1" height="1" fill="#000000"/>
<rect x="18" y="17" width="1" height="1" fill="#000000"/>
<rect x="19" y="17" width="1" height="1" fill="#000000"/>
<rect x="20" y="17" width="1" height="1" fill="#000000"/>
<rect x="24" y="17" width="1" height="1" fill="#000000"/>
<rect x="26" y="17" width="1" height="1" fill="#000000"/>
<rect x="27" y="17" width="1" height="1" fill="#000000"/>
<rect x="28" y="17" width="1" height="1" fill="#000000"/>
<rect x="31" y="17" width="1" height="1" fill="#000000"/>
<rect x="32" y="17" width="1" height="1" fill="#000000"/>
<rect x="2" y="18" width="1" height="1" fill="#000000"/>
<rect x="4" y="18" width="1" height="1" fill="#000000"/>
<rect x="6" y="18" width="1" height="1" fill="#000000"/>
<rect x="8" y="18" width="1" height="1" fill="#000000"/>
<rect x="10" y="18" width="1" height="1" fill="#000000"/>
<rect x="11" y="18" width="1" height="1" fill="#000000"/>
<rect x="15" y="18" width="1" height="1" fill="#000000"/>
<rect x="17" y="18" width="1" height="1" fill="#000000"/>
<rect x="19" y="18" width="1" height="1" fill="#000000"/>
<rect x="21" y="18" width="1" height="1" fill="#000000"/>
<rect x="22" y="18" width="1" height="1" fill="#000000"/>
<rect x="23" y="18" width="1" height="1" fill="#000000"/>
<rect x="27" y="18" width="1" height="1" fill="#000000"/>
<rect x="32" y="18" width="1" height="1" fill="#000000"/>
<rect x="3" y="19" width="1" height="1" fill="#000000"/>
<rect x="7" y="19" width="1" height="1" fill="#000000"/>
<rect x="8" y="19" width="1" height="1" fill="#000000"/>
<rect x="11" y="19" width="1" height="1" fill="#000000"/>
<rect x="12" y="19" width="1" height="1" fill="#000000"/>
<rect x="13" y="19" width="1" height="1" fill="#000000"/>
<rect x="14" y="19" width="1" height="1" fill="#000000"/>
<rect x="19" y="19" width="1" height="1" fill="#000000"/>
<rect x="20" y="19" width="1" height="1" fill="#000000"/>
<rect x="25" y="19" width="1" height="1" fill="#000000"/>
<rect x="26" y="19" width="1" height="1" fill="#000000"/>
<rect x="27" y="19" width="1" height="1" fill="#000000"/>
<rect x="28" y="19" width="1" height="1" fill="#000000"/>
<rect x="30" y="19" width="1" height="1" fill="#000000"/>
<rect x="31" y="19" width="1" height="1" fill="#000000"/>
<rect x="32" y="19" width="1" height="1" fill="#000000"/>
<rect x="1" y="20" width="1" height="1" fill="#000000"/>
<rect x="2" y="20" width="1" height="1" fill="#000000"/>
<rect x="3" y="20" width="1" height="1" fill="#000000"/>
<rect x="4" y="20" width="1" height="1" fill="#000000"/>
<rect x="5" y="20" width="1" height="1" fill="#000000"/>
<rect x="6" y="20" width="1" height="1" fill="#000000"/>
<rect x="8" y="20" width="1" height="1" fill="#000000"/>
<rect x="10" y="20" width="1" height="1" fill="#000000"/>
<rect x="12" y="20" width="1" height="1" fill="#000000"/>
<rect x="14" y="20" width="1" height="1" fill="#000000"/>
<rect x="17" y="20" width="1" height="1" fill="#000000"/>
<rect x="18" y="20" width="1" height="1" fill="#000000"/>
<rect x="19" y="20" width="1" height="1" fill="#000000"/>
<rect x="23" y="20" width="1" height="1" fill="#000000"/>
<rect x="24" y="20" width="1" height="1" fill="#000000"/>
<rect x="27" y="20" width="1" height="1" fill="#000000"/>
<rect x="0" y="21" width="1" height="1" fill="#000000"/>
<rect x="1" y="21" width="1" height="1" fill="#000000"/>
<rect x="3" y="21" width="1" height="1" fill="#000000"/>
<rect x="5" y="21" width="1" height="1" fill="#000000"/>
<rect x="8" y="21" width="1" height="1" fill="#000000"/>
<rect x="9" y="21" width="1" height="1" fill="#000000"/>
<rect x="10" y="21" width="1" height="1" fill="#000000"/>
<rect x="11" y="21" width="1" height="1" fill="#000000"/>
<rect x="14" y="21" width="1" height="1" fill="#000000"/>
<rect x="15" y="21" width="1" height="1" fill="#000000"/>
<rect x="17" y="21" width="1" height="1" fill="#000000"/>
<rect x="18" y="21" width="1" height="1" fill="#000000"/>
<rect x="19" y="21" width="1" height="1" fill="#000000"/>
<rect x="23" y="21" width="1" height="1" fill="#000000"/>
<rect x="28" y="21" width="1" height="1" fill="#000000"/>
<rect x="29" y="21" width="1" height="1" fill="#000000"/>
<rect x="31" y="21" width="1" height="1" fill="#000000"/>
<rect x="0" y="22" width="1" height="1" fill="#000000"/>
<rect x="2" y="22" width="1" height="1" fill="#000000"/>
<rect x="6" y="22" width="1" height="1" fill="#000000"/>
<rect x="7" y="22" width="1" height="1" fill="#000000"/>
<rect x="8" y="22" width="1" height="1" fill="#000000"/>
<rect x="11" y="22" width="1" height="1" fill="#000000"/>
<rect x="15" y="22" width="1" height="1" fill="#000000"/>
<rect x="20" y="22" width="1" height="1" fill="#000000"/>
<rect x="21" y="22" width="1" height="1" fill="#000000"/>
<rect x="23" y="22" width="1" height="1" fill="#000000"/>
<rect x="25" y="22" width="1" height="1" fill="#000000"/>
<rect x="26" y="22" width="1" height="1" fill="#000000"/>
<rect x="31" y="22" width="1" height="1" fill="#000000"/>
<rect x="32" y="22" width="1" height="1" fill="#000000"/>
<rect x="0" y="23" width="1" height="1" fill="#000000"/>
<rect x="4" y="23" width="1" height="1" fill="#000000"/>
<rect x="8" y="23" width="1" height="1" fill="#000000"/>
<rect x="15" y="23" width="1" height="1" fill="#000000"/>
<rect x="16" y="23" width="1" height="1" fill="#000000"/>
<rect x="17" y="23" width="1" height="1" fill="#000000"/>
<rect x="18" y="23" width="1" height="1" fill="#000000"/>
<rect x="20" y="23" width="1" height="1" fill="#000000"/>
<rect x="22" y="23" width="1" height="1" fill="#000000"/>
<rect x="25" y="23" width="1" height="1" fill="#000000"/>
<rect x="30" y="23" width="1" height="1" fill="#000000"/>
<rect x="0" y="24" width="1" height="1" fill="#000000"/>
<rect x="1" y="24" width="1" height="1" fill="#000000"/>
<rect x="2" y="24" width="1" height="1" fill="#000000"/>
<rect x="3" y="24" width="1" height="1" fill="#000000"/>
<rect x="4" y="24" width="1" height="1" fill="#000000"/>
<rect x="5" y="24" width="1" height="1" fill="#000000"/>
<rect x="6" y="24" width="1" height="1" fill="#000000"/>
<rect x="7" y="24" width="1" height="1" fill="#000000"/>
<rect x="9" y="24" width="1" height="1" fill="#000000"/>
<rect x="12" y="24" width="1" height="1" fill="#000000"/>
<rect x="15" y="24" width="1" height="1" fill="#000000"/>
<rect x="19" y="24" width="1" height="1" fill="#000000"/>
<rect x="24" y="24" width="1" height="1" fill="#000000"/>
<rect x="25" y="24" width="1" height="1" fill="#000000"/>
<rect x="26" y="24" width="1" height="1" fill="#000000"/>
<rect x="27" y="24" width="1" height="1" fill="#000000"/>
<rect x="28" y="24" width="1" height="1" fill="#000000"/>
<rect x="31" y="24" width="1" height="1" fill="#000000"/>
<rect x="8" y="25" width="1" height="1" fill="#000000"/>
<rect x="9" y="25" width="1" height="1" fill="#000000"/>
<rect x="11" y="25" width="1" height="1" fill="#000000"/>
<rect x="14" y="25" width="1" height="1" fill="#000000"/>
<rect x="15" y="25" width="1" height="1" fill="#000000"/>
<rect x="16" y="25" width="1" height="1" fill="#000000"/>
<rect x="18" y="25" width="1" height="1" fill="#000000"/>
<rect x="22" y="25" width="1" height="1" fill="#000000"/>
<rect x="24" y="25" width="1" height="1" fill="#000000"/>
<rect x="28" y="25" width="1" height="1" fill="#000000"/>
<rect x="30" y="25" width="1" height="1" fill="#000000"/>
<rect x="31" y="25" width="1" height="1" fill="#000000"/>
<rect x="0" y="26" width="1" height="1" fill="#000000"/>
<rect x="1" y="26" width="1" height="1" fill="#000000"/>
<rect x="2" y="26" width="1" height="1" fill="#000000"/>
<rect x="3" y="26" width="1" height="1" fill="#000000"/>
<rect x="4" y="26" width="1" height="1" fill="#000000"/>
<rect x="5" y="26" width="1" height="1" fill="#000000"/>
<rect x="6" y="26" width="1" height="1" fill="#000000"/>
<rect x="9" y="26" width="1" height="1" fill="#000000"/>
<rect x="10" y="26" width="1" height="1" fill="#000000"/>
<rect x="12" y="26" width="1" height="1" fill="#000000"/>
<rect x="14" y="26" width="1" height="1" fill="#000000"/>
<rect x="15" y="26" width="1" height="1" fill="#000000"/>
<rect x="17" y="26" width="1" height="1" fill="#000000"/>
<rect x="18" y="26" width="1" height="1" fill="#000000"/>
<rect x="19" y="26" width="1" height="1" fill="#000000"/>
<rect x="20" y="26" width="1" height="1" fill="#000000"/>
<rect x="22" y="26" width="1" height="1" fill="#000000"/>
<rect x="23" y="26" width="1" height="1" fill="#000000"/>
<rect x="24" y="26" width="1" height="1" fill="#000000"/>
<rect x="26" y="26" width="1" height="1" fill="#000000"/>
<rect x="28" y="26" width="1" height="1" fill="#000000"/>
<rect x="30" y="26" width="1" height="1" fill="#000000"/>
<rect x="0" y="27" width="1" height="1" fill="#000000"/>
<rect x="6" y="27" width="1" height="1" fill="#000000"/>
<rect x="9" y="27" width="1" height="1" fill="#000000"/>
<rect x="16" y="27" width="1" height="1" fill="#000000"/>
<rect x="18" y="27" width="1" height="1" fill="#000000"/>
<rect x="23" y="27" width="1" height="1" fill="#000000"/>
<rect x="24" y="27" width="1" height="1" fill="#000000"/>
<rect x="28" y="27" width="1" height="1" fill="#000000"/>
<rect x="29" y="27" width="1" height="1" fill="#000000"/>
<rect x="30" y="27" width="1" height="1" fill="#000000"/>
<rect x="32" y="27" width="1" height="1" fill="#000000"/>
<rect x="0" y="28" width="1" height="1" fill="#000000"/>
<rect x="2" y="28" width="1" height="1" fill="#000000"/>
<rect x="3" y="28" width="1" height="1" fill="#000000"/>
<rect x="4" y="28" width="1" height="1" fill="#000000"/>
<rect x="6" y="28" width="1" height="1" fill="#000000"/>
<rect x="8" y="28" width="1" height="1" fill="#000000"/>
<rect x="10" y="28" width="1" height="1" fill="#000000"/>
<rect x="12" y="28" width="1" height="1" fill="#000000"/>
<rect x="13" y="28" width="1" height="1" fill="#000000"/>
<rect x="15" y="28" width="1" height="1" fill="#000000"/>
<rect x="20" y="28" width="1" height="1" fill="#000000"/>
<rect x="23" y="28" width="1" height="1" fill="#000000"/>
<rect x="24" y="28" width="1" height="1" fill="#000000"/>
<rect x="25" y="28" width="1" height="1" fill="#000000"/>
<rect x="26" y="28" width="1" height="1" fill="#000000"/>
<rect x="27" y="28" width="1" height="1" fill="#000000"/>
<rect x="28" y="28" width="1" height="1" fill="#000000"/>
<rect x="29" y="28" width="1" height="1" fill="#000000"/>
<rect x="31" y="28" width="1" height="1" fill="#000000"/>
<rect x="0" y="29" width="1" height="1" fill="#000000"/>
<rect x="2" y="29" width="1" height="1" fill="#000000"/>
<rect x="3" y="29" width="1" height="1" fill="#000000"/>
<rect x="4" y="29" width="1" height="1" fill="#000000"/>
<rect x="6" y="29" width="1" height="1" fill="#000000"/>
<rect x="8" y="29" width="1" height="1" fill="#000000"/>
<rect x="10" y="29" width="1" height="1" fill="#000000"/>
<rect x="13" y="29" width="1" height="1" fill="#000000"/>
<rect x="15" y="29" width="1" height="1" fill="#000000"/>
<rect x="18" y="29" width="1" height="1" fill="#000000"/>
<rect x="19" y="29" width="1" height="1" fill="#000000"/>
<rect x="20" y="29" width="1" height="1" fill="#000000"/>
<rect x="21" y="29" width="1" height="1" fill="#000000"/>
<rect x="22" y="29" width="1" height="1" fill="#000000"/>
<rect x="27" y="29" width="1" height="1" fill="#000000"/>
<rect x="0" y="30" width="1" height="1" fill="#000000"/>
<rect x="2" y="30" width="1" height="1" fill="#000000"/>
<rect x="3" y="30" width="1" height="1" fill="#000000"/>
<rect x="4" y="30" width="1" height="1" fill="#000000"/>
<rect x="6" y="30" width="1" height="1" fill="#000000"/>
<rect x="11" y="30" width="1" height="1" fill="#000000"/>
<rect x="12" y="30" width="1" height="1" fill="#000000"/>
<rect x="15" y="30" width="1" height="1" fill="#000000"/>
<rect x="16" y="30" width="1" height="1" fill="#000000"/>
<rect x="17" y="30" width="1" height="1" fill="#000000"/>
<rect x="18" y="30" width="1" height="1" fill="#000000"/>
<rect x="19" y="30" width="1" height="1" fill="#000000"/>
<rect x="20" y="30" width="1" height="1" fill="#000000"/>
<rect x="23" y="30" width="1" height="1" fill="#000000"/>
<rect x="24" y="30" width="1" height="1" fill="#000000"/>
<rect x="25" y="30" width="1" height="1" fill="#000000"/>
<rect x="28" y="30" width="1" height="1" fill="#000000"/>
<rect x="29" y="30" width="1" height="1" fill="#000000"/>
<rect x="30" y="30" width="1" height="1" fill="#000000"/>
<rect x="32" y="30" width="1" height="1" fill="#000000"/>
<rect x="0" y="31" width="1" height="1" fill="#000000"/>
<rect x="6" y="31" width="1" height="1" fill="#000000"/>
<rect x="8" y="31" width="1" height="1" fill="#000000"/>
<rect x="10" y="31" width="1" height="1" fill="#000000"/>
<rect x="11" y="31" width="1" height="1" fill="#000000"/>
<rect x="12" y="31" width="1" height="1" fill="#000000"/>
<rect x="13" y="31" width="1" height="1" fill="#000000"/>
<rect x="15" y="31" width="1" height="1" fill="#000000"/>
<rect x="16" y="31" width="1" height="1" fill="#000000"/>
<rect x="19" y="31" width="1" height="1" fill="#000000"/>
<rect x="22" y="31" width="1" height="1" fill="#000000"/>
<rect x="27" y="31" width="1" height="1" fill="#000000"/>
<rect x="29" y="31" width="1" height="1" fill="#000000"/>
<rect x="30" y="31" width="1" height="1" fill="#000000"/>
<rect x="31" y="31" width="1" height="1" fill="#000000"/>
<rect x="32" y="31" width="1" height="1" fill="#000000"/>
<rect x="0" y="32" width="1" height="1" fill="#000000"/>
<rect x="1" y="32" width="1" height="1" fill="#000000"/>
<rect x="2" y="32" width="1" height="1" fill="#000000"/>
<rect x="3" y="32" width="1" height="1" fill="#000000"/>
<rect x="4" y="32" width="1" height="1" fill="#000000"/>
<rect x="5" y="32" width="1" height="1" fill="#000000"/>
<rect x="6" y="32" width="1" height="1" fill="#000000"/>
<rect x="8" y="32" width="1" height="1" fill="#000000"/>
<rect x="9" y="32" width="1" height="1" fill="#000000"/>
<rect x="14" y="32" width="1" height="1" fill="#000000"/>
<rect x="16" y="32" width="1" height="1" fill="#000000"/>
<rect x="17" y="32" width="1" height="1" fill="#000000"/>
<rect x="19" y="32" width="1" height="1" fill="#000000"/>
<rect x="23" y="32" width="1" height="1" fill="#000000"/>
<rect x="24" y="32" width="1" height="1" fill="#000000"/>
<rect x="25" y="32" width="1" height="1" fill="#000000"/>
<rect x="26" y="32" width="1" height="1" fill="#000000"/>
<rect x="27" y="32" width="1" height="1" fill="#000000"/>
<rect x="28" y="32" width="1" height="1" fill="#000000"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -0,0 +1,56 @@
{% extends "layout.html" %}
{% block head %}
{{ super() }}
<meta http-equiv="refresh" content="600">
<meta name="google" content="notranslate">
{% endblock %}
{% block scripts %}
{{ super() }}
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
var source = new EventSource('{{ url_for('push')}}');
source.onmessage = function(event){
console.log("event:" + event.data);
try{
var data = JSON.parse(event.data);
$('#message').html(data.message);
led = $('#led');
led.attr('src', data.img);
} catch(e){console.log(e);}}
});
</script>
{% endblock %}
{% block content %}
<h1 class="text-center display-4">{{ welcome }}</h1>
<div class="row">
<div class="col">
<div class="text-center">
<div class="h2">{{ room }}</div>
<img id="led" src="">
<div class="h2" id="message"></div>
</div>
<hr>
<div>
<h4 class="text">Kontakt</h4>
0941 / 37801 - 740
<hr>
Keine App? Kein Problem! WiFi anschalten und https://lock.binary.kitchen besuchen.
</div>
</div>
<div class="col-6">
<a class="twitter-grid" data-partner="tweetdeck" href="https://twitter.com/binary_kitchen/timelines/1038927503446949888?ref_src=twsrc%5Etfw">gallery</a>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</div>
<div class="col text-center">
<img src="static/token.svg" height="300">
<img width=175 src='https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png'/>
<img width=175 src='https://fdroid.gitlab.io/artwork/badge/get-it-on.png' />
</div>
</div>
{{ super() }}
{% endblock %}

View File

@ -0,0 +1,39 @@
{% extends "layout.html" %}
{%- block metas %}
{{super()}}
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{%- endblock metas %}
{% block content %}
<h1 class="text-center">{{ banner }}</h1>
<div class="row">
<div class="col-md-3 text-center">
<img src="{{ led }}">
</div>
<div class="col-md-8">
<form action="" method="post" class="form" role="form">
{{ authentication_form.csrf_token }}
<div class="form-group ">
<label class="control-label" for="username">Username</label>
<input class="form-control" id="username" name="username" type="text" value="">
</div>
<div class="form-group required">
<label class="control-label" for="password">Password</label>
<input class="form-control" id="password" name="password" required type="password" value="">
</div>
<input class="btn btn-success btn-lg btn-block" id="open" name="open" type="submit" value="Open">
<input class="btn btn-warning btn-lg btn-block" id="present" name="present" type="submit" value="Present">
<input class="btn btn-danger btn-lg btn-block" id="close" name="close" type="submit" value="Close">
</form>
{% if response %}
<hr/>
<h1>{{ response }}</h1>
{% endif %}
<hr/>
Die Kitchen ist: {{ state_text }}
</div>
</div>
{{ super() }}
{% endblock %}

View File

@ -0,0 +1,17 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
{% block head %}{%- endblock head %}
<title>{{ title }}</title>
{% block metas %}{%- endblock metas %}
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
{% block content %}{%- endblock content %}
<div class="footer-copyright text-center py-3 small">
Ralf Ramsauer &copy; 2018 Binary Kitchen e.V.
</div>
{% block scripts %}{%- endblock scripts %}
</body>
</html>

View File

@ -1,13 +1,11 @@
[Unit]
Description=Binary Kitchen doorlockd service
After=network.target
After=network.target network-online.target
[Service]
User=root
Group=root
EnvironmentFile=-/etc/sysconfig/doorlockd
ExecStart=/usr/local/sbin/doorlockd
ExecStart=doorlockd
[Install]
WantedBy=multi-user.target

13
systemd/doorstate.service Normal file
View File

@ -0,0 +1,13 @@
[Unit]
Description=Binary Kitchen doorstate service
After=network.target network-online.target
[Service]
User=root
Group=root
ExecStart=doorstate
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target

View File

@ -1,171 +0,0 @@
<?php
function tellLock($pCommand, $pUser, $pPass, $pToken, $pIp){
$json = '{
"user":' . json_encode( $pUser ) . ',
"password":' . json_encode( $pPass ) . ',
"command":' . json_encode( $pCommand ) . ',
"token":' . json_encode( $pToken ) . ',
"ip":' . json_encode( $pIp ) . '
}'."\n";
$address = "127.0.0.1";
$port = "5555";
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($socket === false) {
echo "socket_create() failed: " . socket_strerror(socket_last_error()) . "\n";
}
$result = socket_connect($socket, $address, $port);
if ($result === false) {
echo "socket_connect() failed: ($result) " . socket_strerror(socket_last_error($socket)) . "\n";
}
socket_write($socket, $json, strlen($json));
$result = socket_read($socket, 1024);
socket_close($socket);
return $result;
}
$showLoginForm = false;
$showSuccess = false;
$showFailure = false;
$showTokenForm = false;
$isApi = false;
$pIp = $_SERVER[ 'REMOTE_ADDR' ];
if( $_SERVER[ 'REQUEST_METHOD' ] == "POST" ) {
if (array_key_exists("user", $_POST)
&& array_key_exists('pass', $_POST)
&& array_key_exists('token', $_POST)
&& array_key_exists('command', $_POST)
&& array_key_exists('api', $_POST))
{
$pUser = $_POST[ 'user' ];
$pPass = $_POST[ 'pass' ];
$pToken = $_POST[ 'token' ];
$pCommand = $_POST[ 'command' ];
$pApi = $_POST[ 'api' ];
if ($pApi == "true")
{
$isApi = true;
}
$jsonResponse = json_decode(tellLock($pCommand, $pUser, $pPass, $pToken, $pIp), true);
if ($jsonResponse == null || !isset($jsonResponse['message']) || !isset($jsonResponse['code'])) {
$showFailure = true;
$failureMsg = 'Error parsing JSON response';
} else {
$failureMsg = $jsonResponse['message'];
$code = $jsonResponse['code'];
$showSuccess = ($code == 0);
$showFailure = !$showSuccess;
}
} else {
$failureMsg = 'Invalid Request';
$showFailure = true;
}
} else {
// This is done by apache mod_rewrite
$pToken = $_GET['token'];
$lToken = $str= ltrim ($pToken, '/');
if(strlen($lToken) == 0) {
$showLoginForm = true;
$showTokenForm = true;
} else {
$showLoginForm = true;
}
}
if ($isApi == false) {
?>
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
</head>
<body>
<style>
* {
font: normal 30px Arial,sans-serif;
}
body {
background-color: #037;
color: white;
background-image: url('logo.svg' );
background-repeat: repeat;
background-size: 300%;
background-position: -200px -100px;
}
form {
position: relative;
display: block;
width: auto;
text-align: center;
}
input {
position: relative;
display: block;
width: auto;
width: 100%;
}
button {
width: 100%;
margin-top: 40px;
}
</style>
<?php if ($showLoginForm): ?>
<form name="login" method="post" action="/">
<?php if ($showTokenForm): ?>
<label for="token">Token</label>
<input type="text" name="token" value="">
<?php else: ?>
<input type="hidden" name="token" value="<?php echo $lToken;?>">
<?php endif; ?>
<input type="hidden" name="api" value="false">
<label for="user">User</label>
<input id="user" type="text" name="user">
<label for="pass">Pass</label>
<input id="pass" type="password" name="pass">
<button name="command" value="unlock">Open</button>
<hr/>
<button name="command" value="lock">Lock</button>
</form>
<?php elseif( $showSuccess ): ?>
<h1>Welcome Cpt. Cook</h1>
<?php elseif( $showFailure ): ?>
<h1>Something went wrong: <?php echo $failureMsg; ?></h1>
<?php endif; ?>
</body>
</html>
<?php
} else {
echo $code;
}
?>