├── .codegen ├── .models │ ├── acl-compare.json │ ├── acl-download.json │ ├── acl-grant.json │ ├── acl-revoke.json │ ├── acl-show.json │ ├── acl-upload.json │ ├── clear-time-profiles.json │ ├── commands.json │ ├── delete-card.json │ ├── delete-cards.json │ ├── get-all-devices.json │ ├── get-card.json │ ├── get-cards.json │ ├── get-device.json │ ├── get-door-control.json │ ├── get-door-delay.json │ ├── get-event.json │ ├── get-events.json │ ├── get-status.json │ ├── get-time-profile.json │ ├── get-time-profiles.json │ ├── get-time.json │ ├── open-door.json │ ├── put-card.json │ ├── record-special-events.json │ ├── restore-default-parameters.json │ ├── set-door-control.json │ ├── set-door-delay.json │ ├── set-door-interlock.json │ ├── set-door-keypads.json │ ├── set-door-passcodes.json │ ├── set-task-list.json │ ├── set-time-profile.json │ ├── set-time-profiles.json │ └── set-time.json └── markdown │ ├── .templates │ └── templates.md │ ├── README.md │ ├── SUMMARY.md │ ├── acl-compare.md │ ├── acl-download.md │ ├── acl-grant.md │ ├── acl-revoke.md │ ├── acl-show.md │ ├── acl-upload.md │ ├── clear-time-profiles.md │ ├── delete-card.md │ ├── delete-cards.md │ ├── get-card.md │ ├── get-cards.md │ ├── get-device.md │ ├── get-devices.md │ ├── get-door-control.md │ ├── get-door-delay.md │ ├── get-event.md │ ├── get-events.md │ ├── get-status.md │ ├── get-time-profile.md │ ├── get-time-profiles.md │ ├── get-time.md │ ├── open-door.md │ ├── put-card.md │ ├── record-special-events.md │ ├── restore-default-parameters.md │ ├── security.md │ ├── set-door-control.md │ ├── set-door-delay.md │ ├── set-door-interlock.md │ ├── set-door-keypads.md │ ├── set-door-passcodes.md │ ├── set-task-list.md │ ├── set-time-profile.md │ ├── set-time-profiles.md │ └── set-time.md ├── .github └── workflows │ ├── build.yml │ ├── ghcr.yml │ └── nightly.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── TODO.md ├── acl ├── acl.go ├── compare.go ├── download.go ├── grant.go ├── revoke.go ├── show.go ├── tar.go ├── tsv.go ├── upload.go └── zip.go ├── auth ├── auth.go ├── hmac.go ├── hotp.go ├── hotp_test.go ├── nonce.go ├── permissions.go └── rsa.go ├── cmd └── uhppoted-mqtt │ └── main.go ├── commands ├── command.go ├── daemonize.go ├── daemonize_darwin.go ├── daemonize_linux.go ├── daemonize_windows.go ├── run.go ├── run_darwin.go ├── run_linux.go ├── run_windows.go ├── sys_darwin.go ├── sys_linux.go ├── sys_windows.go ├── undaemonize_darwin.go ├── undaemonize_linux.go └── undaemonize_windows.go ├── common └── error.go ├── device ├── antipassback.go ├── cards.go ├── controller.go ├── device.go ├── doors.go ├── events.go ├── listen.go ├── status.go ├── tasklist.go ├── time.go └── time_profiles.go ├── doc.go ├── docker ├── compose.zip ├── compose │ ├── broker.pem │ ├── client.cert │ ├── client.key │ ├── compose.yml │ └── uhppoted.conf ├── dev │ ├── Dockerfile │ └── uhppoted.conf ├── doc │ ├── Dockerfile │ ├── README.md │ ├── broker.pem │ ├── secure │ │ ├── client.cert │ │ ├── client.key │ │ └── rsa │ │ │ ├── encryption │ │ │ └── mqttd.key │ │ │ └── signing │ │ │ └── mqttd.key │ ├── uhppoted-mqtt │ └── uhppoted.conf └── ghcr │ ├── Dockerfile │ └── uhppoted.conf ├── documentation ├── FAQ.md ├── TLS.md ├── TLS.sh ├── commands │ ├── README.md │ ├── SUMMARY.md │ ├── acl-compare.md │ ├── acl-download.md │ ├── acl-grant.md │ ├── acl-revoke.md │ ├── acl-show.md │ ├── acl-upload.md │ ├── clear-time-profiles.md │ ├── delete-card.md │ ├── delete-cards.md │ ├── get-antipassback.md │ ├── get-card.md │ ├── get-cards.md │ ├── get-device.md │ ├── get-devices.md │ ├── get-door-control.md │ ├── get-door-delay.md │ ├── get-event.md │ ├── get-events.md │ ├── get-status.md │ ├── get-time-profile.md │ ├── get-time-profiles.md │ ├── get-time.md │ ├── open-door.md │ ├── put-card.md │ ├── record-special-events.md │ ├── restore-default-parameters.md │ ├── security.md │ ├── set-antipassback.md │ ├── set-door-control.md │ ├── set-door-delay.md │ ├── set-door-interlock.md │ ├── set-door-keypads.md │ ├── set-door-passcodes.md │ ├── set-task-list.md │ ├── set-time-profile.md │ ├── set-time-profiles.md │ └── set-time.md ├── greengrass │ ├── .gitignore │ ├── CLI.md │ ├── IAM.md │ ├── README.md │ ├── aws-lambda-tar.py │ ├── discovery.md │ ├── provisioning.md │ ├── uhppoted-mqtt.md │ └── uhppoted-setup.sh └── signatures.md ├── go.mod ├── go.sum ├── log └── log.go └── mqtt ├── message.go ├── monitor.go ├── mqtt.go └── statistics.go /.codegen/.models/acl-compare.json: -------------------------------------------------------------------------------- 1 | { 2 | "acl_compare": { 3 | "command": "acl-compare", 4 | "request": { 5 | "topic": "acl/acl:compare", 6 | "fields": [ 7 | { 8 | "field": "acl", 9 | "value": "URL", 10 | "description": "Source URL for the ACL file" 11 | }, 12 | { 13 | "field": "report", 14 | "value": "URL", 15 | "description": "Destination URL for the report file" 16 | }, 17 | { 18 | "field": "mime-type", 19 | "value": "IANA mime-type", 20 | "description": "application/x-gzip for tar.gz files, application/zip for .zip files and text/tab-separated-values for TSV files" 21 | } 22 | ] 23 | }, 24 | "response": { 25 | "fields": [ 26 | { 27 | "field": "url", 28 | "value": "URL", 29 | "description": "URL for the uploaded report file" 30 | }, 31 | { 32 | "field": "report", 33 | "value": "list of record", 34 | "description": "list of the changes made to each controller" 35 | }, 36 | { 37 | "field": "report.controller", 38 | "value": "uint32", 39 | "description": "controller ID" 40 | }, 41 | { 42 | "field": "report.controller.diffent", 43 | "value": "uint32", 44 | "description": "number of cards on the controller that have the same card number but different permissions" 45 | }, 46 | { 47 | "field": "report.controller.extraneous", 48 | "value": "uint32", 49 | "description": "number of cards on the controller that are not in the ACL file" 50 | }, 51 | { 52 | "field": "report.controller.updated", 53 | "value": "uint32", 54 | "description": "number of cards updated on controller" 55 | }, 56 | { 57 | "field": "report.controller.unchanged", 58 | "value": "uint32", 59 | "description": "number of cards unchanged on controller" 60 | }, 61 | { 62 | "field": "report.controller.missing", 63 | "value": "uint32", 64 | "description": "number of card in the ACL file that are not present on the controller" 65 | }, 66 | { 67 | "field": "report.controller.unchanged", 68 | "value": "uint32", 69 | "description": "number of cards on the controller that match the ACL file" 70 | } 71 | ] 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.codegen/.models/acl-download.json: -------------------------------------------------------------------------------- 1 | { 2 | "acl_download": { 3 | "command": "acl-download", 4 | "request": { 5 | "topic": "acl/acl:download", 6 | "fields": [ 7 | { 8 | "field": "url", 9 | "value": "URL", 10 | "description": "Source URL for the ACL file" 11 | }, 12 | { 13 | "field": "mime-type", 14 | "value": "IANA mime-type", 15 | "description": "application/x-gzip for tar.gz files, application/zip for .zip files and text/tab-separated-values for TSV files" 16 | } 17 | ] 18 | }, 19 | "response": { 20 | "fields": [ 21 | { 22 | "field": "report", 23 | "value": "list of record", 24 | "description": "list of the changes made to each controller" 25 | }, 26 | { 27 | "field": "report.controller", 28 | "value": "uint32", 29 | "description": "controller ID" 30 | }, 31 | { 32 | "field": "report.controller.added", 33 | "value": "uint32", 34 | "description": "number of cards added to controller" 35 | }, 36 | { 37 | "field": "report.controller.deleted", 38 | "value": "uint32", 39 | "description": "number of cards deleted from controller" 40 | }, 41 | { 42 | "field": "report.controller.updated", 43 | "value": "uint32", 44 | "description": "number of cards updated on controller" 45 | }, 46 | { 47 | "field": "report.controller.unchanged", 48 | "value": "uint32", 49 | "description": "number of cards unchanged on controller" 50 | }, 51 | { 52 | "field": "report.controller.errors", 53 | "value": "uint32", 54 | "description": "number of errors for controller" 55 | }, 56 | { 57 | "field": "report.controller.failed", 58 | "value": "uint32", 59 | "description": "number of cards that could not be transferred to controller" 60 | }, 61 | { 62 | "field": "warnings", 63 | "value": "array of string", 64 | "description": "list of warning messages while transferring ACL to controller" 65 | } 66 | ] 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /.codegen/.models/acl-grant.json: -------------------------------------------------------------------------------- 1 | { 2 | "acl_grant": { 3 | "command": "acl-grant", 4 | "request": { 5 | "topic": "acl/card:grant", 6 | "fields": [ 7 | { 8 | "field": "card-number", 9 | "value": "uint32", 10 | "description": "card number" 11 | }, 12 | { 13 | "field": "start-date", 14 | "value": "date", 15 | "description": "card 'valid from' date (inclusive)" 16 | }, 17 | { 18 | "field": "end-date", 19 | "value": "date", 20 | "description": "card 'valid until' date (inclusive)" 21 | }, 22 | { 23 | "field": "doors", 24 | "value": "array of string", 25 | "description": "string list of door names" 26 | } 27 | ] 28 | }, 29 | "response": { 30 | "fields": [ 31 | { 32 | "field": "granted", 33 | "value": "bool", 34 | "description": "grant success/fail" 35 | } 36 | ] 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.codegen/.models/acl-revoke.json: -------------------------------------------------------------------------------- 1 | { 2 | "acl_revoke": { 3 | "command": "acl-revoke", 4 | "request": { 5 | "topic": "acl/card:revoke", 6 | "fields": [ 7 | { 8 | "field": "card-number", 9 | "value": "uint32", 10 | "description": "card number" 11 | }, 12 | { 13 | "field": "start-date", 14 | "value": "date", 15 | "description": "card 'valid from' date (inclusive)" 16 | }, 17 | { 18 | "field": "end-date", 19 | "value": "date", 20 | "description": "card 'valid until' date (inclusive)" 21 | }, 22 | { 23 | "field": "doors", 24 | "value": "array of string", 25 | "description": "string list of door names" 26 | } 27 | ] 28 | }, 29 | "response": { 30 | "fields": [ 31 | { 32 | "field": "revoked", 33 | "value": "bool", 34 | "description": "revoke success/fail" 35 | } 36 | ] 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.codegen/.models/acl-show.json: -------------------------------------------------------------------------------- 1 | { 2 | "acl_show": { 3 | "command": "acl-show", 4 | "request": { 5 | "topic": "acl/card:show", 6 | "fields": [ 7 | { 8 | "field": "card-number", 9 | "value": "uint32", 10 | "description": "(required) card number" 11 | } 12 | ] 13 | }, 14 | "response": { 15 | "fields": [ 16 | { 17 | "field": "card-number", 18 | "value": "uint32", 19 | "description": "card number" 20 | }, 21 | { 22 | "field": "deleted", 23 | "value": "bool", 24 | "description": "card delete success/fail" 25 | } 26 | ] 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.codegen/.models/acl-upload.json: -------------------------------------------------------------------------------- 1 | { 2 | "acl_upload": { 3 | "command": "acl-upload", 4 | "request": { 5 | "topic": "acl/acl:upload", 6 | "fields": [ 7 | { 8 | "field": "url", 9 | "value": "URL", 10 | "description": "Destination URL for the ACL file" 11 | } 12 | ] 13 | }, 14 | "response": { 15 | "fields": [ 16 | { 17 | "field": "url", 18 | "value": "URL", 19 | "description": "URL of uploaded file" 20 | } 21 | ] 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.codegen/.models/clear-time-profiles.json: -------------------------------------------------------------------------------- 1 | { 2 | "clear_time_profiles": { 3 | "command": "clear-time-profiles", 4 | "request": { 5 | "topic": "device/time-profiles:delete", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "uint32", 10 | "description": "(required) controller serial number" 11 | } 12 | ] 13 | }, 14 | "response": { 15 | "fields": [ 16 | { 17 | "field": "device-id", 18 | "value": "", 19 | "description": "controller serial number" 20 | }, 21 | { 22 | "field": "deleted", 23 | "value": "bool", 24 | "description": "clear time profiles success/fail" 25 | } 26 | ] 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.codegen/.models/commands.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands": [ 3 | { 4 | "command": "get-devices" 5 | }, 6 | { 7 | "command": "get-device" 8 | }, 9 | { 10 | "command": "get-time" 11 | }, 12 | { 13 | "command": "set-time" 14 | }, 15 | { 16 | "command": "restore-default-parameters" 17 | }, 18 | { 19 | "command": "get-door-delay" 20 | }, 21 | { 22 | "command": "set-door-delay" 23 | }, 24 | { 25 | "command": "get-door-control" 26 | }, 27 | { 28 | "command": "set-door-control" 29 | }, 30 | { 31 | "command": "set-door-interlock" 32 | }, 33 | { 34 | "command": "set-door-keypads" 35 | }, 36 | { 37 | "command": "record-special-events" 38 | }, 39 | { 40 | "command": "open-door" 41 | }, 42 | { 43 | "command": "get-status" 44 | }, 45 | { 46 | "command": "get-cards" 47 | }, 48 | { 49 | "command": "get-card" 50 | }, 51 | { 52 | "command": "put-card" 53 | }, 54 | { 55 | "command": "get-events" 56 | }, 57 | { 58 | "command": "get-event" 59 | }, 60 | { 61 | "command": "delete-card" 62 | }, 63 | { 64 | "command": "delete-cards" 65 | }, 66 | { 67 | "command": "get-time-profile" 68 | }, 69 | { 70 | "command": "set-time-profile" 71 | }, 72 | { 73 | "command": "clear-time-profiles" 74 | }, 75 | { 76 | "command": "get-time-profiles" 77 | }, 78 | { 79 | "command": "set-time-profiles" 80 | }, 81 | { 82 | "command": "set-task-list" 83 | }, 84 | { 85 | "command": "acl-show" 86 | }, 87 | { 88 | "command": "acl-grant" 89 | }, 90 | { 91 | "command": "acl-revoke" 92 | }, 93 | { 94 | "command": "acl-upload-file" 95 | }, 96 | { 97 | "command": "acl-upload-s3" 98 | }, 99 | { 100 | "command": "acl-upload-http" 101 | }, 102 | { 103 | "command": "acl-download-file" 104 | }, 105 | { 106 | "command": "acl-download-s3" 107 | }, 108 | { 109 | "command": "acl-download-http" 110 | }, 111 | { 112 | "command": "acl-compare-file" 113 | }, 114 | { 115 | "command": "acl-compare-s3" 116 | }, 117 | { 118 | "command": "acl-compare-http" 119 | } 120 | ] 121 | } 122 | -------------------------------------------------------------------------------- /.codegen/.models/delete-card.json: -------------------------------------------------------------------------------- 1 | { 2 | "delete_card": { 3 | "command": "delete-card", 4 | "request": { 5 | "topic": "device/card:delete", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "uint32", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "card-number", 14 | "value": "uint32", 15 | "description": "(required) card number" 16 | } 17 | ] 18 | }, 19 | "response": { 20 | "fields": [ 21 | { 22 | "field": "device-id", 23 | "value": "", 24 | "description": "controller serial number" 25 | }, 26 | { 27 | "field": "card-number", 28 | "value": "uint32", 29 | "description": "card number" 30 | }, 31 | { 32 | "field": "deleted", 33 | "value": "bool", 34 | "description": "card delete success/fail" 35 | } 36 | ] 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.codegen/.models/delete-cards.json: -------------------------------------------------------------------------------- 1 | { 2 | "delete_cards": { 3 | "command": "delete-cards", 4 | "request": { 5 | "topic": "device/cards:delete", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "uint32", 10 | "description": "(required) controller serial number" 11 | } 12 | ] 13 | }, 14 | "response": { 15 | "fields": [ 16 | { 17 | "field": "device-id", 18 | "value": "", 19 | "description": "controller serial number" 20 | }, 21 | { 22 | "field": "deleted", 23 | "value": "bool", 24 | "description": "delete all cards success/fail" 25 | } 26 | ] 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.codegen/.models/get-all-devices.json: -------------------------------------------------------------------------------- 1 | { 2 | "get_devices": { 3 | "command": "get-devices", 4 | "request": { 5 | "topic": "devices:get", 6 | "fields": [] 7 | }, 8 | "response": { 9 | "fields": [ 10 | { 11 | "field": "device-id", 12 | "value": "", 13 | "description": "controller serial number" 14 | }, 15 | { 16 | "field": "device-type", 17 | "value": "", 18 | "description": "controller type (UTO311-L0x)" 19 | }, 20 | { 21 | "field": "ip-address", 22 | "value": "
", 23 | "description": "controller IPv4 address" 24 | }, 25 | { 26 | "field": "port", 27 | "value": "", 28 | "description": "UDP port for controller commands" 29 | } 30 | ] 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.codegen/.models/get-card.json: -------------------------------------------------------------------------------- 1 | { 2 | "get_card": { 3 | "command": "get-card", 4 | "request": { 5 | "topic": "device/card:get", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "uint32", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "card-number", 14 | "value": "uint32", 15 | "description": "(required) card number" 16 | } 17 | ] 18 | }, 19 | "response": { 20 | "fields": [ 21 | { 22 | "field": "device-id", 23 | "value": "", 24 | "description": "controller serial number" 25 | }, 26 | { 27 | "field": "card", 28 | "value": "record", 29 | "description": "card record" 30 | }, 31 | { 32 | "field": "card-number", 33 | "value": "uint32", 34 | "description": "card number" 35 | }, 36 | { 37 | "field": "start-date", 38 | "value": "date", 39 | "description": "card 'valid from' date (inclusive)" 40 | }, 41 | { 42 | "field": "end-date", 43 | "value": "date", 44 | "description": "card 'valid until' date (inclusive)" 45 | }, 46 | { 47 | "field": "doors", 48 | "value": "{1:uint8, 2:uint8, 3:uint8, 4:uint8}", 49 | "description": "door [1..4] access rights" 50 | } 51 | ] 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.codegen/.models/get-cards.json: -------------------------------------------------------------------------------- 1 | { 2 | "get_cards": { 3 | "command": "get-cards", 4 | "request": { 5 | "topic": "device/cards:get", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "", 10 | "description": "(required) controller serial number" 11 | } 12 | ] 13 | }, 14 | "response": { 15 | "fields": [ 16 | { 17 | "field": "device-id", 18 | "value": "", 19 | "description": "controller serial number" 20 | }, 21 | { 22 | "field": "cards", 23 | "value": "[]uint32", 24 | "description": "list of card numbers" 25 | } 26 | ] 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.codegen/.models/get-device.json: -------------------------------------------------------------------------------- 1 | { 2 | "get_device": { 3 | "command": "get-device", 4 | "request": { 5 | "topic": "device:get", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "", 10 | "description": "(required) controller serial number" 11 | } 12 | ] 13 | }, 14 | "response": { 15 | "fields": [ 16 | { 17 | "field": "device-id", 18 | "value": "", 19 | "description": "controller serial number" 20 | }, 21 | { 22 | "field": "device-type", 23 | "value": "", 24 | "description": "controller type (UTO311-L0x)" 25 | }, 26 | { 27 | "field": "ip-address", 28 | "value": "
", 29 | "description": "controller IPv4 address" 30 | }, 31 | { 32 | "field": "port", 33 | "value": "", 34 | "description": "UDP port for controller commands" 35 | } 36 | ] 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.codegen/.models/get-door-control.json: -------------------------------------------------------------------------------- 1 | { 2 | "get_door_control": { 3 | "command": "get-door-control", 4 | "request": { 5 | "topic": "device/door/control:get", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "door", 14 | "value": "", 15 | "description": "(required) door (1..4)" 16 | } 17 | ] 18 | }, 19 | "response": { 20 | "fields": [ 21 | { 22 | "field": "device-id", 23 | "value": "", 24 | "description": "controller serial number" 25 | }, 26 | { 27 | "field": "door", 28 | "value": "", 29 | "description": "door (1..4) from the request" 30 | }, 31 | { 32 | "field": "control", 33 | "value": "", 34 | "description": "door control mode (normally open, normally closed or controlled)" 35 | } 36 | ] 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.codegen/.models/get-door-delay.json: -------------------------------------------------------------------------------- 1 | { 2 | "get_door_delay": { 3 | "command": "get-door-delay", 4 | "request": { 5 | "topic": "device/door/delay:get", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "door", 14 | "value": "", 15 | "description": "(required) door (1..4)" 16 | } 17 | ] 18 | }, 19 | "response": { 20 | "fields": [ 21 | { 22 | "field": "device-id", 23 | "value": "", 24 | "description": "controller serial number" 25 | }, 26 | { 27 | "field": "door", 28 | "value": "", 29 | "description": "door (1..4) from the request" 30 | }, 31 | { 32 | "field": "delay", 33 | "value": "", 34 | "description": "door open delay" 35 | } 36 | ] 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.codegen/.models/get-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "get_event": { 3 | "command": "get-event", 4 | "request": { 5 | "topic": "device/event:get", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "uint32", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "event-index", 14 | "value": "uint32", 15 | "description": "(required) index of event to fetch" 16 | } 17 | ] 18 | }, 19 | "response": { 20 | "fields": [ 21 | { 22 | "field": "device-id", 23 | "value": "uint32", 24 | "description": "controller serial number" 25 | }, 26 | { 27 | "field": "event", 28 | "value": "event record", 29 | "description": "event details" 30 | }, 31 | { 32 | "field": "event.timestamp", 33 | "value": "datetime", 34 | "description": "last event date/time" 35 | }, 36 | { 37 | "field": "event.device-id", 38 | "value": "uint32", 39 | "description": "last event controller serial number" 40 | }, 41 | { 42 | "field": "event-id", 43 | "value": "uint32", 44 | "description": "last event index" 45 | }, 46 | { 47 | "field": "event.type", 48 | "value": "uint8", 49 | "description": "last event type code" 50 | }, 51 | { 52 | "field": "event.type-text", 53 | "value": "string", 54 | "description": "last event type" 55 | }, 56 | { 57 | "field": "event.door-id", 58 | "value": "uint8", 59 | "description": "last event door" 60 | }, 61 | { 62 | "field": "event.card-number", 63 | "value": "uint32", 64 | "description": "last event card number" 65 | }, 66 | { 67 | "field": "event.access-granted", 68 | "value": "bool", 69 | "description": "last event access granted" 70 | }, 71 | { 72 | "field": "event.direction", 73 | "value": "uint8", 74 | "description": "last event direction (1:IN, 2:OUT)" 75 | }, 76 | { 77 | "field": "event.direction-text", 78 | "value": "string", 79 | "description": "last event direction ('in', 'out')" 80 | }, 81 | { 82 | "field": "event.reason", 83 | "value": "uint8", 84 | "description": "last event reason code" 85 | }, 86 | { 87 | "field": "event.reason-text", 88 | "value": "string", 89 | "description": "last event reason" 90 | } 91 | ] 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /.codegen/.models/get-time-profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "get_time_profile": { 3 | "command": "get-time-profile", 4 | "request": { 5 | "topic": "device/time-profile:get", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "uint32", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "profile-id", 14 | "value": "uint8", 15 | "description": "(required) time profile ID [2..254]" 16 | } 17 | ] 18 | }, 19 | "response": { 20 | "fields": [ 21 | { 22 | "field": "device-id", 23 | "value": "uint32", 24 | "description": "controller serial number" 25 | }, 26 | { 27 | "field": "time-profile", 28 | "value": "record", 29 | "description": "time profile record" 30 | }, 31 | { 32 | "field": "id", 33 | "value": "uint8", 34 | "description": "time profile ID [2..254" 35 | }, 36 | { 37 | "field": "start-date", 38 | "value": "date", 39 | "description": "time profile 'enabled from' date (inclusive)" 40 | }, 41 | { 42 | "field": "end-date", 43 | "value": "date", 44 | "description": "time profile 'enabled until' date (inclusive)" 45 | }, 46 | { 47 | "field": "weekdays", 48 | "value": "string list of weekday", 49 | "description": "weekdays on which time profile is enabled" 50 | }, 51 | { 52 | "field": "segments", 53 | "value": "array of time segments", 54 | "description": "time segments 1-3" 55 | }, 56 | { 57 | "field": "segment.start", 58 | "value": "time", 59 | "description": "segment start time (HHmm)" 60 | }, 61 | { 62 | "field": "segment.end", 63 | "value": "time", 64 | "description": "segment end time (HHmm)" 65 | }, 66 | { 67 | "field": "linked-profile-id", 68 | "value": "uint8", 69 | "description": "(optional) ID of linked time profile [2..254]" 70 | } 71 | ] 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.codegen/.models/get-time-profiles.json: -------------------------------------------------------------------------------- 1 | { 2 | "get_time_profiles": { 3 | "command": "get-time-profiles", 4 | "request": { 5 | "topic": "device/time-profiles:get", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "uint32", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "from", 14 | "value": "uint8", 15 | "description": "(optional) start time profile ID [2..254]. Defaults to 2." 16 | }, 17 | { 18 | "field": "to", 19 | "value": "uint8", 20 | "description": "(optional) end time profile ID [2..254]. Defaults to 254." 21 | } 22 | ] 23 | }, 24 | "response": { 25 | "fields": [ 26 | { 27 | "field": "device-id", 28 | "value": "uint32", 29 | "description": "controller serial number" 30 | }, 31 | { 32 | "field": "profiles", 33 | "value": "array of record", 34 | "description": "array of time profile records" 35 | }, 36 | { 37 | "field": "profile.id", 38 | "value": "uint8", 39 | "description": "time profile ID [2..254" 40 | }, 41 | { 42 | "field": "profile.start-date", 43 | "value": "date", 44 | "description": "time profile 'enabled from' date (inclusive)" 45 | }, 46 | { 47 | "field": "profile.end-date", 48 | "value": "date", 49 | "description": "time profile 'enabled until' date (inclusive)" 50 | }, 51 | { 52 | "field": "profile.weekdays", 53 | "value": "string list of weekday", 54 | "description": "weekdays on which time profile is enabled" 55 | }, 56 | { 57 | "field": "profile.segments", 58 | "value": "array of time segments", 59 | "description": "time segments 1-3" 60 | }, 61 | { 62 | "field": "profile.segment.start", 63 | "value": "time", 64 | "description": "segment start time (HHmm)" 65 | }, 66 | { 67 | "field": "profile.segment.end", 68 | "value": "time", 69 | "description": "segment end time (HHmm)" 70 | }, 71 | { 72 | "field": "profile.linked-profile-id", 73 | "value": "uint8", 74 | "description": "(optional) ID of linked time profile [2..254]" 75 | } 76 | ] 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /.codegen/.models/get-time.json: -------------------------------------------------------------------------------- 1 | { 2 | "get_time": { 3 | "command": "get-time", 4 | "request": { 5 | "topic": "device/time:get", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "", 10 | "description": "(required) controller serial number" 11 | } 12 | ] 13 | }, 14 | "response": { 15 | "fields": [ 16 | { 17 | "field": "device-id", 18 | "value": "", 19 | "description": "controller serial number" 20 | }, 21 | { 22 | "field": "date-time", 23 | "value": "", 24 | "description": "controller system date and time" 25 | } 26 | ] 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.codegen/.models/open-door.json: -------------------------------------------------------------------------------- 1 | { 2 | "open_door": { 3 | "command": "open-door", 4 | "request": { 5 | "topic": "device/door/lock:open", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "door", 14 | "value": "", 15 | "description": "(required) door (1..4) to open" 16 | }, 17 | { 18 | "field": "card-number", 19 | "value": "", 20 | "description": "(required) card number used to validate access" 21 | } 22 | ] 23 | }, 24 | "response": { 25 | "fields": [ 26 | { 27 | "field": "device-id", 28 | "value": "", 29 | "description": "controller serial number" 30 | }, 31 | { 32 | "field": "door", 33 | "value": "", 34 | "description": "door (1..4) from the request" 35 | }, 36 | { 37 | "field": "opened", 38 | "value": "", 39 | "description": "true if opened, false otherwise" 40 | } 41 | ] 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.codegen/.models/put-card.json: -------------------------------------------------------------------------------- 1 | { 2 | "put_card": { 3 | "command": "put-card", 4 | "request": { 5 | "topic": "device/card:put", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "uint32", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "card", 14 | "value": "record", 15 | "description": "card record" 16 | }, 17 | { 18 | "field": "card-number", 19 | "value": "uint32", 20 | "description": "card number" 21 | }, 22 | { 23 | "field": "start-date", 24 | "value": "date", 25 | "description": "card 'valid from' date (inclusive)" 26 | }, 27 | { 28 | "field": "end-date", 29 | "value": "date", 30 | "description": "card 'valid until' date (inclusive)" 31 | }, 32 | { 33 | "field": "doors", 34 | "value": "{1:uint8, 2:uint8, 3:uint8, 4:uint8}", 35 | "description": "door [1..4] access rights" 36 | } 37 | ] 38 | }, 39 | "response": { 40 | "fields": [ 41 | { 42 | "field": "device-id", 43 | "value": "", 44 | "description": "controller serial number" 45 | }, 46 | { 47 | "field": "card", 48 | "value": "record", 49 | "description": "card record" 50 | }, 51 | { 52 | "field": "card-number", 53 | "value": "uint32", 54 | "description": "card number" 55 | }, 56 | { 57 | "field": "start-date", 58 | "value": "date", 59 | "description": "card 'valid from' date (inclusive)" 60 | }, 61 | { 62 | "field": "end-date", 63 | "value": "date", 64 | "description": "card 'valid until' date (inclusive)" 65 | }, 66 | { 67 | "field": "doors", 68 | "value": "{1:uint8, 2:uint8, 3:uint8, 4:uint8}", 69 | "description": "door [1..4] access rights" 70 | } 71 | ] 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.codegen/.models/record-special-events.json: -------------------------------------------------------------------------------- 1 | { 2 | "record_special_events": { 3 | "command": "record-special-events", 4 | "request": { 5 | "topic": "device/special-events:set", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "enabled", 14 | "value": "", 15 | "description": "(required) true/false" 16 | } 17 | ] 18 | }, 19 | "response": { 20 | "fields": [ 21 | { 22 | "field": "device-id", 23 | "value": "", 24 | "description": "controller serial number" 25 | }, 26 | { 27 | "field": "door", 28 | "value": "", 29 | "description": "door (1..4) from the request" 30 | }, 31 | { 32 | "field": "control", 33 | "value": "", 34 | "description": "door control mode (normally open, normally closed or controlled)" 35 | } 36 | ] 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.codegen/.models/restore-default-parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "restore_default_parameters": { 3 | "command": "restore-default-parameters", 4 | "request": { 5 | "topic": "device:reset", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "uint32", 10 | "description": "(required) controller serial number" 11 | } 12 | ] 13 | }, 14 | "response": { 15 | "fields": [ 16 | { 17 | "field": "device-id", 18 | "value": "", 19 | "description": "controller serial number" 20 | }, 21 | { 22 | "field": "reset", 23 | "value": "bool", 24 | "description": "reset controller success/fail" 25 | } 26 | ] 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.codegen/.models/set-door-control.json: -------------------------------------------------------------------------------- 1 | { 2 | "set_door_control": { 3 | "command": "set-door-control", 4 | "request": { 5 | "topic": "device/door/control:set", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "door", 14 | "value": "", 15 | "description": "(required) door (1..4)" 16 | }, 17 | { 18 | "field": "control", 19 | "value": "", 20 | "description": "door control mode (normally open, normally closed or controlled)" 21 | } 22 | ] 23 | }, 24 | "response": { 25 | "fields": [ 26 | { 27 | "field": "device-id", 28 | "value": "", 29 | "description": "controller serial number" 30 | }, 31 | { 32 | "field": "door", 33 | "value": "", 34 | "description": "door (1..4) from the request" 35 | }, 36 | { 37 | "field": "control", 38 | "value": "", 39 | "description": "door control mode (normally open, normally closed or controlled)" 40 | } 41 | ] 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.codegen/.models/set-door-delay.json: -------------------------------------------------------------------------------- 1 | { 2 | "set_door_delay": { 3 | "command": "set-door-delay", 4 | "request": { 5 | "topic": "device/door/delay:set", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "door", 14 | "value": "", 15 | "description": "(required) door (1..4) to open" 16 | }, 17 | { 18 | "field": "delay", 19 | "value": "", 20 | "description": "door open delay" 21 | } 22 | ] 23 | }, 24 | "response": { 25 | "fields": [ 26 | { 27 | "field": "device-id", 28 | "value": "", 29 | "description": "controller serial number" 30 | }, 31 | { 32 | "field": "door", 33 | "value": "", 34 | "description": "door (1..4) from the request" 35 | }, 36 | { 37 | "field": "delay", 38 | "value": "", 39 | "description": "door open duration" 40 | } 41 | ] 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.codegen/.models/set-door-interlock.json: -------------------------------------------------------------------------------- 1 | { 2 | "set_door_interlock": { 3 | "command": "set-door-interlock", 4 | "request": { 5 | "topic": "device/door/interlock:set", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "interlock", 14 | "value": "", 15 | "description": "door interlock mode (0: none, 1:doors 1&2, 2:doors 3&4, 3:doors 1&2 and doors 3&4, 4:doors 1&2&3, 8:doors 1&2&3&4" 16 | } 17 | ] 18 | }, 19 | "response": { 20 | "fields": [ 21 | { 22 | "field": "device-id", 23 | "value": "", 24 | "description": "controller serial number" 25 | }, 26 | { 27 | "field": "interlock", 28 | "value": "", 29 | "description": "door interlock mode (from request)" 30 | } 31 | ] 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.codegen/.models/set-door-keypads.json: -------------------------------------------------------------------------------- 1 | { 2 | "set_door_keypads": { 3 | "command": "activate-keypads", 4 | "request": { 5 | "topic": "device/door/keypads:set", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "keypads", 14 | "value": "map[uint8]bool", 15 | "description": "map of activated readers (unlisted readers are deactivated)" 16 | } 17 | ] 18 | }, 19 | "response": { 20 | "fields": [ 21 | { 22 | "field": "device-id", 23 | "value": "", 24 | "description": "controller serial number" 25 | }, 26 | { 27 | "field": "keypads", 28 | "value": "map[uint8]bool", 29 | "description": "map of readers to activated status" 30 | } 31 | ] 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.codegen/.models/set-door-passcodes.json: -------------------------------------------------------------------------------- 1 | { 2 | "set_door_passcodes": { 3 | "command": "set-door-passcodes", 4 | "request": { 5 | "topic": "device/door/passcodes:set", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "door", 14 | "value": "", 15 | "description": "(required) door (1..4)" 16 | }, 17 | { 18 | "field": "passcodes", 19 | "value": "", 20 | "description": "array of passcodes ([1..999999]). Only the first four entries are used." 21 | } 22 | ] 23 | }, 24 | "response": { 25 | "fields": [ 26 | { 27 | "field": "device-id", 28 | "value": "", 29 | "description": "controller serial number" 30 | }, 31 | { 32 | "field": "door", 33 | "value": "", 34 | "description": "door (1..4) from the request" 35 | }, 36 | { 37 | "field": "passcodes", 38 | "value": "", 39 | "description": "passcodes assigned to door" 40 | } 41 | ] 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.codegen/.models/set-task-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "set_task_list": { 3 | "command": "set-task-list", 4 | "request": { 5 | "topic": "device/tasklist:set", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "uint32", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "tasks", 14 | "value": "array of task", 15 | "description": "list of task records" 16 | }, 17 | { 18 | "field": "task.task", 19 | "value": "string", 20 | "description": "task type" 21 | }, 22 | { 23 | "field": "task.door", 24 | "value": "uint8", 25 | "description": "door for task" 26 | }, 27 | { 28 | "field": "task.start-date", 29 | "value": "date", 30 | "description": "date from which task is enabled (inclusive)" 31 | }, 32 | { 33 | "field": "task.end-date", 34 | "value": "date", 35 | "description": "date until which task is enabled (inclusive)" 36 | }, 37 | { 38 | "field": "task.weekdays", 39 | "value": "string list of weekday", 40 | "description": "weekdays on which time profile is enabled" 41 | }, 42 | { 43 | "field": "task.start", 44 | "value": "time", 45 | "description": "task start time (HHmm)" 46 | } 47 | ] 48 | }, 49 | "response": { 50 | "fields": [ 51 | { 52 | "field": "device-id", 53 | "value": "uint32", 54 | "description": "controller serial number" 55 | }, 56 | { 57 | "field": "warnings", 58 | "value": "array of string", 59 | "description": "list of warning messages" 60 | } 61 | ] 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.codegen/.models/set-time-profiles.json: -------------------------------------------------------------------------------- 1 | { 2 | "set_time_profiles": { 3 | "command": "set-time-profiles", 4 | "request": { 5 | "topic": "device/time-profiles:set", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "uint32", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "profiles", 14 | "value": "array of record", 15 | "description": "array of time profile record" 16 | }, 17 | { 18 | "field": "profile.id", 19 | "value": "uint8", 20 | "description": "time profile ID [2..254" 21 | }, 22 | { 23 | "field": "profile.start-date", 24 | "value": "date", 25 | "description": "time profile 'enabled from' date (inclusive)" 26 | }, 27 | { 28 | "field": "profile.end-date", 29 | "value": "date", 30 | "description": "time profile 'enabled until' date (inclusive)" 31 | }, 32 | { 33 | "field": "profile.weekdays", 34 | "value": "string list of weekday", 35 | "description": "weekdays on which time profile is enabled" 36 | }, 37 | { 38 | "field": "profile.segments", 39 | "value": "array of time segments", 40 | "description": "time segments 1-3" 41 | }, 42 | { 43 | "field": "profile.segment.start", 44 | "value": "time", 45 | "description": "segment start time (HHmm)" 46 | }, 47 | { 48 | "field": "profile.segment.end", 49 | "value": "time", 50 | "description": "segment end time (HHmm)" 51 | }, 52 | { 53 | "field": "profile.linked-profile-id", 54 | "value": "uint8", 55 | "description": "(optional) ID of linked time profile [2..254]" 56 | } 57 | ] 58 | }, 59 | "response": { 60 | "fields": [ 61 | { 62 | "field": "device-id", 63 | "value": "uint32", 64 | "description": "controller serial number" 65 | }, 66 | { 67 | "field": "warnings", 68 | "value": "array of string", 69 | "description": "list of warning messages" 70 | } 71 | ] 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.codegen/.models/set-time.json: -------------------------------------------------------------------------------- 1 | { 2 | "set_time": { 3 | "command": "set-time", 4 | "request": { 5 | "topic": "device/time:set", 6 | "fields": [ 7 | { 8 | "field": "device-id", 9 | "value": "", 10 | "description": "(required) controller serial number" 11 | }, 12 | { 13 | "field": "date-time", 14 | "value": "", 15 | "description": "(required) date and time to set (YYYY-mm-dd HH:mm:ss)" 16 | } 17 | ] 18 | }, 19 | "response": { 20 | "fields": [ 21 | { 22 | "field": "device-id", 23 | "value": "", 24 | "description": "controller serial number" 25 | }, 26 | { 27 | "field": "date-time", 28 | "value": "", 29 | "description": "controller system date and time (YYYY-mm-dd HH:mm:ss)" 30 | } 31 | ] 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.codegen/markdown/.templates/templates.md: -------------------------------------------------------------------------------- 1 | {{define "request"}} 2 | ``` 3 | Request: 4 | 5 | topic: //{{ .request.topic }} 6 | 7 | message: 8 | { 9 | "message": { 10 | "request": { 11 | "request-id": "", 12 | "client-id": "", 13 | "reply-to": "", 14 | {{- range .request.fields}} 15 | "{{.field}}": "{{.value}}", 16 | {{- end}} 17 | } 18 | } 19 | } 20 | 21 | request-id (optional) message ID, returned in the response 22 | client-id (required) client ID for authentication and authorisation (if enabled) 23 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 24 | configured reply topic) if not provided. 25 | {{- range .request.fields}} 26 | {{printf "%-12s" .field}} {{.description}} 27 | {{- end}} 28 | ``` 29 | {{end}} 30 | 31 | 32 | {{define "request-preamble"}} 33 | "client-id": "QWERTY", 34 | "request-id": "AH173635G3", 35 | "reply-to": "uhppoted/reply/97531", 36 | {{- end}} 37 | 38 | 39 | {{define "response"}} 40 | ``` 41 | Response: 42 | { 43 | "message": { 44 | "reply": { 45 | "request-id": , 46 | "client-id": , 47 | "method": "{{.command}}", 48 | "response": { 49 | {{- range .response.fields}} 50 | "{{.field}}": "{{.value}}", 51 | {{- end}} 52 | }, 53 | ... 54 | } 55 | }, 56 | ... 57 | } 58 | 59 | request-id message ID from the request 60 | client-id client ID from the request 61 | {{- range .response.fields}} 62 | {{printf "%-12s" .field}} {{.description}} 63 | {{- end}} 64 | ``` 65 | {{end}} 66 | 67 | {{define "response-preamble"}} 68 | "server-id": "uhppoted" 69 | "client-id": "QWERTY", 70 | "request-id": "AH173635G3", 71 | {{- end}} 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /.codegen/markdown/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Overview](README.md) 4 | 5 | [Security](security.md) 6 | 7 | # Reference Guide 8 | 9 | {{ range .commands}} 10 | - [`{{.command}}`]({{.command}}.md) 11 | {{end}} 12 | 13 | -------------------------------------------------------------------------------- /.codegen/markdown/acl-compare.md: -------------------------------------------------------------------------------- 1 | {{- with .acl_compare -}} 2 | ### `{{.command}}` 3 | 4 | Compares the access control permissions on a set of controllers with a file retrieved from a URL. The URL 5 | can be any URL that will return a file, including: 6 | 7 | - `file://` (e.g. _file:///var/uhppoted/ACL.tar.gz_) 8 | - `http://` (e.g. _http://localhost:8080/ACL.tar.gz_) 9 | - `https://` (e.g. _https://localhost:8080/ACL.tar.gz_) 10 | - `s3:///` (e.g. _s3://uhppoted/ACL.tar.gz_) 11 | 12 | Downloads from Amazon S3 expect the credentials and bucket information to be configured in _uhppoted.conf_: 13 | ``` 14 | aws.credentials = /etc/uhppoted/aws.credentials 15 | ; aws.profile = default 16 | ; aws.region = us-east-1 17 | ``` 18 | 19 | _aws.credentials_: 20 | ``` 21 | [default] 22 | aws_access_key_id = AKIAQMNWIVKBYA57IRWH 23 | aws_access_key_id = AKIZ.............QYV 24 | aws_secret_access_key = FRE................................zuyqt 25 | 26 | ``` 27 | 28 | {{template "request" . -}} 29 | {{template "response" . }} 30 | 31 | Example: 32 | ``` 33 | topic: uhppoted/gateway/requests/{{ .request.topic }} 34 | 35 | { 36 | "message": { 37 | "request": { 38 | {{- template "request-preamble"}} 39 | "url": { 40 | "acl": "file://../runtime/mqttd/QWERTY.tar.gz", 41 | "report": "file://../runtime/mqttd/report.tar.gz" 42 | }, 43 | "mime-type": "application/x-gzip" 44 | } 45 | } 46 | } 47 | 48 | { 49 | "message": { 50 | "reply": { 51 | {{- template "response-preamble"}} 52 | "method": "acl:compare", 53 | "response": { 54 | "url": "file:///var/uhppoted/report.tar.gz" 55 | "report": { 56 | "201020304": { 57 | "different": 0, 58 | "extraneous": 0, 59 | "missing": 0, 60 | "unchanged": 3 61 | }, 62 | "303986753": { 63 | "different": 0, 64 | "extraneous": 0, 65 | "missing": 2, 66 | "unchanged": 1 67 | }, 68 | "405419896": { 69 | "different": 0, 70 | "extraneous": 0, 71 | "missing": 0, 72 | "unchanged": 3 73 | } 74 | } 75 | } 76 | } 77 | } 78 | } 79 | ``` 80 | {{end -}} 81 | -------------------------------------------------------------------------------- /.codegen/markdown/acl-download.md: -------------------------------------------------------------------------------- 1 | {{- with .acl_download -}} 2 | ### `{{.command}}` 3 | 4 | Updates the access control permissions on a set of controllers from a file retrieved from a URL. The URL 5 | can be any URL that will return a file, including: 6 | 7 | - `file://` (e.g. _file:///var/uhppoted/ACL.tar.gz_) 8 | - `http://` (e.g. _http://localhost:8080/ACL.tar.gz_) 9 | - `https://` (e.g. _https://localhost:8080/ACL.tar.gz_) 10 | - `s3:///` (e.g. _s3://uhppoted/ACL.tar.gz_) 11 | 12 | Downloads from Amazon S3 expect the credentials and bucket information to be configured in _uhppoted.conf_: 13 | ``` 14 | aws.credentials = /etc/uhppoted/aws.credentials 15 | ; aws.profile = default 16 | ; aws.region = us-east-1 17 | ``` 18 | 19 | _aws.credentials_: 20 | ``` 21 | [default] 22 | aws_access_key_id = AKIAQMNWIVKBYA57IRWH 23 | aws_access_key_id = AKIZ.............QYV 24 | aws_secret_access_key = FRE................................zuyqt 25 | 26 | ``` 27 | 28 | {{template "request" . -}} 29 | {{template "response" . }} 30 | 31 | Example: 32 | ``` 33 | topic: uhppoted/gateway/requests/{{ .request.topic }} 34 | 35 | { 36 | "message": { 37 | "request": { 38 | {{- template "request-preamble"}} 39 | "request": { 40 | "url": "file:///var/uhppoted/ACL.tar.gz", 41 | "mime-type": "application/x-gzip" 42 | } 43 | } 44 | } 45 | 46 | { 47 | "message": { 48 | "reply": { 49 | {{- template "response-preamble"}} 50 | "method": "acl:download", 51 | "response": { 52 | "report": { 53 | "201020304": { 54 | "added": 3, 55 | "deleted": 0, 56 | "errors": 0, 57 | "failed": 0, 58 | "unchanged": 0, 59 | "updated": 0 60 | }, 61 | "303986753": { 62 | "added": 1, 63 | "deleted": 0, 64 | "errors": 2, 65 | "failed": 0, 66 | "unchanged": 0, 67 | "updated": 0 68 | }, 69 | "405419896": { 70 | "added": 0, 71 | "deleted": 0, 72 | "errors": 0, 73 | "failed": 0, 74 | "unchanged": 0, 75 | "updated": 3 76 | } 77 | }, 78 | "warnings": [ 79 | "303986753: Time profile 29 is not defined for 303986753" 80 | ] 81 | } 82 | } 83 | } 84 | } 85 | ``` 86 | {{end -}} 87 | -------------------------------------------------------------------------------- /.codegen/markdown/acl-grant.md: -------------------------------------------------------------------------------- 1 | {{- with .acl_grant -}} 2 | ### `{{.command}}` 3 | 4 | Grants system-wide access control permissions for a card. The permissions are 5 | added to any existing permissions. 6 | 7 | {{template "request" . -}} 8 | {{template "response" . }} 9 | 10 | Example: 11 | ``` 12 | topic: uhppoted/gateway/requests/{{ .request.topic }} 13 | 14 | { 15 | "message": { 16 | "request": { 17 | {{- template "request-preamble"}} 18 | "request": { 19 | "card-number": 8165538, 20 | "start-date": "2022-01-01", 21 | "end-date": "2022-12-31", 22 | "doors": [ 23 | "Gryffindor", 24 | "Slytherin" 25 | ] 26 | } 27 | } 28 | } 29 | 30 | { 31 | "message": { 32 | "reply": { 33 | {{- template "response-preamble"}} 34 | "method": "acl:grant", 35 | "response": { 36 | "granted": true 37 | } 38 | } 39 | } 40 | } 41 | ``` 42 | {{end -}} 43 | -------------------------------------------------------------------------------- /.codegen/markdown/acl-revoke.md: -------------------------------------------------------------------------------- 1 | {{- with .acl_revoke -}} 2 | ### `{{.command}}` 3 | 4 | Revokes system-wide access control permissions for a card. The permissions are 5 | removed from any existing permissions. 6 | 7 | {{template "request" . -}} 8 | {{template "response" . }} 9 | 10 | Example: 11 | ``` 12 | topic: uhppoted/gateway/requests/{{ .request.topic }} 13 | 14 | { 15 | "message": { 16 | "request": { 17 | {{- template "request-preamble"}} 18 | "request": { 19 | "card-number": 8165538, 20 | "start-date": "2022-01-01", 21 | "end-date": "2022-12-31", 22 | "doors": [ 23 | "Dungeon" 24 | ] 25 | } 26 | } 27 | } 28 | 29 | { 30 | "message": { 31 | "reply": { 32 | {{- template "response-preamble"}} 33 | "method": "acl:grant", 34 | "response": { 35 | "revoked": true 36 | } 37 | } 38 | } 39 | } 40 | ``` 41 | {{end}} -------------------------------------------------------------------------------- /.codegen/markdown/acl-show.md: -------------------------------------------------------------------------------- 1 | {{- with .acl_show -}} 2 | ### `{{.command}}` 3 | 4 | Retrieves the system-wide access control permissions for a card. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "card-number": 8165538 18 | } 19 | } 20 | } 21 | 22 | { 23 | "message": { 24 | "reply": { 25 | {{- template "response-preamble"}} 26 | "method": "acl:show", 27 | "response": { 28 | "card-number": 8165538, 29 | "permissions": [ 30 | { 31 | "door": "Gryffindor", 32 | "end-date": "2021-12-31", 33 | "start-date": "2021-01-01" 34 | }, 35 | { 36 | "door": "Slytherin", 37 | "end-date": "2021-12-31", 38 | "start-date": "2021-01-01" 39 | } 40 | ] 41 | } 42 | } 43 | } 44 | } 45 | ``` 46 | {{end -}} 47 | 48 | -------------------------------------------------------------------------------- /.codegen/markdown/acl-upload.md: -------------------------------------------------------------------------------- 1 | {{- with .acl_upload -}} 2 | ### `{{.command}}` 3 | 4 | Extracts an access control list from a set of controllers and stores it to the URL in the request. The URL 5 | can be any URL that will accept an file, including: 6 | 7 | - `file://` (e.g. _file:///var/uhppoted/uploaded/ACL.tar.gz_) 8 | - `http://` (e.g. _http://localhost:8080/uploaded/ACL.tar.gz_) 9 | - `https://` (e.g. _https://localhost:8080/uploaded/ACL.tar.gz_) 10 | - `s3:///` (e.g. _s3://uhppoted/uploaded/ACL.tar.gz_) 11 | 12 | Uploads to Amazon S3 expect the credentials and bucket information to be configured in _uhppoted.conf_: 13 | ``` 14 | aws.credentials = /etc/uhppoted/aws.credentials 15 | ; aws.profile = default 16 | ; aws.region = us-east-1 17 | ``` 18 | 19 | _aws.credentials_: 20 | ``` 21 | [default] 22 | aws_access_key_id = AKIAQMNWIVKBYA57IRWH 23 | aws_access_key_id = AKIZ.............QYV 24 | aws_secret_access_key = FRE................................zuyqt 25 | 26 | ``` 27 | 28 | {{template "request" . -}} 29 | {{template "response" . }} 30 | 31 | Example: 32 | ``` 33 | topic: uhppoted/gateway/requests/{{ .request.topic }} 34 | 35 | { 36 | "message": { 37 | "request": { 38 | {{- template "request-preamble"}} 39 | "request": { 40 | "url": "file:///var/uhppoted/uploaded/ACL.tar.gz", 41 | } 42 | } 43 | } 44 | 45 | { 46 | "message": { 47 | "reply": { 48 | {{- template "response-preamble"}} 49 | "method": "acl:upload", 50 | "response": { 51 | "uploaded": "file:///var/uhppoted/uploaded/uhppoted.tar.gz" 52 | } 53 | } 54 | } 55 | } 56 | ``` 57 | {{end -}} 58 | -------------------------------------------------------------------------------- /.codegen/markdown/clear-time-profiles.md: -------------------------------------------------------------------------------- 1 | {{- with .clear_time_profiles -}} 2 | ### `{{.command}}` 3 | 4 | Clears all time profiles on a a controller. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | "message": { 14 | "request": { 15 | {{- template "request-preamble"}} 16 | "device-id": 405419896 17 | } 18 | } 19 | } 20 | 21 | { 22 | "message": { 23 | "reply": { 24 | {{- template "response-preamble"}} 25 | "method": "clear-time-profiles", 26 | "response": { 27 | "cleared": true, 28 | "device-id": 405419896 29 | }, 30 | } 31 | }, 32 | } 33 | ``` 34 | {{end -}} 35 | -------------------------------------------------------------------------------- /.codegen/markdown/delete-card.md: -------------------------------------------------------------------------------- 1 | {{- with .delete_card -}} 2 | ### `{{.command}}` 3 | 4 | Deletes a card from a controller. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896, 18 | "card-number": 8165538 19 | } 20 | } 21 | } 22 | 23 | { 24 | "message": { 25 | "reply": { 26 | {{- template "response-preamble"}} 27 | "method": "delete-card", 28 | "response": { 29 | "device-id": 405419896, 30 | "card-number": 8165538, 31 | "deleted": true 32 | } 33 | } 34 | } 35 | } 36 | ``` 37 | {{end -}} 38 | -------------------------------------------------------------------------------- /.codegen/markdown/delete-cards.md: -------------------------------------------------------------------------------- 1 | {{- with .delete_cards -}} 2 | ### `{{.command}}` 3 | 4 | Deletes all cards from a controller. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896 18 | } 19 | } 20 | } 21 | 22 | { 23 | "message": { 24 | "reply": { 25 | {{- template "response-preamble"}} 26 | "method": "delete-cards", 27 | "response": { 28 | "device-id": 405419896, 29 | "deleted": true 30 | } 31 | } 32 | } 33 | } 34 | ``` 35 | {{end -}} 36 | -------------------------------------------------------------------------------- /.codegen/markdown/get-card.md: -------------------------------------------------------------------------------- 1 | {{- with .get_card -}} 2 | ### `{{.command}}` 3 | 4 | Retrieves a card record from a controller. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896, 18 | "card-number": 8165538 19 | } 20 | } 21 | } 22 | 23 | { 24 | "message": { 25 | "reply": { 26 | {{- template "response-preamble"}} 27 | "method": "get-card", 28 | "response": { 29 | "device-id": 405419896, 30 | "card": { 31 | "card-number": 8165538, 32 | "start-date": "2021-01-01" 33 | "end-date": "2021-12-31", 34 | "doors": { 35 | "1": 1, 36 | "2": 0, 37 | "3": 0, 38 | "4": 1 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | ``` 46 | {{end -}} 47 | -------------------------------------------------------------------------------- /.codegen/markdown/get-cards.md: -------------------------------------------------------------------------------- 1 | {{- with .get_cards -}} 2 | ### `{{.command}}` 3 | 4 | Retrieves a list of the cards stored on a controller. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896 18 | } 19 | } 20 | } 21 | 22 | { 23 | "message": { 24 | "reply": { 25 | {{- template "response-preamble"}} 26 | "method": "get-cards", 27 | "response": { 28 | "device-id": 405419896, 29 | "cards": [ 30 | 8165537, 31 | 8165539, 32 | 8165538 33 | ] 34 | } 35 | } 36 | } 37 | } 38 | ``` 39 | {{end -}} 40 | 41 | 42 | -------------------------------------------------------------------------------- /.codegen/markdown/get-device.md: -------------------------------------------------------------------------------- 1 | {{- with .get_device -}} 2 | ### `{{.command}}` 3 | 4 | Returns the controller information for a UHPPOTE controller. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | topic: uhppoted/gateway/requests/{{ .request.topic }} 14 | 15 | { 16 | "message": { 17 | "request": { 18 | {{- template "request-preamble"}} 19 | "device-id": 405419896 20 | } 21 | } 22 | } 23 | 24 | { 25 | "message": { 26 | "reply": { 27 | {{- template "response-preamble"}} 28 | "method": "get-device", 29 | "response": { 30 | "address": { 31 | "IP": "192.168.1.100", 32 | "Port": 60000, 33 | "Zone": "" 34 | }, 35 | "date": "2018-11-05", 36 | "device-id": 405419896, 37 | "device-type": "UTO311-L04", 38 | "gateway-address": "192.168.1.1", 39 | "ip-address": "192.168.1.100", 40 | "mac-address": "00:12:23:34:45:56", 41 | "subnet-mask": "255.255.255.0", 42 | "timezone": {}, 43 | "version": "0892" 44 | } 45 | } 46 | } 47 | } 48 | ``` 49 | {{end -}} 50 | 51 | 52 | -------------------------------------------------------------------------------- /.codegen/markdown/get-devices.md: -------------------------------------------------------------------------------- 1 | {{- with .get_devices -}} 2 | ### `{{.command}}` 3 | 4 | Returns a list of all UHPPOTE controllers found via a UDP broadcast on the local LAN or specifically 5 | configured in _uhppoted.conf_. 6 | 7 | {{template "request" . -}} 8 | {{template "response" . }} 9 | 10 | Example: 11 | ``` 12 | topic: uhppoted/gateway/requests/{{ .request.topic }} 13 | 14 | { 15 | "message": { 16 | "request": { 17 | {{- template "request-preamble"}} 18 | } 19 | } 20 | } 21 | 22 | { 23 | "message": { 24 | "reply": { 25 | {{- template "response-preamble"}} 26 | "method": "get-devices", 27 | "response": { 28 | "devices": { 29 | "201020304": { 30 | "device-type": "UTO311-L02", 31 | "ip-address": "192.168.1.101", 32 | "port": 60000 33 | }, 34 | "303986753": { 35 | "device-type": "UTO311-L03", 36 | "ip-address": "192.168.1.100", 37 | "port": 60000 38 | }, 39 | "405419896": { 40 | "device-type": "UTO311-L04", 41 | "ip-address": "192.168.1.100", 42 | "port": 60000 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | ``` 50 | {{end -}} 51 | 52 | 53 | -------------------------------------------------------------------------------- /.codegen/markdown/get-door-control.md: -------------------------------------------------------------------------------- 1 | {{- with .get_door_delay -}} 2 | ### `{{.command}}` 3 | 4 | Retrieves the control mode for a controller door. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896, 18 | "door": 3 19 | } 20 | } 21 | } 22 | 23 | { 24 | "message": { 25 | "reply": { 26 | {{- template "response-preamble"}} 27 | "method": "get-door-control", 28 | "response": { 29 | "device-id": 405419896, 30 | "door": 3, 31 | "control": "controlled" 32 | } 33 | } 34 | } 35 | } 36 | ``` 37 | {{end -}} 38 | 39 | 40 | -------------------------------------------------------------------------------- /.codegen/markdown/get-door-delay.md: -------------------------------------------------------------------------------- 1 | {{- with .get_door_delay -}} 2 | ### `{{.command}}` 3 | 4 | Retrieves the open delay for a controller door. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896, 18 | "door": 3 19 | } 20 | } 21 | } 22 | 23 | { 24 | "message": { 25 | "reply": { 26 | {{- template "response-preamble"}} 27 | "method": "get-door-delay", 28 | "response": { 29 | "device-id": 405419896, 30 | "door": 3, 31 | "delay": 7 32 | } 33 | } 34 | } 35 | } 36 | ``` 37 | {{end -}} 38 | 39 | 40 | -------------------------------------------------------------------------------- /.codegen/markdown/get-event.md: -------------------------------------------------------------------------------- 1 | {{- with .get_event -}} 2 | ### `{{.command}}` 3 | 4 | Retrieves an event from the controller. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896, 18 | "event-index": 50 19 | } 20 | } 21 | } 22 | 23 | { 24 | "message": { 25 | "reply": { 26 | {{- template "response-preamble"}} 27 | "method": "get-event", 28 | "response": { 29 | "device-id": 405419896, 30 | "event": { 31 | "access-granted": true, 32 | "card-number": 8165538, 33 | "device-id": 405419896, 34 | "direction": 1, 35 | "direction-text": "in", 36 | "door-id": 4, 37 | "event-id": 50, 38 | "event-reason": 0, 39 | "event-reason-text": "", 40 | "event-type": 2, 41 | "event-type-text": "door", 42 | "timestamp": "2019-08-09 16:18:55 PDT" 43 | } 44 | } 45 | } 46 | } 47 | } 48 | ``` 49 | {{end -}} 50 | 51 | 52 | -------------------------------------------------------------------------------- /.codegen/markdown/get-events.md: -------------------------------------------------------------------------------- 1 | {{- with .get_events -}} 2 | ### `{{.command}}` 3 | 4 | Retrieves the current, first and last event indices from a controller, and optionally 5 | fetches up to _count_ events starting from the current event index. 6 | 7 | {{template "request" . -}} 8 | {{template "response" . }} 9 | 10 | Example: 11 | ``` 12 | topic: uhppoted/gateway/requests/{{ .request.topic }} 13 | 14 | { 15 | "message": { 16 | "request": { 17 | {{- template "request-preamble"}} 18 | "device-id": 405419896, 19 | "count": 3 20 | } 21 | } 22 | } 23 | 24 | { 25 | "message": { 26 | "reply": { 27 | {{- template "response-preamble"}} 28 | "method": "get-events", 29 | "response": { 30 | "device-id": 405419896, 31 | "current": 26, 32 | "first": 1, 33 | "last": 69, 34 | "events": [ 35 | { 36 | "access-granted": true, 37 | "card-number": 8165533, 38 | "device-id": 405419896, 39 | "direction": 1, 40 | "direction-text": "in", 41 | "door-id": 1, 42 | "event-id": 24, 43 | "event-reason": 0, 44 | "event-reason-text": "", 45 | "event-type": 2, 46 | "event-type-text": "door", 47 | "timestamp": "2019-07-24 20:31:24 PDT" 48 | }, 49 | { 50 | "access-granted": true, 51 | "card-number": 8165534, 52 | "device-id": 405419896, 53 | "direction": 1, 54 | "direction-text": "in", 55 | "door-id": 4, 56 | "event-id": 25, 57 | "event-reason": 0, 58 | "event-reason-text": "", 59 | "event-type": 2, 60 | "event-type-text": "door", 61 | "timestamp": "2019-07-31 20:04:00 PDT" 62 | }, 63 | { 64 | "access-granted": false, 65 | "card-number": 8165535, 66 | "device-id": 405419896, 67 | "direction": 0, 68 | "direction-text": "", 69 | "door-id": 4, 70 | "event-id": 26, 71 | "event-reason": 0, 72 | "event-reason-text": "", 73 | "event-type": 1, 74 | "event-type-text": "le swipe", 75 | "timestamp": "2019-07-31 20:04:32 PDT" 76 | } 77 | ] 78 | } 79 | } 80 | } 81 | } 82 | ``` 83 | {{end -}} 84 | 85 | 86 | -------------------------------------------------------------------------------- /.codegen/markdown/get-status.md: -------------------------------------------------------------------------------- 1 | {{- with .get_status -}} 2 | ### `{{.command}}` 3 | 4 | Retrieves the controller status. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896 18 | } 19 | } 20 | } 21 | 22 | { 23 | "message": { 24 | "reply": { 25 | {{- template "response-preamble"}} 26 | "method": "get-status", 27 | "response": { 28 | "device-id": 405419896, 29 | "status": { 30 | "door-buttons": { 31 | "1": false, 32 | "2": false, 33 | "3": false, 34 | "4": false 35 | }, 36 | "door-states": { 37 | "1": false, 38 | "2": false, 39 | "3": false, 40 | "4": false 41 | }, 42 | "event": { 43 | "access-granted": true, 44 | "card-number": 8165537, 45 | "device-id": 0, 46 | "direction": 1, 47 | "direction-text": "in", 48 | "door-id": 1, 49 | "event-id": 69, 50 | "event-reason": 44, 51 | "event-reason-text": "remote open door", 52 | "event-type": 2, 53 | "event-type-text": "door", 54 | "timestamp": "2021-08-10 10:28:32 PDT" 55 | }, 56 | "input-state": 0, 57 | "relay-state": 0, 58 | "sequence-id": 0, 59 | "special-info": 0, 60 | "system-datetime": "2022-09-12 10:47:31 PDT", 61 | "system-error": 0 62 | } 63 | } 64 | } 65 | } 66 | } 67 | ``` 68 | {{end -}} 69 | 70 | 71 | -------------------------------------------------------------------------------- /.codegen/markdown/get-time-profile.md: -------------------------------------------------------------------------------- 1 | {{- with .get_time_profile -}} 2 | ### `{{.command}}` 3 | 4 | Retrieves a time profile from a controller. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | "message": { 14 | "request": { 15 | {{- template "request-preamble"}} 16 | "device-id": 405419896, 17 | "profile-id": 29 18 | } 19 | } 20 | } 21 | 22 | { 23 | "message": { 24 | "reply": { 25 | {{- template "response-preamble"}} 26 | "method": "get-time-profile", 27 | "response": { 28 | "device-id": 405419896, 29 | "time-profile": { 30 | "id": 29, 31 | "start-date": "2021-01-01", 32 | "end-date": "2021-12-31", 33 | "weekdays": "Monday" 34 | "segments": [ 35 | { 36 | "start": "08:30", 37 | "end": "17:00" 38 | }, 39 | { 40 | "start": "00:00", 41 | "end": "00:00" 42 | }, 43 | { 44 | "start": "00:00", 45 | "end": "00:00" 46 | } 47 | ], 48 | } 49 | } 50 | } 51 | } 52 | } 53 | ``` 54 | {{end -}} 55 | -------------------------------------------------------------------------------- /.codegen/markdown/get-time-profiles.md: -------------------------------------------------------------------------------- 1 | {{- with .get_time_profiles -}} 2 | ### `{{.command}}` 3 | 4 | Retrieves a range of time profiles from a controller. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896, 18 | "from": 2, 19 | "to": 254 20 | } 21 | } 22 | } 23 | 24 | { 25 | "message": { 26 | "reply": { 27 | {{- template "response-preamble"}} 28 | "method": "get-time-profile", 29 | "response": { 30 | "method": "get-time-profiles", 31 | "response": { 32 | "device-id": 405419896, 33 | "profiles": [ 34 | { 35 | "end-date": "2021-12-31", 36 | "id": 29, 37 | "segments": [ 38 | { 39 | "end": "17:00", 40 | "start": "08:30" 41 | }, 42 | { 43 | "end": "00:00", 44 | "start": "00:00" 45 | }, 46 | { 47 | "end": "00:00", 48 | "start": "00:00" 49 | } 50 | ], 51 | "start-date": "2021-01-01", 52 | "weekdays": "Monday" 53 | } 54 | ] 55 | } 56 | } 57 | } 58 | } 59 | ``` 60 | {{end -}} 61 | -------------------------------------------------------------------------------- /.codegen/markdown/get-time.md: -------------------------------------------------------------------------------- 1 | {{- with .get_time -}} 2 | ### `{{.command}}` 3 | 4 | Returns the controller date and time. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896 18 | } 19 | } 20 | } 21 | 22 | { 23 | "message": { 24 | "reply": { 25 | {{- template "response-preamble"}} 26 | "method": "get-time", 27 | "response": { 28 | "date-time": "2022-09-08 11:01:04 PDT", 29 | "device-id": 405419896 30 | } 31 | } 32 | } 33 | } 34 | ``` 35 | {{end -}} 36 | 37 | 38 | -------------------------------------------------------------------------------- /.codegen/markdown/open-door.md: -------------------------------------------------------------------------------- 1 | {{- with .open_door -}} 2 | ### `{{.command}}` 3 | 4 | Remotely opens a door after verifying that the card in the _request_ has permission: 5 | - the card number must match a line in the _uhppoted-mqtt_ `cards` file 6 | - the card number must be a valid card on the controller with permission to open the 7 | door 8 | 9 | The `cards` file is the first level of authorisation and is intended to restrict the use 10 | of the `open-door` command to a limited set of supervisor or override cards. It defaults 11 | to _mqtt/cards_ file in the _uhppoted_ _etc_ directory: 12 | - `/usr/local/etc/com.github.uhppoted/mqtt/cards (MacOS)` 13 | - `/etc/uhppoted/mqtt/cards (Linux)` 14 | - `\Program Data\uhppoted\mqttcards (Windows)` 15 | - `.\mqtt\cards (Windows)` 16 | 17 | but can be configured with the _mqtt.cards_ value in the _uhppoted.conf_ file: 18 | ``` 19 | # MQTT 20 | ... 21 | mqtt.cards = /usr/local/etc/com.github.uhppoted/mqtt/cards 22 | ... 23 | ``` 24 | 25 | Each line in the file should be a regular expression that matches one or more authorised cards. A catch-all `.*` regular expression will authorise all cards e.g.: 26 | ``` 27 | .* 28 | ``` 29 | 30 | In addition, the card number in the request must be valid for the controller: 31 | - the start date must be on or before _today_ 32 | - the end date must be on or after _today_ 33 | - the card must have access to the door 34 | 35 | e.g. 36 | ``` 37 | uhppote-cli get-card 405419896 8165538 38 | 39 | 405419896 8165538 2022-01-01 2022-12-31 Y N N Y 40 | ``` 41 | {{template "request" . -}} 42 | {{template "response" . }} 43 | 44 | Example: 45 | ``` 46 | topic: uhppoted/gateway/requests/{{ .request.topic }} 47 | 48 | { 49 | "message": { 50 | "request": { 51 | {{- template "request-preamble"}} 52 | "device-id": 405419896, 53 | "door": 4, 54 | "card-number": 405419896 55 | } 56 | } 57 | } 58 | { 59 | "message": { 60 | "reply": { 61 | {{- template "response-preamble"}} 62 | "method": "open-door", 63 | "response": { 64 | "device-id": 405419896, 65 | "door": 4, 66 | "opened": true 67 | } 68 | } 69 | } 70 | } 71 | ``` 72 | {{end -}} 73 | 74 | 75 | -------------------------------------------------------------------------------- /.codegen/markdown/put-card.md: -------------------------------------------------------------------------------- 1 | {{- with .put_card -}} 2 | ### `{{.command}}` 3 | 4 | Adds or updates a card record on a controller. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896, 18 | "card": { 19 | "card-number": 8165538, 20 | "start-date": "2021-01-01", 21 | "end-date": "2021-12-31", 22 | "doors": { 23 | "1": true, 24 | "2": false, 25 | "3": 55, 26 | "4": false 27 | } 28 | } 29 | } 30 | } 31 | } 32 | 33 | { 34 | "message": { 35 | "reply": { 36 | {{- template "response-preamble"}} 37 | "method": "put-card", 38 | "response": { 39 | "device-id": 405419896 40 | "card": { 41 | "card-number": 8165538, 42 | "start-date": "2021-01-01" 43 | "end-date": "2021-12-31", 44 | "doors": { 45 | "1": true, 46 | "2": false, 47 | "3": 55, 48 | "4": false 49 | } 50 | } 51 | } 52 | } 53 | } 54 | } 55 | ``` 56 | {{end -}} 57 | -------------------------------------------------------------------------------- /.codegen/markdown/record-special-events.md: -------------------------------------------------------------------------------- 1 | {{- with .record_special_events -}} 2 | ### `{{.command}}` 3 | 4 | Enables/disables event logging for door open, close and pushbutton events. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896, 18 | "enabled": true 19 | } 20 | } 21 | } 22 | 23 | { 24 | "message": { 25 | "reply": { 26 | {{- template "response-preamble"}} 27 | "method": "record-special-events", 28 | "response": { 29 | "DeviceID": 405419896, 30 | "Enable": true, 31 | "Updated": true 32 | } 33 | } 34 | } 35 | } 36 | ``` 37 | {{end -}} 38 | 39 | 40 | -------------------------------------------------------------------------------- /.codegen/markdown/restore-default-parameters.md: -------------------------------------------------------------------------------- 1 | {{- with .restore_default_parameters -}} 2 | ### `{{.command}}` 3 | 4 | Resets a controller to the manufacturer default configuration. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896 18 | } 19 | } 20 | } 21 | 22 | { 23 | "message": { 24 | "reply": { 25 | {{- template "response-preamble"}} 26 | "method": "restore-default-parameters", 27 | "response": { 28 | "device-id": 405419896, 29 | "reset": true 30 | } 31 | } 32 | } 33 | } 34 | ``` 35 | {{end -}} 36 | -------------------------------------------------------------------------------- /.codegen/markdown/set-door-control.md: -------------------------------------------------------------------------------- 1 | {{- with .set_door_delay -}} 2 | ### `{{.command}}` 3 | 4 | Sets the control mode for a controller door. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896, 18 | "door": 3, 19 | "control": "normally closed" 20 | } 21 | } 22 | } 23 | 24 | { 25 | "message": { 26 | "reply": { 27 | {{- template "response-preamble"}} 28 | "method": "set-door-control", 29 | "response": { 30 | "device-id": 405419896, 31 | "door": 3, 32 | "control": "normally closed" 33 | } 34 | } 35 | } 36 | } 37 | ``` 38 | {{end -}} 39 | 40 | 41 | -------------------------------------------------------------------------------- /.codegen/markdown/set-door-delay.md: -------------------------------------------------------------------------------- 1 | {{- with .set_door_delay -}} 2 | ### `{{.command}}` 3 | 4 | Sets the open delay for a door on a controller. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896, 18 | "door": 3, 19 | "delay": 8 20 | } 21 | } 22 | } 23 | 24 | { 25 | "message": { 26 | "reply": { 27 | {{- template "response-preamble"}} 28 | "method": "set-door-delay", 29 | "response": { 30 | "device-id": 405419896, 31 | "door": 3, 32 | "delay": 8 33 | } 34 | } 35 | } 36 | } 37 | ``` 38 | {{end -}} 39 | 40 | 41 | -------------------------------------------------------------------------------- /.codegen/markdown/set-door-interlock.md: -------------------------------------------------------------------------------- 1 | {{- with .set_door_interlock -}} 2 | ### `{{.command}}` 3 | 4 | Sets the door interlock mode for a controller. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896, 18 | "interlock": 4 19 | } 20 | } 21 | } 22 | 23 | { 24 | "message": { 25 | "reply": { 26 | {{- template "response-preamble"}} 27 | "method": "set-door-interlock", 28 | "response": { 29 | "device-id": 405419896, 30 | "interlock": 4 31 | } 32 | } 33 | } 34 | } 35 | ``` 36 | {{end -}} 37 | 38 | 39 | -------------------------------------------------------------------------------- /.codegen/markdown/set-door-keypads.md: -------------------------------------------------------------------------------- 1 | {{- with .set_door_keypads -}} 2 | ### `{{.command}}` 3 | 4 | Activates/deactivates the reader access keypads for a controller. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896, 18 | "keypads": { 19 | "1": true, 20 | "2": true, 21 | "3": false, 22 | "4": true 23 | } 24 | } 25 | } 26 | } 27 | 28 | { 29 | "message": { 30 | "reply": { 31 | {{- template "response-preamble"}} 32 | "method": "set-door-keypads", 33 | "response": { 34 | "device-id": 405419896, 35 | "keypads": { 36 | "1": true, 37 | "2": true, 38 | "3": false, 39 | "4": true 40 | } 41 | } 42 | } 43 | } 44 | } 45 | ``` 46 | {{end -}} 47 | 48 | 49 | -------------------------------------------------------------------------------- /.codegen/markdown/set-door-passcodes.md: -------------------------------------------------------------------------------- 1 | {{- with .set_door_passcodes -}} 2 | ### `{{.command}}` 3 | 4 | Sets up to four supervisor passcodes for a controller door, with valid passcodes being in the range [1..999999]. 5 | Invalid passcodes are replaces by 0 (no code). 6 | 7 | {{template "request" . -}} 8 | {{template "response" . }} 9 | 10 | Example: 11 | ``` 12 | topic: uhppoted/gateway/requests/device/door/passcodes:set 13 | 14 | { 15 | "message": { 16 | "request": { 17 | "client-id": "QWERTY", 18 | "request-id": "AH173635G3", 19 | "reply-to": "uhppoted/reply/97531", 20 | "device-id": 405419896, 21 | "door": 3, 22 | "passcodes": [12345,999999,54321] 23 | } 24 | } 25 | } 26 | 27 | { 28 | "message": { 29 | "reply": { 30 | "server-id": "uhppoted" 31 | "client-id": "QWERTY", 32 | "request-id": "AH173635G3", 33 | "method": "set-door-control", 34 | "response": { 35 | "device-id": 405419896, 36 | "door": 3, 37 | "passcodes": [12345,999999,54321] 38 | } 39 | } 40 | } 41 | } 42 | ``` 43 | {{end -}} 44 | 45 | -------------------------------------------------------------------------------- /.codegen/markdown/set-task-list.md: -------------------------------------------------------------------------------- 1 | {{- with .set_task_list -}} 2 | ### `{{.command}}` 3 | 4 | Stores a tasklist to a controller. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896, 18 | "tasks": [ 19 | { 20 | "task": "trigger once", 21 | "door": 3, 22 | "start-date": "2021-01-01", 23 | "end-date": "2021-12-31", 24 | "weekdays": "Monday,Wednesday,Friday", 25 | "start": "08:27" 26 | } 27 | ] 28 | } 29 | } 30 | } 31 | } 32 | 33 | { 34 | "message": { 35 | "reply": { 36 | {{- template "response-preamble"}} 37 | "method": "set-time-profile", 38 | "response": { 39 | "device-id": 405419896, 40 | "warnings": [] 41 | } 42 | } 43 | } 44 | } 45 | ``` 46 | {{end -}} 47 | -------------------------------------------------------------------------------- /.codegen/markdown/set-time-profile.md: -------------------------------------------------------------------------------- 1 | {{- with .set_time_profile -}} 2 | ### `{{.command}}` 3 | 4 | Adds or updates a time profile on a controller. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896, 18 | "profile": { 19 | "id": 29, 20 | "start-date": "2021-01-01", 21 | "end-date": "2021-12-31", 22 | "weekdays": "Monday,Wednesday,Thursday", 23 | "segments": [ 24 | { 25 | "start": "08:15", 26 | "end": "11:30" 27 | }, 28 | { 29 | "start": "14:05", 30 | "end": "17:45" 31 | } 32 | ], 33 | "linked-profile": 3 34 | } 35 | } 36 | } 37 | } 38 | 39 | { 40 | "message": { 41 | "reply": { 42 | {{- template "response-preamble"}} 43 | "method": "set-time-profile", 44 | "response": { 45 | "device-id": 405419896, 46 | "time-profile": { 47 | "id": 29, 48 | "start-date": "2021-01-01", 49 | "end-date": "2021-12-31", 50 | "weekdays": "Monday,Wednesday,Thursday" 51 | "segments": [ 52 | { 53 | "end": "11:30", 54 | "start": "08:15" 55 | }, 56 | { 57 | "end": "17:45", 58 | "start": "14:05" 59 | }, 60 | { 61 | "end": "00:00", 62 | "start": "00:00" 63 | } 64 | ], 65 | "linked-profile": 3 66 | } 67 | } 68 | } 69 | } 70 | } 71 | ``` 72 | {{end -}} 73 | -------------------------------------------------------------------------------- /.codegen/markdown/set-time-profiles.md: -------------------------------------------------------------------------------- 1 | {{- with .set_time_profiles -}} 2 | ### `{{.command}}` 3 | 4 | Stores a list of time profiles to a controller. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | "message": { 14 | "request": { 15 | {{- template "request-preamble"}} 16 | "device-id": 405419896, 17 | "profiles": [ 18 | { 19 | "id": 29, 20 | "start-date": "2021-01-01", 21 | "end-date": "2021-12-31", 22 | "weekdays": "Monday,Wednesday,Thursday", 23 | "segments": [ 24 | { 25 | "start": "08:15", 26 | "end": "11:30" 27 | }, 28 | { 29 | "start": "14:05", 30 | "end": "17:45" 31 | } 32 | ], 33 | "linked-profile": 30 34 | }, 35 | { 36 | "id": 31, 37 | "start-date": "2021-01-01", 38 | "end-date": "2021-12-31", 39 | "weekdays": "Monday,Wednesday,Thursday", 40 | "segments": [ 41 | { 42 | "start": "08:15", 43 | "end": "11:30" 44 | }, 45 | { 46 | "start": "14:05", 47 | "end": "17:45" 48 | } 49 | ], 50 | "linked-profile": 32 51 | }, 52 | { 53 | "id": 32, 54 | "start-date": "2021-01-01", 55 | "end-date": "2021-12-31", 56 | "weekdays": "Monday,Wednesday", 57 | "segments": [ 58 | { 59 | "start": "08:30", 60 | "end": "15:30" 61 | } 62 | ], 63 | "linked-profile": 33 64 | }, 65 | { 66 | "id": 33, 67 | "start-date": "2021-01-01", 68 | "end-date": "2021-12-31", 69 | "weekdays": "Saturday,Sunday", 70 | "segments": [ 71 | { 72 | "start": "10:30", 73 | "end": "17:30" 74 | } 75 | ] 76 | } 77 | ] 78 | } 79 | } 80 | } 81 | 82 | { 83 | "message": { 84 | "reply": { 85 | {{- template "response-preamble"}} 86 | "method": "get-time-profile", 87 | "response": { 88 | "method": "get-time-profiles", 89 | "response": { 90 | "device-id": 405419896, 91 | "warnings": [ 92 | "profile 29 : linked time profile 30 is not defined" 93 | ] 94 | } 95 | } 96 | } 97 | } 98 | ``` 99 | {{end -}} 100 | -------------------------------------------------------------------------------- /.codegen/markdown/set-time.md: -------------------------------------------------------------------------------- 1 | {{- with .set_time -}} 2 | ### `{{.command}}` 3 | 4 | Sets the controller date and time. 5 | 6 | {{template "request" . -}} 7 | {{template "response" . }} 8 | 9 | Example: 10 | ``` 11 | topic: uhppoted/gateway/requests/{{ .request.topic }} 12 | 13 | { 14 | "message": { 15 | "request": { 16 | {{- template "request-preamble"}} 17 | "device-id": 405419896, 18 | "date-time": "2022-09-09 11:25:04" 19 | } 20 | } 21 | } 22 | 23 | { 24 | "message": { 25 | "reply": { 26 | {{- template "response-preamble"}} 27 | "method": "set-time", 28 | "response": { 29 | "device-id": 405419896, 30 | "date-time": "2022-09-09 11:25:04 PDT" 31 | } 32 | } 33 | } 34 | } 35 | ``` 36 | {{end -}} 37 | 38 | 39 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ main ] 7 | pull_request: 8 | branches: [ main ] 9 | 10 | jobs: 11 | 12 | build: 13 | name: Build 14 | runs-on: ubuntu-latest 15 | steps: 16 | 17 | - name: Set up Go 1.x 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version: ^1.24 21 | id: go 22 | 23 | - name: Install staticcheck 24 | run: go install honnef.co/go/tools/cmd/staticcheck@latest 25 | 26 | - name: Install govulncheck 27 | run: go install golang.org/x/vuln/cmd/govulncheck@latest 28 | 29 | - name: Check out code into the Go module directory 30 | uses: actions/checkout@v4 31 | 32 | - name: Build 33 | run: make build-all 34 | -------------------------------------------------------------------------------- /.github/workflows/ghcr.yml: -------------------------------------------------------------------------------- 1 | name: ghcr 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [published] 7 | 8 | jobs: 9 | 10 | build: 11 | name: Push uhppoted-mqtt container to ghcr.io 12 | runs-on: ubuntu-latest 13 | steps: 14 | 15 | - name: Set up Go 1.x 16 | uses: actions/setup-go@v5 17 | with: 18 | go-version: ^1.24 19 | cache: false 20 | 21 | - name: Install staticcheck 22 | run: go install honnef.co/go/tools/cmd/staticcheck@latest 23 | 24 | - name: Install govulncheck 25 | run: go install golang.org/x/vuln/cmd/govulncheck@latest 26 | 27 | - name: Check out code into the Go module directory 28 | uses: actions/checkout@v4 29 | 30 | - name: Build Docker image 31 | run: | 32 | make docker-ghcr 33 | docker images 34 | 35 | - name: Docker login to ghcr.io 36 | run: | 37 | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin 38 | 39 | - name: Push Docker 'latest' image to ghcr.io 40 | run: | 41 | docker push ghcr.io/uhppoted/mqttd:latest 42 | 43 | - name: Push versioned Docker image to ghcr.io 44 | if: github.event_name == 'release' && github.event.action == 'published' 45 | run: | 46 | TAG="${{ github.event.release.tag_name }}" 47 | VERSION=${TAG#v} 48 | echo ">>>>>> build Docker image version ${VERSION}" 49 | make docker-ghcr DOCKER=ghcr.io/uhppoted/mqttd:${VERSION} 50 | docker images 51 | docker push ghcr.io/uhppoted/mqttd:${VERSION} 52 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: nightly 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '30 1 1 * *' 7 | release: 8 | types: [published] 9 | 10 | jobs: 11 | 12 | build: 13 | name: Build 14 | runs-on: ubuntu-latest 15 | steps: 16 | 17 | - name: Check out code into the Go module directory 18 | uses: actions/checkout@v4 19 | 20 | - name: Set up Go 1.x 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version: ^1.24 24 | id: go 25 | 26 | - name: Install staticcheck 27 | run: go install honnef.co/go/tools/cmd/staticcheck@latest 28 | 29 | - name: Install govulncheck 30 | run: go install golang.org/x/vuln/cmd/govulncheck@latest 31 | 32 | - name: Build 33 | run: make build-all 34 | 35 | - name: Upload Linux artifact 36 | uses: actions/upload-artifact@v4 37 | with: 38 | path: dist/development/linux/uhppoted-mqtt 39 | name: uhppoted-mqtt_linux-nightly 40 | 41 | - name: Upload MacOS Intel artifact 42 | uses: actions/upload-artifact@v4 43 | with: 44 | path: dist/development/darwin-x64/uhppoted-mqtt 45 | name: uhppoted-mqtt_darwin-x64-nightly 46 | 47 | - name: Upload MacOS Apple silicon artifact 48 | uses: actions/upload-artifact@v4 49 | with: 50 | path: dist/development/darwin-arm64/uhppoted-mqtt 51 | name: uhppoted-mqtt_darwin-arm64-nightly 52 | 53 | - name: Upload Windows artifact 54 | uses: actions/upload-artifact@v4 55 | with: 56 | path: dist/development/windows/uhppoted-mqtt.exe 57 | name: uhppoted-mqtt_windows-nightly.exe 58 | 59 | - name: Upload ARM64 artifact 60 | uses: actions/upload-artifact@v4 61 | with: 62 | path: dist/development/arm/uhppoted-mqtt 63 | name: uhppoted-mqtt_arm-nightly 64 | 65 | - name: Upload ARMv7 artifact 66 | uses: actions/upload-artifact@v4 67 | with: 68 | path: dist/development/arm7/uhppoted-mqtt 69 | name: uhppoted-mqtt_arm7-nightly 70 | 71 | - name: Upload ARMv6 artifact 72 | uses: actions/upload-artifact@v4 73 | with: 74 | path: dist/development/arm6/uhppoted-mqtt 75 | name: uhppoted-mqtt_arm6-nightly 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | bin 3 | dist 4 | runtime 5 | go.work 6 | go.work.sum 7 | documentation/docker 8 | documentation/etc 9 | documentation/greengrass/tmp 10 | release-notes.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 twyst 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | ### IN PROGRESS 4 | 5 | - [x] anti-passback (cf. https://github.com/uhppoted/uhppoted/issues/60) 6 | - [x] `get-antipassback` 7 | - [x] `set-antipassback` 8 | - [x] Docker 9 | - [x] documentation 10 | - [x] CHANGELOG 11 | - [x] README 12 | 13 | - [ ] Remove startup warnings for missing encryption/signing/etc files if auth is not enabled. 14 | - [ ] Clean up Paho logging 15 | - [ ] MQTT v5 16 | 17 | ## To Be Done 18 | 19 | - [ ] [Sparkplug B](https://github.com/eclipse-sparkplug/sparkplug) 20 | - [ ] [MQTT Dash](https://iot.stackexchange.com/questions/6561/generic-mobile-applications-for-smart-home-devices) 21 | - [ ] [UCANs](https://ucan.xyz/) 22 | - [ ] (optionally) Generate uhppoted.conf if it doesn't exist 23 | - [ ] Make reconnect time configurable 24 | - [ ] Relook at encoding reply content - maybe json.RawMessage can preserve the field order 25 | - [ ] Replace values passed in Context with initialised struct 26 | - [ ] last-will-and-testament (?) 27 | - [ ] publish add/delete card, etc to event stream 28 | - [ ] MQTT v5.0 29 | - [ ] [JSON-RPC](https://en.wikipedia.org/wiki/JSON-RPC) (?) 30 | - [ ] Add to CLI 31 | - [ ] Non-ephemeral key transport: https://tools.ietf.org/html/rfc5990#appendix-A 32 | - [ ] user:open/get permissions require matching card number 33 | - [ ] [AEAD](http://alexander.holbreich.org/message-authentication) 34 | - [ ] Support for multiple brokers 35 | - [ ] NACL/tweetnacl 36 | - [ ] Report system events for e.g. listen bound/not bound 37 | 38 | ### Documentation 39 | 40 | - [ ] TeX protocol description 41 | - [ ] godoc 42 | - [ ] build documentation 43 | - [ ] install documentation 44 | - [ ] user manuals 45 | - [ ] man/info page 46 | 47 | ### Other 48 | 49 | 1. Integration tests 50 | 2. Verify fields in listen events/status replies against SDK: 51 | - battery status can be (at least) 0x00, 0x01 and 0x04 52 | 3. EventLogger 53 | - MacOS: use [system logging](https://developer.apple.com/documentation/os/logging) 54 | - Windows: event logging 55 | 4. Update file watchers to fsnotify when that is merged into the standard library (1.4 ?) 56 | - https://github.com/golang/go/issues/4068 57 | 5. [Teserakt E2E encryption](https://teserakt.io) 58 | 6. [Fernet encryption](https://asecuritysite.com/encryption/fernet) 59 | 7. [IoT standards](https://iot.stackexchange.com/questions/5363/mqtt-json-format-for-process-automation-industry) 60 | 8. [StackExchange: MQTT security tests](https://iot.stackexchange.com/questions/452/what-simple-security-tests-can-i-perform-on-my-mqtt-network) 61 | 9. [VerneMQ](https://vernemq.com) 62 | 10.[SparkplugB](https://cogentdatahub.com/connect/mqtt/sparkplug-b) 63 | 64 | ## NOTES 65 | 66 | 1. [os_arch.go](https://gist.github.com/camabeh/a02e6846e00251e1820c784516c0318f) 67 | -------------------------------------------------------------------------------- /acl/grant.go: -------------------------------------------------------------------------------- 1 | package acl 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/uhppoted/uhppote-core/types" 8 | api "github.com/uhppoted/uhppoted-lib/acl" 9 | "github.com/uhppoted/uhppoted-lib/uhppoted" 10 | "github.com/uhppoted/uhppoted-mqtt/common" 11 | ) 12 | 13 | func (a *ACL) Grant(impl uhppoted.IUHPPOTED, request []byte) (interface{}, error) { 14 | body := struct { 15 | CardNumber *uint32 `json:"card-number"` 16 | From *types.Date `json:"start-date"` 17 | To *types.Date `json:"end-date"` 18 | Profile int `json:"profile"` 19 | Doors []string `json:"doors"` 20 | }{} 21 | 22 | if err := json.Unmarshal(request, &body); err != nil { 23 | return common.MakeError(StatusBadRequest, "Cannot parse request", err), fmt.Errorf("%w: %v", uhppoted.ErrBadRequest, err) 24 | } 25 | 26 | if body.CardNumber == nil { 27 | return common.MakeError(StatusBadRequest, "Missing/invalid card number", nil), fmt.Errorf("missing/invalid card number") 28 | } 29 | 30 | if body.From == nil { 31 | return common.MakeError(StatusBadRequest, "Missing/invalid start date", nil), fmt.Errorf("missing/invalid start date") 32 | } 33 | 34 | if body.To == nil { 35 | return common.MakeError(StatusBadRequest, "Missing/invalid end date", nil), fmt.Errorf("missing/invalid end date") 36 | } 37 | 38 | if body.Profile != 0 && (body.Profile < 2 || body.Profile > 254) { 39 | return common.MakeError(StatusBadRequest, fmt.Sprintf("Invalid time profile (%v)", body.Profile), nil), fmt.Errorf("invalid time profile (%v)", body.Profile) 40 | } 41 | 42 | err := api.Grant(a.UHPPOTE, a.Devices, *body.CardNumber, *body.From, *body.To, body.Profile, body.Doors) 43 | if err != nil { 44 | return common.MakeError(StatusInternalServerError, err.Error(), nil), err 45 | } 46 | 47 | return struct { 48 | Granted bool `json:"granted"` 49 | }{ 50 | Granted: true, 51 | }, nil 52 | } 53 | -------------------------------------------------------------------------------- /acl/revoke.go: -------------------------------------------------------------------------------- 1 | package acl 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | api "github.com/uhppoted/uhppoted-lib/acl" 8 | "github.com/uhppoted/uhppoted-lib/uhppoted" 9 | "github.com/uhppoted/uhppoted-mqtt/common" 10 | ) 11 | 12 | func (a *ACL) Revoke(impl uhppoted.IUHPPOTED, request []byte) (interface{}, error) { 13 | body := struct { 14 | CardNumber *uint32 `json:"card-number"` 15 | Doors []string `json:"doors"` 16 | }{} 17 | 18 | if err := json.Unmarshal(request, &body); err != nil { 19 | return common.MakeError(StatusBadRequest, "Cannot parse request", err), fmt.Errorf("%w: %v", uhppoted.ErrBadRequest, err) 20 | } 21 | 22 | if body.CardNumber == nil { 23 | return common.MakeError(StatusBadRequest, "Missing/invalid card number", nil), fmt.Errorf("missing/invalid card number") 24 | } 25 | 26 | err := api.Revoke(a.UHPPOTE, a.Devices, *body.CardNumber, body.Doors) 27 | if err != nil { 28 | return common.MakeError(StatusInternalServerError, err.Error(), nil), err 29 | } 30 | 31 | return struct { 32 | Revoked bool `json:"revoked"` 33 | }{ 34 | Revoked: true, 35 | }, nil 36 | } 37 | -------------------------------------------------------------------------------- /acl/show.go: -------------------------------------------------------------------------------- 1 | package acl 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | api "github.com/uhppoted/uhppoted-lib/acl" 8 | "github.com/uhppoted/uhppoted-lib/uhppoted" 9 | "github.com/uhppoted/uhppoted-mqtt/common" 10 | ) 11 | 12 | func (a *ACL) Show(impl uhppoted.IUHPPOTED, request []byte) (interface{}, error) { 13 | body := struct { 14 | CardNumber *uint32 `json:"card-number"` 15 | }{} 16 | 17 | if err := json.Unmarshal(request, &body); err != nil { 18 | return common.MakeError(StatusBadRequest, "Cannot parse request", err), fmt.Errorf("%w: %v", uhppoted.ErrBadRequest, err) 19 | } 20 | 21 | if body.CardNumber == nil { 22 | return common.MakeError(StatusBadRequest, "Missing/invalid card number", nil), fmt.Errorf("missing/invalid card number") 23 | } 24 | 25 | acl, err := api.GetCard(a.UHPPOTE, a.Devices, *body.CardNumber) 26 | if err != nil { 27 | return common.MakeError(StatusInternalServerError, "Error retrieving card access permissions", err), err 28 | } 29 | 30 | if acl == nil { 31 | return common.MakeError(StatusInternalServerError, "Error retrieving card access permissions", nil), fmt.Errorf(" response to GetCard request") 32 | } 33 | 34 | response := Permissions{ 35 | CardNumber: *body.CardNumber, 36 | Permissions: []Permission{}, 37 | } 38 | 39 | for k, v := range acl { 40 | response.Permissions = append(response.Permissions, Permission{ 41 | Door: k, 42 | StartDate: v.From, 43 | EndDate: v.To, 44 | Profile: v.Profile, 45 | }) 46 | } 47 | 48 | return response, nil 49 | } 50 | -------------------------------------------------------------------------------- /acl/tar.go: -------------------------------------------------------------------------------- 1 | package acl 2 | 3 | import ( 4 | "archive/tar" 5 | "bytes" 6 | "compress/gzip" 7 | "fmt" 8 | "io" 9 | "path/filepath" 10 | "time" 11 | ) 12 | 13 | func targz(files map[string][]byte, w io.Writer) error { 14 | var b bytes.Buffer 15 | 16 | tw := tar.NewWriter(&b) 17 | for filename, body := range files { 18 | header := &tar.Header{ 19 | Name: filename, 20 | Mode: 0660, 21 | Size: int64(len(body)), 22 | Uname: "uhppoted", 23 | Gname: "uhppoted", 24 | } 25 | 26 | if err := tw.WriteHeader(header); err != nil { 27 | return err 28 | } 29 | 30 | if _, err := tw.Write([]byte(body)); err != nil { 31 | return err 32 | } 33 | } 34 | 35 | if err := tw.Close(); err != nil { 36 | return err 37 | } 38 | 39 | gz := gzip.NewWriter(w) 40 | 41 | gz.Name = fmt.Sprintf("uhppoted-%s.tar.gz", time.Now().Format("2006-01-02T150405")) 42 | gz.ModTime = time.Now() 43 | gz.Comment = "" 44 | 45 | _, err := gz.Write(b.Bytes()) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | return gz.Close() 51 | } 52 | 53 | func untar(r io.Reader) (map[string][]byte, string, error) { 54 | files := map[string][]byte{} 55 | uname := "" 56 | 57 | gz, err := gzip.NewReader(r) 58 | if err != nil { 59 | return nil, "", err 60 | } 61 | 62 | tr := tar.NewReader(gz) 63 | 64 | for { 65 | header, err := tr.Next() 66 | if err == io.EOF { 67 | break 68 | } else if err != nil { 69 | return nil, "", err 70 | } 71 | 72 | switch header.Typeflag { 73 | case tar.TypeReg: 74 | if filepath.Ext(header.Name) == ".acl" { 75 | if _, ok := files["ACL"]; ok { 76 | return nil, "", fmt.Errorf("multiple ACL files in tar.gz") 77 | } 78 | 79 | var buffer bytes.Buffer 80 | if _, err := io.Copy(&buffer, tr); err != nil { 81 | return nil, "", err 82 | } 83 | 84 | files["ACL"] = buffer.Bytes() 85 | uname = header.Uname 86 | } 87 | 88 | if header.Name == "signature" { 89 | if _, ok := files["signature"]; ok { 90 | return nil, "", fmt.Errorf("multiple signature files in tar.gz") 91 | } 92 | 93 | var buffer bytes.Buffer 94 | if _, err := io.Copy(&buffer, tr); err != nil { 95 | return nil, "", err 96 | } 97 | 98 | files["signature"] = buffer.Bytes() 99 | } 100 | } 101 | } 102 | 103 | if _, ok := files["ACL"]; !ok { 104 | return nil, "", fmt.Errorf("ACL file missing from tar.gz") 105 | } 106 | 107 | if _, ok := files["signature"]; !ok { 108 | return nil, "", fmt.Errorf("'signature' file missing from tar.gz") 109 | } 110 | 111 | return files, uname, nil 112 | } 113 | -------------------------------------------------------------------------------- /acl/tsv.go: -------------------------------------------------------------------------------- 1 | package acl 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | func unpackTSV(r io.Reader) (map[string][]byte, string, error) { 8 | files := map[string][]byte{} 9 | uname := "" 10 | 11 | if bytes, err := io.ReadAll(r); err != nil { 12 | return nil, "", err 13 | } else { 14 | files["ACL"] = bytes 15 | } 16 | 17 | files["signature"] = []byte{} 18 | 19 | return files, uname, nil 20 | } 21 | -------------------------------------------------------------------------------- /acl/upload.go: -------------------------------------------------------------------------------- 1 | package acl 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/url" 7 | "strings" 8 | 9 | api "github.com/uhppoted/uhppoted-lib/acl" 10 | "github.com/uhppoted/uhppoted-lib/uhppoted" 11 | "github.com/uhppoted/uhppoted-mqtt/common" 12 | ) 13 | 14 | func (a *ACL) Upload(impl uhppoted.IUHPPOTED, request []byte) (interface{}, error) { 15 | body := struct { 16 | URL *string `json:"url"` 17 | }{} 18 | 19 | if err := json.Unmarshal(request, &body); err != nil { 20 | return common.MakeError(StatusBadRequest, "Cannot parse request", err), fmt.Errorf("%w: %v", uhppoted.ErrBadRequest, err) 21 | } 22 | 23 | if body.URL == nil { 24 | return common.MakeError(StatusBadRequest, "Missing/invalid upload URI", nil), fmt.Errorf("missing/invalid upload URI") 25 | } 26 | 27 | uri, err := url.Parse(*body.URL) 28 | if err != nil { 29 | return common.MakeError(StatusBadRequest, "Missing/invalid upload URI", err), fmt.Errorf("invalid upload URL '%v' (%w)", body.URL, err) 30 | } 31 | 32 | acl, errors := api.GetACL(a.UHPPOTE, a.Devices) 33 | if len(errors) > 0 { 34 | err := fmt.Errorf("%v", errors) 35 | return common.MakeError(StatusInternalServerError, "Error retrieving ACL", err), err 36 | } 37 | 38 | if acl == nil { 39 | return common.MakeError(StatusInternalServerError, "Error retrieving card access permissions", nil), fmt.Errorf(" response to GetCard request") 40 | } 41 | 42 | for k, l := range acl { 43 | infof("acl:upload", "%v Retrieved %v records", k, len(l)) 44 | } 45 | 46 | var w strings.Builder 47 | if err := api.MakeTSV(acl, a.Devices, &w); err != nil { 48 | return common.MakeError(StatusInternalServerError, "Error reformatting card access permissions", err), err 49 | } 50 | 51 | if err = a.store("acl:upload", uri.String(), "uhppoted.acl", []byte(w.String())); err != nil { 52 | return common.MakeError(StatusBadRequest, "Error uploading ACL", err), err 53 | } 54 | 55 | return struct { 56 | Uploaded string `json:"uploaded"` 57 | }{ 58 | Uploaded: uri.String(), 59 | }, nil 60 | } 61 | -------------------------------------------------------------------------------- /acl/zip.go: -------------------------------------------------------------------------------- 1 | package acl 2 | 3 | import ( 4 | "archive/zip" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "path/filepath" 9 | ) 10 | 11 | func zipf(files map[string][]byte, w io.Writer) error { 12 | zw := zip.NewWriter(w) 13 | for filename, body := range files { 14 | if f, err := zw.Create(filename); err != nil { 15 | return err 16 | } else if _, err = f.Write([]byte(body)); err != nil { 17 | return err 18 | } 19 | } 20 | 21 | return zw.Close() 22 | } 23 | 24 | func unzip(r io.Reader) (map[string][]byte, string, error) { 25 | files := map[string][]byte{} 26 | uname := "" 27 | 28 | b, err := io.ReadAll(r) 29 | if err != nil { 30 | return nil, "", err 31 | } 32 | 33 | zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b))) 34 | if err != nil { 35 | return nil, "", err 36 | } 37 | 38 | for _, f := range zr.File { 39 | if filepath.Ext(f.Name) == ".acl" { 40 | if _, ok := files["ACL"]; ok { 41 | return nil, "", fmt.Errorf("multiple ACL files in tar.gz") 42 | } 43 | 44 | rc, err := f.Open() 45 | if err != nil { 46 | return nil, "", err 47 | } 48 | 49 | var buffer bytes.Buffer 50 | if _, err := io.Copy(&buffer, rc); err != nil { 51 | return nil, "", err 52 | } 53 | 54 | files["ACL"] = buffer.Bytes() 55 | uname = f.Comment 56 | rc.Close() 57 | } 58 | 59 | if f.Name == "signature" { 60 | if _, ok := files["signature"]; ok { 61 | return nil, "", fmt.Errorf("multiple signature files in tar.gz") 62 | } 63 | 64 | rc, err := f.Open() 65 | if err != nil { 66 | return nil, "", err 67 | } 68 | 69 | var buffer bytes.Buffer 70 | if _, err := io.Copy(&buffer, rc); err != nil { 71 | return nil, "", err 72 | } 73 | 74 | files["signature"] = buffer.Bytes() 75 | rc.Close() 76 | } 77 | } 78 | 79 | if _, ok := files["ACL"]; !ok { 80 | return nil, "", fmt.Errorf("ACL file missing from tar.gz") 81 | } 82 | 83 | if _, ok := files["signature"]; !ok { 84 | return nil, "", fmt.Errorf("'signature' file missing from tar.gz") 85 | } 86 | 87 | return files, uname, nil 88 | } 89 | -------------------------------------------------------------------------------- /auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | const LOG_TAG = "auth" 4 | -------------------------------------------------------------------------------- /auth/hmac.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha256" 6 | ) 7 | 8 | type HMAC struct { 9 | Required bool 10 | secret []byte 11 | } 12 | 13 | func NewHMAC(required bool, key string) (*HMAC, error) { 14 | return &HMAC{ 15 | Required: required, 16 | secret: []byte(key), 17 | }, nil 18 | } 19 | 20 | func (h *HMAC) Verify(message []byte, mac []byte) bool { 21 | hash := hmac.New(sha256.New, h.secret) 22 | 23 | hash.Write(message) 24 | 25 | return hmac.Equal(mac, hash.Sum(nil)) 26 | } 27 | 28 | func (h *HMAC) MAC(message []byte) []byte { 29 | if len(h.secret) != 0 { 30 | hash := hmac.New(sha256.New, h.secret) 31 | 32 | hash.Write(message) 33 | 34 | return hash.Sum(nil) 35 | } 36 | 37 | return []byte{} 38 | } 39 | -------------------------------------------------------------------------------- /auth/nonce.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strconv" 7 | 8 | "github.com/uhppoted/uhppoted-lib/kvs" 9 | "github.com/uhppoted/uhppoted-mqtt/log" 10 | ) 11 | 12 | type Nonce struct { 13 | ignore bool 14 | mqttd struct { 15 | *kvs.KeyValueStore 16 | filepath string 17 | } 18 | counters struct { 19 | *kvs.KeyValueStore 20 | filepath string 21 | } 22 | } 23 | 24 | func NewNonce(verify bool, server, clients string) (*Nonce, error) { 25 | var err error 26 | 27 | var f = func(value string) (interface{}, error) { 28 | return strconv.ParseUint(value, 10, 64) 29 | } 30 | 31 | nonce := Nonce{ 32 | ignore: !verify, 33 | mqttd: struct { 34 | *kvs.KeyValueStore 35 | filepath string 36 | }{ 37 | kvs.NewKeyValueStore("nonce:mqttd", f), 38 | server, 39 | }, 40 | counters: struct { 41 | *kvs.KeyValueStore 42 | filepath string 43 | }{ 44 | kvs.NewKeyValueStore("nonce:clients", f), 45 | clients, 46 | }, 47 | } 48 | 49 | if err = nonce.mqttd.LoadFromFile(server); err != nil { 50 | log.Warnf(LOG_TAG, "%v", err) 51 | } 52 | 53 | if err = nonce.counters.LoadFromFile(clients); err != nil { 54 | log.Warnf(LOG_TAG, "%v", err) 55 | } 56 | 57 | return &nonce, nil 58 | } 59 | 60 | func (n *Nonce) Validate(clientID *string, nonce *uint64) error { 61 | if !n.ignore || (clientID != nil && nonce != nil) { 62 | if clientID == nil { 63 | return errors.New("missing client-id") 64 | } 65 | 66 | if nonce == nil { 67 | return errors.New("missing nonce missing") 68 | } 69 | 70 | c, ok := n.counters.Get(*clientID) 71 | if !ok { 72 | c = uint64(0) 73 | } 74 | 75 | if *nonce <= c.(uint64) { 76 | return fmt.Errorf("nonce reused: %s, %d", *clientID, *nonce) 77 | } 78 | 79 | n.counters.Store(*clientID, *nonce, n.counters.filepath) 80 | } 81 | 82 | return nil 83 | } 84 | 85 | func (n *Nonce) Next() uint64 { 86 | c, ok := n.mqttd.Get("mqttd") 87 | if !ok { 88 | c = uint64(0) 89 | } 90 | 91 | nonce := c.(uint64) + 1 92 | 93 | n.mqttd.Store("mqttd", nonce, n.mqttd.filepath) 94 | 95 | return nonce 96 | } 97 | -------------------------------------------------------------------------------- /auth/permissions.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | 8 | "github.com/uhppoted/uhppoted-lib/kvs" 9 | ) 10 | 11 | type Permissions struct { 12 | Enabled bool 13 | users *kvs.KeyValueStore 14 | groups *kvs.KeyValueStore 15 | } 16 | 17 | type permission struct { 18 | resource *regexp.Regexp 19 | action *regexp.Regexp 20 | } 21 | 22 | func (p permission) String() string { 23 | return fmt.Sprintf("resource:`%s` action:`%s`", p.resource, p.action) 24 | } 25 | 26 | func NewPermissions(enabled bool, users, groups string) (*Permissions, error) { 27 | separator := regexp.MustCompile(`\s*,\s*`) 28 | 29 | u := func(value string) (interface{}, error) { 30 | return separator.Split(value, -1), nil 31 | } 32 | 33 | g := func(value string) (interface{}, error) { 34 | permissions := []permission{} 35 | re := regexp.MustCompile(`(.*?):(.*)`) 36 | tokens := separator.Split(value, -1) 37 | for _, s := range tokens { 38 | if match := re.FindStringSubmatch(s); len(match) == 3 { 39 | resource, err := regexp.Compile("^" + strings.ReplaceAll(match[1], "*", ".*") + "$") 40 | if err != nil { 41 | return permissions, err 42 | } 43 | 44 | action, err := regexp.Compile("^" + strings.ReplaceAll(match[2], "*", ".*") + "$") 45 | if err != nil { 46 | return permissions, err 47 | } 48 | 49 | permissions = append(permissions, permission{ 50 | resource: resource, 51 | action: action, 52 | }) 53 | } 54 | } 55 | 56 | return permissions, nil 57 | } 58 | 59 | permissions := Permissions{ 60 | Enabled: enabled, 61 | users: kvs.NewKeyValueStore("permissions:users", u), 62 | groups: kvs.NewKeyValueStore("permissions:groups", g), 63 | } 64 | 65 | if enabled { 66 | err := permissions.users.LoadFromFile(users) 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | err = permissions.groups.LoadFromFile(groups) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | permissions.users.Watch(users) 77 | permissions.groups.Watch(groups) 78 | } 79 | 80 | return &permissions, nil 81 | } 82 | 83 | func (p *Permissions) Validate(clientID, resource, action string) error { 84 | groups, ok := p.users.Get(clientID) 85 | if !ok { 86 | return fmt.Errorf("%s: Not a member of any groups", clientID) 87 | } 88 | 89 | for _, g := range groups.([]string) { 90 | if permissions, ok := p.groups.Get(g); ok { 91 | for _, q := range permissions.([]permission) { 92 | if q.resource.MatchString(resource) && q.action.MatchString(action) { 93 | return nil 94 | } 95 | } 96 | } 97 | } 98 | 99 | return fmt.Errorf("%s: Not authorised for %s:%s", clientID, resource, action) 100 | } 101 | -------------------------------------------------------------------------------- /cmd/uhppoted-mqtt/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/uhppoted/uhppote-core/uhppote" 8 | "github.com/uhppoted/uhppoted-lib/command" 9 | "github.com/uhppoted/uhppoted-lib/config" 10 | "github.com/uhppoted/uhppoted-mqtt/commands" 11 | ) 12 | 13 | var cli = []uhppoted.Command{ 14 | &commands.RUN, 15 | &commands.DAEMONIZE, 16 | &commands.UNDAEMONIZE, 17 | &uhppoted.Version{ 18 | Application: commands.SERVICE, 19 | Version: uhppote.VERSION, 20 | }, 21 | &uhppoted.Config{ 22 | Application: commands.SERVICE, 23 | Config: config.DefaultConfig, 24 | }, 25 | } 26 | 27 | var help = uhppoted.NewHelp(commands.SERVICE, cli, &commands.RUN) 28 | 29 | func main() { 30 | cmd, err := uhppoted.Parse(cli, &commands.RUN, help) 31 | if err != nil { 32 | fmt.Printf("\nError parsing command line: %v\n\n", err) 33 | os.Exit(1) 34 | } 35 | 36 | if err = cmd.Execute(); err != nil { 37 | fmt.Printf("\nERROR: %v\n\n", err) 38 | os.Exit(1) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /commands/command.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | const SERVICE = `uhppoted-mqtt` 9 | 10 | func helpOptions(flagset *flag.FlagSet) { 11 | flags := 0 12 | count := 0 13 | 14 | flag.VisitAll(func(f *flag.Flag) { 15 | count++ 16 | }) 17 | 18 | flagset.VisitAll(func(f *flag.Flag) { 19 | flags++ 20 | fmt.Printf(" --%-13s %s\n", f.Name, f.Usage) 21 | }) 22 | 23 | if count > 0 { 24 | fmt.Println() 25 | fmt.Println(" Options:") 26 | flag.VisitAll(func(f *flag.Flag) { 27 | fmt.Printf(" --%-13s %s\n", f.Name, f.Usage) 28 | }) 29 | } 30 | 31 | if flags > 0 { 32 | fmt.Println() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /commands/run_darwin.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | syslog "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | 11 | "github.com/uhppoted/uhppote-core/uhppote" 12 | "github.com/uhppoted/uhppoted-lib/config" 13 | "github.com/uhppoted/uhppoted-lib/eventlog" 14 | "github.com/uhppoted/uhppoted-mqtt/log" 15 | ) 16 | 17 | var RUN = Run{ 18 | configuration: "/usr/local/etc/com.github.uhppoted/uhppoted.conf", 19 | dir: "/usr/local/var/com.github.uhppoted", 20 | logLevel: "info", 21 | pidFile: fmt.Sprintf("/usr/local/var/com.github.uhppoted/%s.pid", SERVICE), 22 | logFile: fmt.Sprintf("/usr/local/var/com.github.uhppoted/logs/%s.log", SERVICE), 23 | logFileSize: 10, 24 | console: false, 25 | debug: false, 26 | } 27 | 28 | func (r *Run) FlagSet() *flag.FlagSet { 29 | flagset := flag.NewFlagSet("run", flag.ExitOnError) 30 | 31 | flagset.StringVar(&r.configuration, "config", r.configuration, "Sets the configuration file path") 32 | flagset.StringVar(&r.dir, "dir", r.dir, "Work directory") 33 | flagset.StringVar(&r.pidFile, "pid", r.pidFile, "Sets the service PID file path") 34 | flagset.StringVar(&r.logLevel, "log-level", r.logFile, "Sets the logging level (debug, info, warning or error)") 35 | flagset.StringVar(&r.logFile, "logfile", r.logFile, "Sets the log file path") 36 | flagset.IntVar(&r.logFileSize, "logfilesize", r.logFileSize, "Sets the log file size before forcing a log rotate") 37 | flagset.BoolVar(&r.console, "console", r.console, "Writes log entries to stdout") 38 | flagset.BoolVar(&r.debug, "debug", r.debug, "Displays internal information for diagnosing errors") 39 | 40 | return flagset 41 | } 42 | 43 | func (r *Run) Execute(args ...interface{}) error { 44 | log.Infof("", "%s service %s - %s (PID %d)\n", SERVICE, uhppote.VERSION, "MacOS", os.Getpid()) 45 | 46 | f := func(c *config.Config) error { 47 | return r.exec(c) 48 | } 49 | 50 | return r.execute(f) 51 | } 52 | 53 | func (cmd *Run) exec(c *config.Config) error { 54 | logger := syslog.New(os.Stdout, "", syslog.LstdFlags) 55 | interrupt := make(chan os.Signal, 1) 56 | 57 | signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) 58 | 59 | if !cmd.console { 60 | events := eventlog.Ticker{Filename: cmd.logFile, MaxSize: cmd.logFileSize} 61 | logger = syslog.New(&events, "", syslog.Ldate|syslog.Ltime|syslog.LUTC) 62 | rotate := make(chan os.Signal, 1) 63 | 64 | signal.Notify(rotate, syscall.SIGHUP) 65 | 66 | go func() { 67 | for { 68 | <-rotate 69 | log.Infof("", "Rotating %s log file '%s'\n", SERVICE, cmd.logFile) 70 | events.Rotate() 71 | } 72 | }() 73 | } 74 | 75 | log.SetLogger(logger) 76 | log.SetLevel(cmd.logLevel) 77 | 78 | cmd.run(c, logger, interrupt) 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /commands/run_linux.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | syslog "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | 11 | "github.com/uhppoted/uhppote-core/uhppote" 12 | "github.com/uhppoted/uhppoted-lib/config" 13 | "github.com/uhppoted/uhppoted-lib/eventlog" 14 | "github.com/uhppoted/uhppoted-mqtt/log" 15 | ) 16 | 17 | var RUN = Run{ 18 | configuration: "/etc/uhppoted/uhppoted.conf", 19 | dir: "/var/uhppoted", 20 | logLevel: "info", 21 | pidFile: fmt.Sprintf("/var/uhppoted/%s.pid", SERVICE), 22 | logFile: fmt.Sprintf("/var/log/uhppoted/%s.log", SERVICE), 23 | logFileSize: 10, 24 | console: false, 25 | debug: false, 26 | } 27 | 28 | func (r *Run) FlagSet() *flag.FlagSet { 29 | flagset := flag.NewFlagSet("run", flag.ExitOnError) 30 | 31 | flagset.StringVar(&r.configuration, "config", r.configuration, "Sets the configuration file path") 32 | flagset.StringVar(&r.dir, "dir", r.dir, "Work directory") 33 | flagset.StringVar(&r.pidFile, "pid", r.pidFile, "Sets the service PID file path") 34 | flagset.StringVar(&r.logLevel, "log-level", r.logFile, "Sets the logging level (debug, info, warning or error)") 35 | flagset.StringVar(&r.logFile, "logfile", r.logFile, "Sets the log file path") 36 | flagset.IntVar(&r.logFileSize, "logfilesize", r.logFileSize, "Sets the log file size before forcing a log rotate") 37 | flagset.BoolVar(&r.console, "console", r.console, "Writes log entries to stdout") 38 | flagset.BoolVar(&r.debug, "debug", r.debug, "Displays internal information for diagnosing errors") 39 | 40 | return flagset 41 | } 42 | 43 | func (r *Run) Execute(args ...interface{}) error { 44 | log.Infof("", "%s service %s - %s (PID %d)\n", SERVICE, uhppote.VERSION, "Linux", os.Getpid()) 45 | 46 | f := func(c *config.Config) error { 47 | return r.exec(c) 48 | } 49 | 50 | return r.execute(f) 51 | } 52 | 53 | func (cmd *Run) exec(c *config.Config) error { 54 | logger := syslog.New(os.Stdout, "", syslog.LstdFlags) 55 | interrupt := make(chan os.Signal, 1) 56 | 57 | signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) 58 | 59 | if !cmd.console { 60 | events := eventlog.Ticker{Filename: cmd.logFile, MaxSize: cmd.logFileSize} 61 | logger = syslog.New(&events, "", syslog.Ldate|syslog.Ltime|syslog.LUTC) 62 | rotate := make(chan os.Signal, 1) 63 | 64 | signal.Notify(rotate, syscall.SIGHUP) 65 | 66 | go func() { 67 | for { 68 | <-rotate 69 | syslog.Printf("Rotating %s log file '%s'\n", SERVICE, cmd.logFile) 70 | events.Rotate() 71 | } 72 | }() 73 | } 74 | 75 | log.SetLogger(logger) 76 | log.SetLevel(cmd.logLevel) 77 | 78 | cmd.run(c, logger, interrupt) 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /commands/sys_darwin.go: -------------------------------------------------------------------------------- 1 | package commands 2 | -------------------------------------------------------------------------------- /commands/sys_linux.go: -------------------------------------------------------------------------------- 1 | package commands 2 | -------------------------------------------------------------------------------- /commands/sys_windows.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "golang.org/x/sys/windows" 5 | "path/filepath" 6 | ) 7 | 8 | func workdir() string { 9 | if programData, err := windows.KnownFolderPath(windows.FOLDERID_ProgramData, windows.KF_FLAG_DEFAULT); err != nil { 10 | return `C:\uhppoted` 11 | } else { 12 | return filepath.Join(programData, "uhppoted") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /common/error.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type Error struct { 4 | Code int `json:"code"` 5 | Message string `json:"message,omitempty"` 6 | Debug string `json:"debug,omitempty"` 7 | } 8 | 9 | func MakeError(code int, message string, debug error) *Error { 10 | var dbg string 11 | 12 | if debug != nil { 13 | dbg = debug.Error() 14 | } 15 | 16 | return &Error{ 17 | Code: code, 18 | Message: message, 19 | Debug: dbg, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /device/controller.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/uhppoted/uhppoted-lib/uhppoted" 7 | "github.com/uhppoted/uhppoted-mqtt/common" 8 | ) 9 | 10 | func (d *Device) GetDevices(impl uhppoted.IUHPPOTED, request []byte) (interface{}, error) { 11 | rq := uhppoted.GetDevicesRequest{} 12 | 13 | response, err := impl.GetDevices(rq) 14 | if err != nil { 15 | return common.MakeError(StatusInternalServerError, "Error searching for active devices", err), err 16 | } 17 | 18 | return response, nil 19 | } 20 | 21 | func (d *Device) GetDevice(impl uhppoted.IUHPPOTED, request []byte) (interface{}, error) { 22 | body := struct { 23 | DeviceID *uhppoted.DeviceID `json:"device-id"` 24 | }{} 25 | 26 | if response, err := unmarshal(request, &body); err != nil { 27 | return response, err 28 | } 29 | 30 | if body.DeviceID == nil { 31 | return common.MakeError(StatusBadRequest, "Invalid/missing device ID", nil), fmt.Errorf("invalid/missing device ID") 32 | } 33 | 34 | rq := uhppoted.GetDeviceRequest{ 35 | DeviceID: *body.DeviceID, 36 | } 37 | 38 | response, err := impl.GetDevice(rq) 39 | if err != nil { 40 | return common.MakeError(StatusInternalServerError, fmt.Sprintf("Could not retrieve device information for %d", *body.DeviceID), err), err 41 | } 42 | 43 | return response, nil 44 | } 45 | 46 | // Resets a controller to the manufacturer default configuration. 47 | func (d *Device) RestoreDefaultParameters(impl uhppoted.IUHPPOTED, request []byte) (any, error) { 48 | body := struct { 49 | DeviceID *uhppoted.DeviceID `json:"device-id"` 50 | }{} 51 | 52 | if response, err := unmarshal(request, &body); err != nil { 53 | return response, err 54 | } 55 | 56 | if body.DeviceID == nil { 57 | return common.MakeError(StatusBadRequest, "Invalid/missing controller ID", nil), fmt.Errorf("invalid/missing controller ID") 58 | } 59 | 60 | controller := uint32(*body.DeviceID) 61 | 62 | if err := impl.RestoreDefaultParameters(controller); err != nil { 63 | return common.MakeError(StatusInternalServerError, fmt.Sprintf("Could not reset controller %d to manufacturer default configuration", controller), err), err 64 | } 65 | 66 | response := struct { 67 | DeviceID uint32 `json:"device-id"` 68 | Reset bool `json:"reset"` 69 | }{ 70 | DeviceID: controller, 71 | Reset: true, 72 | } 73 | 74 | return response, nil 75 | } 76 | -------------------------------------------------------------------------------- /device/device.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/uhppoted/uhppoted-lib/uhppoted" 7 | "github.com/uhppoted/uhppoted-mqtt/common" 8 | "github.com/uhppoted/uhppoted-mqtt/log" 9 | ) 10 | 11 | const ( 12 | StatusInternalServerError = uhppoted.StatusInternalServerError 13 | StatusBadRequest = uhppoted.StatusBadRequest 14 | StatusUnauthorized = uhppoted.StatusUnauthorized 15 | StatusNotFound = uhppoted.StatusNotFound 16 | ) 17 | 18 | type Device struct { 19 | AuthorizedCards []string 20 | } 21 | 22 | func SetProtocol(version string) { 23 | } 24 | 25 | func unmarshal(bytes []byte, request interface{}) (interface{}, error) { 26 | if err := json.Unmarshal(bytes, request); err != nil { 27 | return common.MakeError(StatusBadRequest, "Cannot parse request", err), err 28 | } 29 | 30 | return nil, nil 31 | } 32 | 33 | func debugf(format string, args ...any) { 34 | log.Debugf("mqttd", format, args...) 35 | } 36 | 37 | func infof(format string, args ...any) { 38 | log.Infof("mqttd", format, args...) 39 | } 40 | 41 | func warnf(format string, args ...any) { 42 | log.Warnf("mqttd", format, args...) 43 | } 44 | -------------------------------------------------------------------------------- /device/status.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/uhppoted/uhppote-core/types" 7 | "github.com/uhppoted/uhppoted-lib/uhppoted" 8 | "github.com/uhppoted/uhppoted-mqtt/common" 9 | ) 10 | 11 | type Status struct { 12 | DoorState map[uint8]bool `json:"door-states"` 13 | DoorButton map[uint8]bool `json:"door-buttons"` 14 | SystemError uint8 `json:"system-error"` 15 | SystemDateTime types.DateTime `json:"system-datetime"` 16 | SequenceId uint32 `json:"sequence-id"` 17 | SpecialInfo uint8 `json:"special-info"` 18 | RelayState uint8 `json:"relay-state"` 19 | InputState uint8 `json:"input-state"` 20 | Event any `json:"event,omitempty"` 21 | } 22 | 23 | func (d *Device) GetStatus(impl uhppoted.IUHPPOTED, request []byte) (interface{}, error) { 24 | body := struct { 25 | DeviceID uint32 `json:"device-id"` 26 | }{} 27 | 28 | if response, err := unmarshal(request, &body); err != nil { 29 | return response, err 30 | } 31 | 32 | if body.DeviceID == 0 { 33 | return common.MakeError(StatusBadRequest, "Invalid/missing device ID", nil), fmt.Errorf("invalid/missing device ID") 34 | } 35 | 36 | deviceID := body.DeviceID 37 | 38 | reply, err := impl.GetStatus(deviceID) 39 | if err != nil { 40 | return common.MakeError(uhppoted.StatusInternalServerError, fmt.Sprintf("Could not retrieve status for %d", deviceID), err), err 41 | } 42 | 43 | response := struct { 44 | DeviceID uint32 `json:"device-id"` 45 | Status Status `json:"status"` 46 | }{ 47 | DeviceID: deviceID, 48 | Status: Status{ 49 | DoorState: map[uint8]bool{}, 50 | DoorButton: map[uint8]bool{}, 51 | SystemError: reply.SystemError, 52 | SystemDateTime: reply.SystemDateTime, 53 | SequenceId: reply.SequenceId, 54 | SpecialInfo: reply.SpecialInfo, 55 | RelayState: reply.RelayState, 56 | InputState: reply.InputState, 57 | Event: nil, 58 | }, 59 | } 60 | 61 | for k, v := range reply.DoorState { 62 | response.Status.DoorState[k] = v 63 | } 64 | 65 | for k, v := range reply.DoorButton { 66 | response.Status.DoorButton[k] = v 67 | } 68 | 69 | if !reply.Event.IsZero() { 70 | event := Transmogrify(reply.Event) 71 | response.Status.Event = event 72 | } 73 | 74 | return &response, nil 75 | } 76 | -------------------------------------------------------------------------------- /device/tasklist.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/uhppoted/uhppote-core/types" 7 | "github.com/uhppoted/uhppoted-lib/uhppoted" 8 | "github.com/uhppoted/uhppoted-mqtt/common" 9 | ) 10 | 11 | func (d *Device) PutTaskList(impl uhppoted.IUHPPOTED, request []byte) (interface{}, error) { 12 | body := struct { 13 | DeviceID *uint32 `json:"device-id"` 14 | Tasks []types.Task `json:"tasks"` 15 | }{} 16 | 17 | if response, err := unmarshal(request, &body); err != nil { 18 | return response, err 19 | } 20 | 21 | if body.DeviceID == nil { 22 | return common.MakeError(uhppoted.StatusBadRequest, "Invalid/missing device ID", nil), fmt.Errorf("invalid/missing device ID") 23 | } 24 | 25 | rq := uhppoted.PutTaskListRequest{ 26 | DeviceID: *body.DeviceID, 27 | Tasks: body.Tasks, 28 | } 29 | 30 | response, _, err := impl.PutTaskList(rq) 31 | if err != nil { 32 | return common.MakeError(uhppoted.StatusInternalServerError, fmt.Sprintf("%d: could not update task list", *body.DeviceID), err), err 33 | } 34 | 35 | warnings := []string{} 36 | for _, w := range response.Warnings { 37 | warnings = append(warnings, fmt.Sprintf("%v", w)) 38 | } 39 | 40 | return struct { 41 | DeviceID uint32 `json:"device-id"` 42 | Warnings []string `json:"warnings"` 43 | }{ 44 | DeviceID: uint32(response.DeviceID), 45 | Warnings: warnings, 46 | }, nil 47 | } 48 | -------------------------------------------------------------------------------- /device/time.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/uhppoted/uhppote-core/types" 7 | "github.com/uhppoted/uhppoted-lib/uhppoted" 8 | "github.com/uhppoted/uhppoted-mqtt/common" 9 | ) 10 | 11 | func (d *Device) GetTime(impl uhppoted.IUHPPOTED, request []byte) (interface{}, error) { 12 | body := struct { 13 | DeviceID *uhppoted.DeviceID `json:"device-id"` 14 | }{} 15 | 16 | if response, err := unmarshal(request, &body); err != nil { 17 | return response, err 18 | } 19 | 20 | if body.DeviceID == nil { 21 | return common.MakeError(StatusBadRequest, "Invalid/missing device ID", nil), fmt.Errorf("invalid/missing device ID") 22 | } 23 | 24 | rq := uhppoted.GetTimeRequest{ 25 | DeviceID: *body.DeviceID, 26 | } 27 | 28 | response, err := impl.GetTime(rq) 29 | if err != nil { 30 | return common.MakeError(StatusInternalServerError, fmt.Sprintf("Could not retrieve device time for %d", *body.DeviceID), err), err 31 | } 32 | 33 | return response, nil 34 | } 35 | 36 | func (d *Device) SetTime(impl uhppoted.IUHPPOTED, request []byte) (interface{}, error) { 37 | body := struct { 38 | DeviceID *uhppoted.DeviceID `json:"device-id"` 39 | DateTime *types.DateTime `json:"date-time"` 40 | }{} 41 | 42 | if response, err := unmarshal(request, &body); err != nil { 43 | return response, err 44 | } 45 | 46 | if body.DeviceID == nil { 47 | return common.MakeError(StatusBadRequest, "Invalid/missing device ID", nil), fmt.Errorf("invalid/missing device ID") 48 | } 49 | 50 | if body.DateTime == nil { 51 | return common.MakeError(StatusBadRequest, "Invalid/missing datetime", nil), fmt.Errorf("invalid/missing datetime") 52 | } 53 | 54 | rq := uhppoted.SetTimeRequest{ 55 | DeviceID: *body.DeviceID, 56 | DateTime: *body.DateTime, 57 | } 58 | 59 | response, err := impl.SetTime(rq) 60 | if err != nil { 61 | return common.MakeError(StatusInternalServerError, fmt.Sprintf("Could not set device time for %d", *body.DeviceID), err), err 62 | } 63 | 64 | if response == nil { 65 | return nil, nil 66 | } 67 | 68 | return response, nil 69 | } 70 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 uhppoted@twyst.co.za. All rights reserved. 2 | // Use of this source code is governed by an MIT-style license 3 | // that can be found in the LICENSE file. 4 | 5 | /* 6 | Package uhppoted-mqtt implements an MQTT client for the UHPPOTE TCP/IP Wiegand-26 access controllers. 7 | 8 | The MQTT client wraps the low level UDP API implemented by uhppote-core in an set of MQTT messages that 9 | add support for authentication and authorisation as well as adding functionality to manage access control 10 | lists and events. 11 | 12 | The MQTT client is based on Eclipse Paho and at this point in time supports only MQTT v3.1. 13 | */ 14 | package mqtt 15 | -------------------------------------------------------------------------------- /docker/compose.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uhppoted/uhppoted-mqtt/0517296221de1c3c717d26d5b0b0dd70a07a9d56/docker/compose.zip -------------------------------------------------------------------------------- /docker/compose/broker.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDiTCCAnGgAwIBAgIEMdXSnTANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJN 3 | UTEPMA0GA1UECBMGZG9ja2VyMQ8wDQYDVQQHEwZkb2NrZXIxETAPBgNVBAoTCHVo 4 | cHBvdGVkMQ8wDQYDVQQLEwZoaXZlbXExEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0y 5 | MzA3MjUxOTM5MTZaFw0yNDA3MjQxOTM5MTZaMGcxCzAJBgNVBAYTAk1RMQ8wDQYD 6 | VQQIEwZkb2NrZXIxDzANBgNVBAcTBmRvY2tlcjERMA8GA1UEChMIdWhwcG90ZWQx 7 | DzANBgNVBAsTBmhpdmVtcTESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG 8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkPDLuZM7dZmk0kG6F1R6XfpOxza8y+MaCzrb 9 | 3mZveZ5CT+08y66dKwPjhdbSG8/55joZrL8xHM+NZQfmn+CSMybDhR1jXVJJHQAa 10 | lWPTu2Bjo3JK38syREJm8S+6KMiFpf15SFnRUuvWG8it/SQhVA+Qqk5YYDu+k8bd 11 | jCO1ro51ELkbzRFAxeRjHuoauunV9e0l9Q+MehzCs2NMnLWlPF5lZMqJ4GIjJuUU 12 | Dc6iuFRaz6c6ykInkTGkxKwj1LUnBdmngDHUvb7CQxETtxGHRm57FXyfnpPu/Fdu 13 | fuTiwL1JGWMqZEk+/LkU32EFdwDprUcHjm2Hbag4s/WfNCUwrwIDAQABoz0wOzAd 14 | BgNVHQ4EFgQUaUIf73xGLvwJr3NWDvM+exx12fIwGgYDVR0RBBMwEYIJbG9jYWxo 15 | b3N0hwTAqAFkMA0GCSqGSIb3DQEBCwUAA4IBAQB4IJjx6mY+OOmiHjIUQiZe7v/X 16 | aG3DkdSlob1pluIg6PR8+SKqsKGsTtQcVsOm6mebyqWSELIgOHNFbVqVWIvzk1uZ 17 | 0skhV+KKcNRXt5UkvSqZJUIIIGztXLggUwO2+38/DFaT5To2PVw4j/X7EPY+MtSL 18 | kjq+NozxHjrrUGDd4Tb1pQy8EcO2aEref9iPtUc1v6OeFFGldWvdCYfoavGK/vO8 19 | bWFj32BA3Ue/DIXEUasekopr+WAePAS3njVwENoDEb8efCYQA5Xp3BVagPSAGx9r 20 | PLl3pRfOhkKB4kuplZcSXF1v2wPbGLgGS/Zj2WyZXHz/iuAit1Oelhp6G/+y 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /docker/compose/client.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDwzCCAqugAwIBAgIUbIeI3tBbyO8WYDuRJEA4HaD9y0IwDQYJKoZIhvcNAQEL 3 | BQAwcTELMAkGA1UEBhMCTVExEjAQBgNVBAgMCWxvY2FsaG9zdDESMBAGA1UEBwwJ 4 | bG9jYWxob3N0MRIwEAYDVQQKDAlsb2NhbGhvc3QxEjAQBgNVBAsMCWxvY2FsaG9z 5 | dDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIyMDEyNzE4MzAwMVoXDTIzMDEyNzE4 6 | MzAwMVowcTELMAkGA1UEBhMCTVExEjAQBgNVBAgMCWxvY2FsaG9zdDESMBAGA1UE 7 | BwwJbG9jYWxob3N0MRIwEAYDVQQKDAlsb2NhbGhvc3QxEjAQBgNVBAsMCWxvY2Fs 8 | aG9zdDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 9 | MIIBCgKCAQEA5pyAAGQQQQuPdWdbLcifPoLCC8GGp1/miAAGbDmlUaoMFWcvU74a 10 | LIGma3ui+wbkd5cj4ASQ1uA0rPJLUdOPRNwzWxtfIIAJLhnO6Ca9ba4S/fwMeWS8 11 | un1EfU37xWWzDXD6aJDlo3py6A58+6gdjw723muQ5VOHP9PMBfKvRawGH6hao0Ff 12 | 1ycwHJEIVmCAeZqsf/4IXVneoM/Dvw3rXydMpFV2mBT8RhTpGnFYJDn2MDC2mCyS 13 | xHtxx2IOv8CumN249C8KHwYXfQVyVHebCY8yNgyVd/QZVDDBnmnAIbp/7n23auzz 14 | VPiZaqIhYbw0rwnabo7kYjdDfpIotMPNQwIDAQABo1MwUTAdBgNVHQ4EFgQU6GUN 15 | mGDLV3YlWgJTZQ4cr0GWjHowHwYDVR0jBBgwFoAU6GUNmGDLV3YlWgJTZQ4cr0GW 16 | jHowDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEABDFz9KV48Wek 17 | i0i8CollU2eJmozZshSUVZAFpIJPXa+YBRC5iPl3ybiPp+nnxZWzcMQKs5z0O2bh 18 | 6kNJuASsguNjgOYrYhzDe1Ra06fEwm2R43ivFKyXjJn0wJQjuTTSKtI0gSM8Sos3 19 | dOmfTtoxW2ZF0HYZ//G1odkBcUFCF3Xl+DP3opR1VfvRpI3xdpkg7myCFOmFqfbL 20 | lYQDdWIBo3NAxTHNOnaRHm5cv40G4e6A7MHzElvuQ8HAeG3a5vWRXHSEgmGDyG7n 21 | 99goN8EOfWtmcwigdzVYqIvjT4cf3JrQuWlWy9zpeUft33KwYXBlmvPFAsQfKZQD 22 | 3xmGQ4r7ng== 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /docker/compose/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDmnIAAZBBBC491 3 | Z1styJ8+gsILwYanX+aIAAZsOaVRqgwVZy9TvhosgaZre6L7BuR3lyPgBJDW4DSs 4 | 8ktR049E3DNbG18ggAkuGc7oJr1trhL9/Ax5ZLy6fUR9TfvFZbMNcPpokOWjenLo 5 | Dnz7qB2PDvbea5DlU4c/08wF8q9FrAYfqFqjQV/XJzAckQhWYIB5mqx//ghdWd6g 6 | z8O/DetfJ0ykVXaYFPxGFOkacVgkOfYwMLaYLJLEe3HHYg6/wK6Y3bj0LwofBhd9 7 | BXJUd5sJjzI2DJV39BlUMMGeacAhun/ufbdq7PNU+JlqoiFhvDSvCdpujuRiN0N+ 8 | kii0w81DAgMBAAECggEASlS/XWDjbFPRlcIOvZ8g1QFIkol6YZCjucpoR5H9G2l4 9 | I53yv0dTIG1ZuuNLESaTlYh948MU/G2gDO68IE/Uqhlf5EnaS5t5WgavjlyOSkAF 10 | lyAa1TYHLwUZgloUgPfa0sZLsFCj9CGP0AR7fJIflPD52Y9KUOCvg389D929aPMB 11 | DNIG+vmH3xjYai94jTPesAlx2FNSyDnhBpDNsQXvoXNX12Sqgiyslpu4hQhPF5xK 12 | cdlnnZQ76NG4Dm7d+CRhZcLnIPQfKSheDdWX/Tsop3MGQSM0SDyYvxTMy/YFb6nW 13 | hd4M1DgsxnBaqOkbFUvILEWlrmWGrkpmmxiT5XU4YQKBgQD3mQ645C43QbI9GzzJ 14 | 0/bTbUIBGeqJysjeRHVs4HrgGpRMZvUrycNE7ZpKXY3nOQyvOeA/TLO/4sYTQrBy 15 | ET9XWx8nifcM2jNoNfi+VdLpEk+Qr4gSh2j3BDhnbBNXwVRwx6NWc6yN9zzEhRzx 16 | wMEDArWM5UCYE6VNmM11Y7MXRQKBgQDub+BPz3QTS56k7DHwT1zhNYyQbMBq/mqw 17 | wiEu86pzKJ2/M2JOHcfdy6dG16MQWynDFRkjM6wXzSKTBAECo1PNsJRiJyMxH078 18 | YCoOqI+V3eZRcRLeCDzJEpTI5+SquCX/D7kj6E8ScOqY6rXCPRYtW7E1AdNymtuV 19 | jujsirB25wKBgQDuzIgOesS4NnyZwvNWFUExmWbXeQ9j5ljlRlGauMbJ7dip94Lu 20 | wKG8kQ0GPETohXVeawuFAY07vJQdb2yOF2Rn9FKP59iGZMy/7Y4CxPOuJyLmAOXU 21 | ORVbaI6d8PaW5Ld0Lpeygc4i/hl10Iyh/a4qISrIwTMTeZ8sv/spBQdtoQKBgQDs 22 | N7vzdaQahZ066KEk4Ysztph2cLdEiJ7sVLcJS+9+vStixCZNG6Xprxwey/1Jc/dw 23 | xJMt08BXqCvrmFjj4ylgKuMmRn3P6aOX1jF0ajwuDjNiVfKKM4D890Kds1dQZrWF 24 | 9m7GCx1jOKjCLC07rMcu0ptB13hDHvYSOg26YkAm2wKBgQDChS6ylw4rjvt8CsX+ 25 | vY5T3Lg/zTcb2ITE13rQPBue1Zwoch+19TGR3jeYr53KKD/WyFjK8SST76US27xi 26 | RGqh9QtB9SFNFMSnrAPZH6HoFVvRY1oQDNh4fNOH1+MIVcR21MZYb7d3SIlix9zh 27 | rBn1O+r4BJsqls+hnWR7NNAtUg== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /docker/compose/compose.yml: -------------------------------------------------------------------------------- 1 | version: "0.0" 2 | 3 | services: 4 | uhppoted-mqtt: 5 | container_name: uhppoted-mqtt 6 | restart: unless-stopped 7 | image: ghcr.io/uhppoted/mqttd:latest 8 | volumes: 9 | - uhppoted:/usr/local/etc/uhppoted 10 | network_mode: "bridge" 11 | ports: 12 | - "60001:60001/udp" 13 | 14 | volumes: 15 | uhppoted: 16 | name: uhppoted 17 | -------------------------------------------------------------------------------- /docker/compose/uhppoted.conf: -------------------------------------------------------------------------------- 1 | # SYSTEM 2 | # bind.address = 0.0.0.0:0 3 | # broadcast.address = 255.255.255.255:60000 4 | # listen.address = 0.0.0.0:60001 5 | 6 | # MQTT 7 | mqtt.server.ID = uhppoted 8 | mqtt.connection.broker = tls://192.168.1.100:8883 9 | mqtt.connection.client.ID = uhppoted 10 | mqtt.connection.username = mqttd 11 | mqtt.connection.password = qwertyuiop 12 | mqtt.connection.broker.certificate = /usr/local/etc/uhppoted/mqtt/broker.pem 13 | mqtt.connection.client.certificate = /usr/local/etc/uhppoted/mqtt/client.cert 14 | mqtt.connection.client.key = /usr/local/etc/uhppoted/mqtt/client.key 15 | 16 | mqtt.topic.root = uhppoted/gateway 17 | mqtt.topic.requests = ./requests 18 | mqtt.topic.replies = ./replies 19 | mqtt.topic.events = ./events 20 | mqtt.topic.system = ./system 21 | mqtt.alerts.qos = 2 22 | mqtt.events.key = events 23 | mqtt.system.key = system 24 | mqtt.events.index.filepath = /var/uhppoted/mqtt/events.retrieved 25 | 26 | mqtt.permissions.enabled = false 27 | 28 | mqtt.security.authentication = NONE 29 | ; mqtt.security.HMAC.required = false 30 | ; mqtt.security.HMAC.key = ThisAndThat 31 | ; mqtt.security.rsa.keys = /etc/uhppoted/mqtt/rsa 32 | ; mqtt.security.nonce.required = true 33 | ; mqtt.security.nonce.server = /var/uhppoted/mqtt/nonce 34 | ; mqtt.security.nonce.clients = /var/uhppoted/mqtt/nonce.counters 35 | mqtt.security.outgoing.sign = false 36 | mqtt.security.outgoing.encrypt = false 37 | 38 | 39 | # DEVICES 40 | # 41 | # Example configuration for UTO311-L04 with serial number 405419896 42 | # UT0311-L0x.405419896.address = 192.168.1.100:60000 43 | # UT0311-L0x.405419896.door.1 = Great Hall 44 | # UT0311-L0x.405419896.door.2 = Kitchen 45 | # UT0311-L0x.405419896.door.3 = Dungeon 46 | # UT0311-L0x.405419896.door.4 = Hogsmeade 47 | -------------------------------------------------------------------------------- /docker/dev/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | EXPOSE 60001/udp 4 | 5 | RUN mkdir -p /usr/local/etc/uhppoted/mqtt 6 | RUN mkdir -p /usr/local/etc/uhppoted/mqtt/rsa 7 | RUN mkdir -p /usr/local/etc/uhppoted/mqtt/rsa/signing 8 | RUN mkdir -p /usr/local/etc/uhppoted/mqtt/rsa/encryption 9 | 10 | COPY uhppoted.conf /usr/local/etc/uhppoted 11 | 12 | WORKDIR /opt/uhppoted 13 | COPY uhppoted-mqtt . 14 | 15 | ENTRYPOINT /opt/uhppoted/uhppoted-mqtt --debug --config /usr/local/etc/uhppoted/uhppoted.conf --console 16 | -------------------------------------------------------------------------------- /docker/dev/uhppoted.conf: -------------------------------------------------------------------------------- 1 | # SYSTEM 2 | # bind.address = 0.0.0.0:0 3 | # broadcast.address = 255.255.255.255:60000 4 | # listen.address = 0.0.0.0:60001 5 | 6 | # MQTT 7 | mqtt.server.ID = uhppoted 8 | mqtt.connection.broker = tcp://192.168.1.100:1883 9 | mqtt.connection.client.ID = uhppoted 10 | mqtt.connection.username = mqttd 11 | mqtt.connection.password = qwertyuiop 12 | ; mqtt.connection.broker = tls://192.168.1.100:8883 13 | ; mqtt.connection.broker.certificate = /usr/local/etc/uhppoted/mqtt/broker.pem 14 | ; mqtt.connection.client.certificate = /usr/local/etc/uhppoted/mqtt/client.cert 15 | ; mqtt.connection.client.key = /usr/local/etc/uhppoted/mqtt/client.key 16 | 17 | mqtt.topic.root = uhppoted/gateway 18 | mqtt.topic.requests = ./requests 19 | mqtt.topic.replies = ./replies 20 | mqtt.topic.events = ./events 21 | mqtt.topic.system = ./system 22 | mqtt.alerts.qos = 2 23 | mqtt.events.key = events 24 | mqtt.system.key = system 25 | mqtt.events.index.filepath = /usr/local/etc/uhppoted/mqtt/events.retrieved 26 | 27 | mqtt.permissions.enabled = false 28 | 29 | mqtt.security.authentication = NONE 30 | ; mqtt.security.HMAC.required = false 31 | ; mqtt.security.HMAC.key = ThisAndThat 32 | ; mqtt.security.rsa.keys = /usr/local/etc/uhppoted/mqtt/rsa 33 | ; mqtt.security.nonce.required = true 34 | ; mqtt.security.nonce.server = /usr/local/etc/uhppoted/mqtt/nonce 35 | ; mqtt.security.nonce.clients = /usr/local/etc/uhppoted/mqtt/nonce.counters 36 | mqtt.security.outgoing.sign = false 37 | mqtt.security.outgoing.encrypt = false 38 | 39 | 40 | # DEVICES 41 | UT0311-L0x.405419896.address = 192.168.1.100:60000 42 | UT0311-L0x.405419896.door.1 = Great Hall 43 | UT0311-L0x.405419896.door.2 = Kitchen 44 | UT0311-L0x.405419896.door.3 = Dungeon 45 | UT0311-L0x.405419896.door.4 = Hogsmeade 46 | 47 | UT0311-L0x.303986753.address = 192.168.1.100:60000 48 | UT0311-L0x.303986753.door.1 = Gryffindor 49 | UT0311-L0x.303986753.door.2 = Hufflepuff 50 | UT0311-L0x.303986753.door.3 = Ravenclaw 51 | UT0311-L0x.303986753.door.4 = Slytherin 52 | 53 | -------------------------------------------------------------------------------- /docker/doc/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | RUN mkdir -p /etc/uhppoted 4 | RUN mkdir -p /etc/uhppoted/mqtt 5 | RUN mkdir -p /etc/uhppoted/mqtt/rsa 6 | RUN mkdir -p /etc/uhppoted/mqtt/rsa/signing 7 | RUN mkdir -p /etc/uhppoted/mqtt/rsa/encryption 8 | 9 | RUN mkdir -p /var/uhppoted 10 | 11 | # uhppoted configuration file 12 | COPY uhppoted.conf /etc/uhppoted 13 | 14 | # MQTT broker TLS certificate 15 | # COPY broker.pem /etc/uhppoted 16 | 17 | # MQTT broker TLS client key and certificate (if client authentication enabled) 18 | # COPY secure/client.key /etc/uhppoted/mqtt 19 | # COPY secure/client.cert /etc/uhppoted/mqtt 20 | 21 | # uhppoted-mqtt RSA signing and encryption keys (for public MQTT brokers) 22 | # COPY secure/rsa/signing/mqttd.key /etc/uhppoted/mqtt/rsa/signing 23 | # COPY secure/rsa/encryption/mqttd.key /etc/uhppoted/mqtt/rsa/encryption 24 | 25 | WORKDIR /opt/uhppoted 26 | COPY uhppoted-mqtt . 27 | 28 | ENTRYPOINT /opt/uhppoted/uhppoted-mqtt --console 29 | -------------------------------------------------------------------------------- /docker/doc/broker.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDiTCCAnGgAwIBAgIEMdXSnTANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJN 3 | UTEPMA0GA1UECBMGZG9ja2VyMQ8wDQYDVQQHEwZkb2NrZXIxETAPBgNVBAoTCHVo 4 | cHBvdGVkMQ8wDQYDVQQLEwZoaXZlbXExEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0y 5 | MzA3MjUxOTM5MTZaFw0yNDA3MjQxOTM5MTZaMGcxCzAJBgNVBAYTAk1RMQ8wDQYD 6 | VQQIEwZkb2NrZXIxDzANBgNVBAcTBmRvY2tlcjERMA8GA1UEChMIdWhwcG90ZWQx 7 | DzANBgNVBAsTBmhpdmVtcTESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG 8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkPDLuZM7dZmk0kG6F1R6XfpOxza8y+MaCzrb 9 | 3mZveZ5CT+08y66dKwPjhdbSG8/55joZrL8xHM+NZQfmn+CSMybDhR1jXVJJHQAa 10 | lWPTu2Bjo3JK38syREJm8S+6KMiFpf15SFnRUuvWG8it/SQhVA+Qqk5YYDu+k8bd 11 | jCO1ro51ELkbzRFAxeRjHuoauunV9e0l9Q+MehzCs2NMnLWlPF5lZMqJ4GIjJuUU 12 | Dc6iuFRaz6c6ykInkTGkxKwj1LUnBdmngDHUvb7CQxETtxGHRm57FXyfnpPu/Fdu 13 | fuTiwL1JGWMqZEk+/LkU32EFdwDprUcHjm2Hbag4s/WfNCUwrwIDAQABoz0wOzAd 14 | BgNVHQ4EFgQUaUIf73xGLvwJr3NWDvM+exx12fIwGgYDVR0RBBMwEYIJbG9jYWxo 15 | b3N0hwTAqAFkMA0GCSqGSIb3DQEBCwUAA4IBAQB4IJjx6mY+OOmiHjIUQiZe7v/X 16 | aG3DkdSlob1pluIg6PR8+SKqsKGsTtQcVsOm6mebyqWSELIgOHNFbVqVWIvzk1uZ 17 | 0skhV+KKcNRXt5UkvSqZJUIIIGztXLggUwO2+38/DFaT5To2PVw4j/X7EPY+MtSL 18 | kjq+NozxHjrrUGDd4Tb1pQy8EcO2aEref9iPtUc1v6OeFFGldWvdCYfoavGK/vO8 19 | bWFj32BA3Ue/DIXEUasekopr+WAePAS3njVwENoDEb8efCYQA5Xp3BVagPSAGx9r 20 | PLl3pRfOhkKB4kuplZcSXF1v2wPbGLgGS/Zj2WyZXHz/iuAit1Oelhp6G/+y 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /docker/doc/secure/client.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDwzCCAqugAwIBAgIUbIeI3tBbyO8WYDuRJEA4HaD9y0IwDQYJKoZIhvcNAQEL 3 | BQAwcTELMAkGA1UEBhMCTVExEjAQBgNVBAgMCWxvY2FsaG9zdDESMBAGA1UEBwwJ 4 | bG9jYWxob3N0MRIwEAYDVQQKDAlsb2NhbGhvc3QxEjAQBgNVBAsMCWxvY2FsaG9z 5 | dDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIyMDEyNzE4MzAwMVoXDTIzMDEyNzE4 6 | MzAwMVowcTELMAkGA1UEBhMCTVExEjAQBgNVBAgMCWxvY2FsaG9zdDESMBAGA1UE 7 | BwwJbG9jYWxob3N0MRIwEAYDVQQKDAlsb2NhbGhvc3QxEjAQBgNVBAsMCWxvY2Fs 8 | aG9zdDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 9 | MIIBCgKCAQEA5pyAAGQQQQuPdWdbLcifPoLCC8GGp1/miAAGbDmlUaoMFWcvU74a 10 | LIGma3ui+wbkd5cj4ASQ1uA0rPJLUdOPRNwzWxtfIIAJLhnO6Ca9ba4S/fwMeWS8 11 | un1EfU37xWWzDXD6aJDlo3py6A58+6gdjw723muQ5VOHP9PMBfKvRawGH6hao0Ff 12 | 1ycwHJEIVmCAeZqsf/4IXVneoM/Dvw3rXydMpFV2mBT8RhTpGnFYJDn2MDC2mCyS 13 | xHtxx2IOv8CumN249C8KHwYXfQVyVHebCY8yNgyVd/QZVDDBnmnAIbp/7n23auzz 14 | VPiZaqIhYbw0rwnabo7kYjdDfpIotMPNQwIDAQABo1MwUTAdBgNVHQ4EFgQU6GUN 15 | mGDLV3YlWgJTZQ4cr0GWjHowHwYDVR0jBBgwFoAU6GUNmGDLV3YlWgJTZQ4cr0GW 16 | jHowDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEABDFz9KV48Wek 17 | i0i8CollU2eJmozZshSUVZAFpIJPXa+YBRC5iPl3ybiPp+nnxZWzcMQKs5z0O2bh 18 | 6kNJuASsguNjgOYrYhzDe1Ra06fEwm2R43ivFKyXjJn0wJQjuTTSKtI0gSM8Sos3 19 | dOmfTtoxW2ZF0HYZ//G1odkBcUFCF3Xl+DP3opR1VfvRpI3xdpkg7myCFOmFqfbL 20 | lYQDdWIBo3NAxTHNOnaRHm5cv40G4e6A7MHzElvuQ8HAeG3a5vWRXHSEgmGDyG7n 21 | 99goN8EOfWtmcwigdzVYqIvjT4cf3JrQuWlWy9zpeUft33KwYXBlmvPFAsQfKZQD 22 | 3xmGQ4r7ng== 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /docker/doc/secure/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDmnIAAZBBBC491 3 | Z1styJ8+gsILwYanX+aIAAZsOaVRqgwVZy9TvhosgaZre6L7BuR3lyPgBJDW4DSs 4 | 8ktR049E3DNbG18ggAkuGc7oJr1trhL9/Ax5ZLy6fUR9TfvFZbMNcPpokOWjenLo 5 | Dnz7qB2PDvbea5DlU4c/08wF8q9FrAYfqFqjQV/XJzAckQhWYIB5mqx//ghdWd6g 6 | z8O/DetfJ0ykVXaYFPxGFOkacVgkOfYwMLaYLJLEe3HHYg6/wK6Y3bj0LwofBhd9 7 | BXJUd5sJjzI2DJV39BlUMMGeacAhun/ufbdq7PNU+JlqoiFhvDSvCdpujuRiN0N+ 8 | kii0w81DAgMBAAECggEASlS/XWDjbFPRlcIOvZ8g1QFIkol6YZCjucpoR5H9G2l4 9 | I53yv0dTIG1ZuuNLESaTlYh948MU/G2gDO68IE/Uqhlf5EnaS5t5WgavjlyOSkAF 10 | lyAa1TYHLwUZgloUgPfa0sZLsFCj9CGP0AR7fJIflPD52Y9KUOCvg389D929aPMB 11 | DNIG+vmH3xjYai94jTPesAlx2FNSyDnhBpDNsQXvoXNX12Sqgiyslpu4hQhPF5xK 12 | cdlnnZQ76NG4Dm7d+CRhZcLnIPQfKSheDdWX/Tsop3MGQSM0SDyYvxTMy/YFb6nW 13 | hd4M1DgsxnBaqOkbFUvILEWlrmWGrkpmmxiT5XU4YQKBgQD3mQ645C43QbI9GzzJ 14 | 0/bTbUIBGeqJysjeRHVs4HrgGpRMZvUrycNE7ZpKXY3nOQyvOeA/TLO/4sYTQrBy 15 | ET9XWx8nifcM2jNoNfi+VdLpEk+Qr4gSh2j3BDhnbBNXwVRwx6NWc6yN9zzEhRzx 16 | wMEDArWM5UCYE6VNmM11Y7MXRQKBgQDub+BPz3QTS56k7DHwT1zhNYyQbMBq/mqw 17 | wiEu86pzKJ2/M2JOHcfdy6dG16MQWynDFRkjM6wXzSKTBAECo1PNsJRiJyMxH078 18 | YCoOqI+V3eZRcRLeCDzJEpTI5+SquCX/D7kj6E8ScOqY6rXCPRYtW7E1AdNymtuV 19 | jujsirB25wKBgQDuzIgOesS4NnyZwvNWFUExmWbXeQ9j5ljlRlGauMbJ7dip94Lu 20 | wKG8kQ0GPETohXVeawuFAY07vJQdb2yOF2Rn9FKP59iGZMy/7Y4CxPOuJyLmAOXU 21 | ORVbaI6d8PaW5Ld0Lpeygc4i/hl10Iyh/a4qISrIwTMTeZ8sv/spBQdtoQKBgQDs 22 | N7vzdaQahZ066KEk4Ysztph2cLdEiJ7sVLcJS+9+vStixCZNG6Xprxwey/1Jc/dw 23 | xJMt08BXqCvrmFjj4ylgKuMmRn3P6aOX1jF0ajwuDjNiVfKKM4D890Kds1dQZrWF 24 | 9m7GCx1jOKjCLC07rMcu0ptB13hDHvYSOg26YkAm2wKBgQDChS6ylw4rjvt8CsX+ 25 | vY5T3Lg/zTcb2ITE13rQPBue1Zwoch+19TGR3jeYr53KKD/WyFjK8SST76US27xi 26 | RGqh9QtB9SFNFMSnrAPZH6HoFVvRY1oQDNh4fNOH1+MIVcR21MZYb7d3SIlix9zh 27 | rBn1O+r4BJsqls+hnWR7NNAtUg== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /docker/doc/secure/rsa/encryption/mqttd.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDvmv0K0WQHN/wW 3 | HgVxN5/adjhdMx0WKVWOFEVefN45/PjGIVOOKK80TS6Z/tJnIePD3tJRfi+gyI7D 4 | CqFzfxM4NMYb/N3FtMbclq/8ahbNftFdMpdZTg0pYI90gfikHOEOS0Wc3HhRaCof 5 | 8WUXChdagdj7rsusCcLQg58VUHpfYLMYzll5zy7DhUqK18LVSXnfKX3Z+kHYgV/O 6 | G2r7hcEJqNYXSXKLEznoU0+3rWKLU7r+dkOSbjrNelHJo/KOr0NH+97s742TSryH 7 | tHT7w4YC9VhR9q1/IARzW+q5a36sVfM4KlKd5jYI84dBNs+eNUCjILsHoSVdDAt/ 8 | NcT0ddOfAgMBAAECggEBAMgVbe9FsbkReDHj+fl0UMm2ZRT25PgnpikSJmdj8JMN 9 | +5RAKi0RLlWHij3QfFThCPj8rjadIBqswAKBcxcma59I+pJOk8dQUavNLcKjuMz7 10 | dMvVvms18Wm3F7RcAIzTDODJ3KTDurmIOX0ZMKiPpbbPralmavAL6qIC96pt3PKd 11 | COqlLSv52X6CN18y9qgITqq+9NQmcp8I6VooFFUhl78m0zRnf6vrRg64DxuaOc6c 12 | PXAD2B2LOptnnTwFl4QMR9eVTJkmfn+ZcfxjG27G/8PE5SuezvNXvWLePzmgpkQn 13 | +BUFLmwGb+icJxk8WQNQJ/imPh/hXnNwqpvPDwEoWHkCgYEA+dD/mAvefig5l9yE 14 | V2HVqzNfIr8CHhFjy+bTP6KSYfnQ71Gg+h7FOlQgGbFdiWrsfz+1NFV7Sr/+t1P+ 15 | CTCn9Zq759pxjDKlZXIE2ILuQ9ad6TzgR9P0j5NGr8bE/PVzOrmlUVboROOnkcbZ 16 | QaqC58BJ6OHdiswXJoEo6x63e8UCgYEA9YlJWyBEDNO6xtcWGX8foeGXV5i2AuLc 17 | jhWAObF0ThxhvBrqmX9hgAQtAlZCDS+pcmiF3dIDgD8YGrTz/YVSnHvS7iZOiX2c 18 | X/WPBHReUbHzDdaueSfOOHpVe80L5J84P3A7gE91mXha1LQpbKEO+ZdyVY8y1bQT 19 | pCbT7GP/VBMCgYBm+wjiHMJzLxHO0FCd1O7HzD2DjUnKK1EAVP7wVIwTZ3ABt5ys 20 | ftK+4L762Gq+ox0qt5BzKmnQvqS53h3ym+QhEtAzG5GDQb18vCvTNOYTgP1HkJjE 21 | A1Ple8i/3SiHPodpxe2oQjMtcss5BMe6khe0gUf2gGVbOhaxAL1lbxwkIQKBgGg/ 22 | Rp7i/yF3D7j2fxKKL7L6ZdgyJSzqhvvoUw2rsxaq5DAKOYq8U3gXzchNOTQCBW5m 23 | xFdeoE/l+eT06Ra9cUqxI+gq6XNkmmz/hB4/DgCfjfNVL7SO2vaNshejAiaqFyaQ 24 | DyM2GVb0i5P9fgz6ALKlw0xiRRIIp8ItHhMijbhzAoGAdySMCZ4qLsFXu8MNl2/J 25 | RZjT3z/QIio5Y3mhvpYaGPcRGQ3TSiIdJgzBPQzSSrBFfJoedMwHpmFSpeC7zVRr 26 | oUziEhrwK4k9ZyEv2apOlZOjsyJw4bVKQRKiyWMM7//lYIip5TnbXMKW+asf3DYB 27 | 3I/Zu6vCCFxKfseH+o4BVaQ= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /docker/doc/secure/rsa/signing/mqttd.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDvmv0K0WQHN/wW 3 | HgVxN5/adjhdMx0WKVWOFEVefN45/PjGIVOOKK80TS6Z/tJnIePD3tJRfi+gyI7D 4 | CqFzfxM4NMYb/N3FtMbclq/8ahbNftFdMpdZTg0pYI90gfikHOEOS0Wc3HhRaCof 5 | 8WUXChdagdj7rsusCcLQg58VUHpfYLMYzll5zy7DhUqK18LVSXnfKX3Z+kHYgV/O 6 | G2r7hcEJqNYXSXKLEznoU0+3rWKLU7r+dkOSbjrNelHJo/KOr0NH+97s742TSryH 7 | tHT7w4YC9VhR9q1/IARzW+q5a36sVfM4KlKd5jYI84dBNs+eNUCjILsHoSVdDAt/ 8 | NcT0ddOfAgMBAAECggEBAMgVbe9FsbkReDHj+fl0UMm2ZRT25PgnpikSJmdj8JMN 9 | +5RAKi0RLlWHij3QfFThCPj8rjadIBqswAKBcxcma59I+pJOk8dQUavNLcKjuMz7 10 | dMvVvms18Wm3F7RcAIzTDODJ3KTDurmIOX0ZMKiPpbbPralmavAL6qIC96pt3PKd 11 | COqlLSv52X6CN18y9qgITqq+9NQmcp8I6VooFFUhl78m0zRnf6vrRg64DxuaOc6c 12 | PXAD2B2LOptnnTwFl4QMR9eVTJkmfn+ZcfxjG27G/8PE5SuezvNXvWLePzmgpkQn 13 | +BUFLmwGb+icJxk8WQNQJ/imPh/hXnNwqpvPDwEoWHkCgYEA+dD/mAvefig5l9yE 14 | V2HVqzNfIr8CHhFjy+bTP6KSYfnQ71Gg+h7FOlQgGbFdiWrsfz+1NFV7Sr/+t1P+ 15 | CTCn9Zq759pxjDKlZXIE2ILuQ9ad6TzgR9P0j5NGr8bE/PVzOrmlUVboROOnkcbZ 16 | QaqC58BJ6OHdiswXJoEo6x63e8UCgYEA9YlJWyBEDNO6xtcWGX8foeGXV5i2AuLc 17 | jhWAObF0ThxhvBrqmX9hgAQtAlZCDS+pcmiF3dIDgD8YGrTz/YVSnHvS7iZOiX2c 18 | X/WPBHReUbHzDdaueSfOOHpVe80L5J84P3A7gE91mXha1LQpbKEO+ZdyVY8y1bQT 19 | pCbT7GP/VBMCgYBm+wjiHMJzLxHO0FCd1O7HzD2DjUnKK1EAVP7wVIwTZ3ABt5ys 20 | ftK+4L762Gq+ox0qt5BzKmnQvqS53h3ym+QhEtAzG5GDQb18vCvTNOYTgP1HkJjE 21 | A1Ple8i/3SiHPodpxe2oQjMtcss5BMe6khe0gUf2gGVbOhaxAL1lbxwkIQKBgGg/ 22 | Rp7i/yF3D7j2fxKKL7L6ZdgyJSzqhvvoUw2rsxaq5DAKOYq8U3gXzchNOTQCBW5m 23 | xFdeoE/l+eT06Ra9cUqxI+gq6XNkmmz/hB4/DgCfjfNVL7SO2vaNshejAiaqFyaQ 24 | DyM2GVb0i5P9fgz6ALKlw0xiRRIIp8ItHhMijbhzAoGAdySMCZ4qLsFXu8MNl2/J 25 | RZjT3z/QIio5Y3mhvpYaGPcRGQ3TSiIdJgzBPQzSSrBFfJoedMwHpmFSpeC7zVRr 26 | oUziEhrwK4k9ZyEv2apOlZOjsyJw4bVKQRKiyWMM7//lYIip5TnbXMKW+asf3DYB 27 | 3I/Zu6vCCFxKfseH+o4BVaQ= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /docker/doc/uhppoted-mqtt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uhppoted/uhppoted-mqtt/0517296221de1c3c717d26d5b0b0dd70a07a9d56/docker/doc/uhppoted-mqtt -------------------------------------------------------------------------------- /docker/doc/uhppoted.conf: -------------------------------------------------------------------------------- 1 | # SYSTEM 2 | bind.address = 0.0.0.0:0 3 | broadcast.address = 255.255.255.255:60000 4 | listen.address = 0.0.0.0:60001 5 | 6 | # MQTT 7 | mqtt.server.ID = uhppoted 8 | mqtt.connection.broker = tcp://192.168.1.100:1883 9 | mqtt.connection.client.ID = uhppoted 10 | mqtt.connection.username = mqttd 11 | mqtt.connection.password = qwertyuiop 12 | ; mqtt.connection.broker = tls://192.168.1.100:8883 13 | ; mqtt.connection.broker.certificate = /etc/uhppoted/broker.pem 14 | ; mqtt.connection.client.certificate = /etc/uhppoted/mqtt/client.cert 15 | ; mqtt.connection.client.key = /etc/uhppoted/mqtt/client.key 16 | 17 | mqtt.topic.root = uhppoted/gateway 18 | mqtt.topic.requests = ./requests 19 | mqtt.topic.replies = ./replies 20 | mqtt.topic.events = ./events 21 | mqtt.topic.system = ./system 22 | mqtt.alerts.qos = 2 23 | mqtt.events.key = events 24 | mqtt.system.key = system 25 | mqtt.events.index.filepath = /var/uhppoted/mqtt.events.retrieved 26 | 27 | mqtt.permissions.enabled = false 28 | 29 | mqtt.security.authentication = NONE 30 | ; mqtt.security.HMAC.required = false 31 | ; mqtt.security.HMAC.key = ThisAndThat 32 | ; mqtt.security.rsa.keys = /etc/uhppoted/mqtt/rsa 33 | ; mqtt.security.nonce.required = true 34 | ; mqtt.security.nonce.server = /var/uhppoted/mqtt.nonce 35 | ; mqtt.security.nonce.clients = /var/uhppoted/mqtt.nonce.counters 36 | mqtt.security.outgoing.sign = false 37 | mqtt.security.outgoing.encrypt = false 38 | 39 | 40 | # DEVICES 41 | UT0311-L0x.405419896.address = 192.168.1.100:60000 42 | UT0311-L0x.405419896.door.1 = Great Hall 43 | UT0311-L0x.405419896.door.2 = Kitchen 44 | UT0311-L0x.405419896.door.3 = Dungeon 45 | UT0311-L0x.405419896.door.4 = Hogsmeade 46 | 47 | UT0311-L0x.303986753.address = 192.168.1.100:60000 48 | UT0311-L0x.303986753.door.1 = Gryffindor 49 | UT0311-L0x.303986753.door.2 = Hufflepuff 50 | UT0311-L0x.303986753.door.3 = Ravenclaw 51 | UT0311-L0x.303986753.door.4 = Slytherin 52 | 53 | -------------------------------------------------------------------------------- /docker/ghcr/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | EXPOSE 60001/udp 4 | 5 | RUN mkdir -p /usr/local/etc/uhppoted/mqtt 6 | RUN mkdir -p /usr/local/etc/uhppoted/mqtt/rsa 7 | RUN mkdir -p /usr/local/etc/uhppoted/mqtt/rsa/signing 8 | RUN mkdir -p /usr/local/etc/uhppoted/mqtt/rsa/encryption 9 | 10 | COPY uhppoted.conf /usr/local/etc/uhppoted 11 | 12 | WORKDIR /opt/uhppoted 13 | COPY uhppoted-mqtt . 14 | 15 | ENTRYPOINT /opt/uhppoted/uhppoted-mqtt --debug --config /usr/local/etc/uhppoted/uhppoted.conf --console 16 | -------------------------------------------------------------------------------- /docker/ghcr/uhppoted.conf: -------------------------------------------------------------------------------- 1 | # SYSTEM 2 | # bind.address = 0.0.0.0:0 3 | # broadcast.address = 255.255.255.255:60000 4 | # listen.address = 0.0.0.0:60001 5 | 6 | # MQTT 7 | mqtt.server.ID = uhppoted 8 | mqtt.connection.broker = tcp://192.168.1.100:1883 9 | mqtt.connection.client.ID = uhppoted 10 | mqtt.connection.username = mqttd 11 | mqtt.connection.password = qwertyuiop 12 | ; mqtt.connection.broker = tls://192.168.1.100:8883 13 | ; mqtt.connection.broker.certificate = /usr/local/etc/uhppoted/mqtt/broker.pem 14 | ; mqtt.connection.client.certificate = /usr/local/etc/uhppoted/mqtt/client.cert 15 | ; mqtt.connection.client.key = /usr/local/etc/uhppoted/mqtt/client.key 16 | 17 | mqtt.topic.root = uhppoted/gateway 18 | mqtt.topic.requests = ./requests 19 | mqtt.topic.replies = ./replies 20 | mqtt.topic.events = ./events 21 | mqtt.topic.system = ./system 22 | mqtt.alerts.qos = 2 23 | mqtt.events.key = events 24 | mqtt.system.key = system 25 | mqtt.events.index.filepath = /usr/local/etc/uhppoted/mqtt/events.retrieved 26 | 27 | mqtt.permissions.enabled = false 28 | 29 | mqtt.security.authentication = NONE 30 | ; mqtt.security.HMAC.required = false 31 | ; mqtt.security.HMAC.key = ThisAndThat 32 | ; mqtt.security.rsa.keys = /usr/local/etc/uhppoted/mqtt/rsa 33 | ; mqtt.security.nonce.required = true 34 | ; mqtt.security.nonce.server = /usr/local/etc/uhppoted/mqtt/nonce 35 | ; mqtt.security.nonce.clients = /usr/local/etc/uhppoted/mqtt/nonce.counters 36 | mqtt.security.outgoing.sign = false 37 | mqtt.security.outgoing.encrypt = false 38 | 39 | 40 | # DEVICES 41 | # 42 | # Example configuration for UTO311-L04 with serial number 405419896 43 | # UT0311-L0x.405419896.address = 192.168.1.100:60000 44 | # UT0311-L0x.405419896.door.1 = Great Hall 45 | # UT0311-L0x.405419896.door.2 = Kitchen 46 | # UT0311-L0x.405419896.door.3 = Dungeon 47 | # UT0311-L0x.405419896.door.4 = Hogsmeade 48 | -------------------------------------------------------------------------------- /documentation/FAQ.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | 1. _My MQTT broker is on an secure network - how do I disable signing and encryption and HMAC and ..?_ 4 | 5 | In _uhppoted.conf_: 6 | ``` 7 | # MQTT 8 | ... 9 | mqtt.security.authentication = NONE 10 | mqtt.security.HMAC.required = false 11 | mqtt.security.nonce.required = false 12 | mqtt.security.outgoing.sign = false 13 | mqtt.security.outgoing.encrypt = false 14 | ... 15 | ``` 16 | 17 | 2. _Why am I still getting old events even though I have set `mqtt.alerts.retained = false` ?_ 18 | 19 | The MQTT broker probably still has retained events from when `mqtt.alerts.retained` 20 | was set to `true`. You need to manually clear the events - normally by sending an 21 | empty retained message to the topics: 22 | 23 | ``` 24 | mqtt publish --topic 'uhppoted/gateway/events' --message '' -r 25 | mqtt publish --topic 'uhppoted/gateway/system' --message '' -r 26 | ``` -------------------------------------------------------------------------------- /documentation/TLS.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | 3 | rm -f localhost.jks 4 | rm -rf docker 5 | rm -rf etc 6 | 7 | mkdir -p ./tmp 8 | 9 | keytool -genkey -keyalg RSA -alias hivemq -keystore ./tmp/localhost.jks \ 10 | -storepass hivemq -keypass hivemq \ 11 | -validity 365 \ 12 | -keysize 2048 \ 13 | -ext "SAN=DNS:localhost,IP:192.168.1.100" \ 14 | -dname "CN=localhost, OU=hivemq, O=uhppoted, L=docker, ST=docker, C=MQ" 15 | 16 | keytool -exportcert -keystore ./tmp/localhost.jks -alias hivemq -keypass hivemq -storepass hivemq -rfc -file ./tmp/localhost.pem 17 | 18 | openssl x509 -in ./tmp/localhost.pem -noout -text 19 | 20 | mkdir -p ./docker 21 | mkdir -p ./docker/hivemq 22 | mkdir -p ./docker/uhppoted-mqtt 23 | mkdir -p ./docker/integration-tests/hivemq 24 | mkdir -p ./docker/integration-tests/mqttd 25 | 26 | cp ./tmp/localhost.jks ./docker/hivemq/localhost.jks 27 | cp ./tmp/localhost.pem ./docker/hivemq/localhost.pem 28 | cp ./tmp/localhost.jks ./docker/integration-tests/hivemq/localhost.jks 29 | cp ./tmp/localhost.pem ./docker/integration-tests/hivemq/localhost.pem 30 | cp ./tmp/localhost.pem ./docker/integration-tests/mqttd/localhost.pem 31 | cp ./tmp/localhost.pem ./docker/uhppoted-mqtt/hivemq.pem 32 | 33 | mkdir -p ./etc/com.github.uhppoted 34 | cp ./tmp/localhost.pem ./etc/com.github.uhppoted/hivemq.pem -------------------------------------------------------------------------------- /documentation/commands/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Overview](README.md) 4 | 5 | [Security](security.md) 6 | 7 | # Reference Guide 8 | 9 | 10 | - [`get-devices`](get-devices.md) 11 | 12 | - [`get-device`](get-device.md) 13 | 14 | - [`get-time`](get-time.md) 15 | 16 | - [`set-time`](set-time.md) 17 | 18 | - [`restore-default-parameters`](restore-default-parameters.md) 19 | 20 | - [`get-door-delay`](get-door-delay.md) 21 | 22 | - [`set-door-delay`](set-door-delay.md) 23 | 24 | - [`get-door-control`](get-door-control.md) 25 | 26 | - [`set-door-control`](set-door-control.md) 27 | 28 | - [`set-door-interlock`](set-door-interlock.md) 29 | 30 | - [`set-door-keypads`](set-door-keypads.md) 31 | 32 | - [`record-special-events`](record-special-events.md) 33 | 34 | - [`open-door`](open-door.md) 35 | 36 | - [`get-status`](get-status.md) 37 | 38 | - [`get-cards`](get-cards.md) 39 | 40 | - [`get-card`](get-card.md) 41 | 42 | - [`put-card`](put-card.md) 43 | 44 | - [`get-events`](get-events.md) 45 | 46 | - [`get-event`](get-event.md) 47 | 48 | - [`delete-card`](delete-card.md) 49 | 50 | - [`delete-cards`](delete-cards.md) 51 | 52 | - [`get-time-profile`](get-time-profile.md) 53 | 54 | - [`set-time-profile`](set-time-profile.md) 55 | 56 | - [`clear-time-profiles`](clear-time-profiles.md) 57 | 58 | - [`get-time-profiles`](get-time-profiles.md) 59 | 60 | - [`set-time-profiles`](set-time-profiles.md) 61 | 62 | - [`set-task-list`](set-task-list.md) 63 | 64 | - [`get-antipassback`](get-antipassback.md) 65 | 66 | - [`set-antipassback`](set-antipassback.md) 67 | 68 | - [`acl-show`](acl-show.md) 69 | 70 | - [`acl-grant`](acl-grant.md) 71 | 72 | - [`acl-revoke`](acl-revoke.md) 73 | 74 | - [`acl-upload-file`](acl-upload-file.md) 75 | 76 | - [`acl-upload-s3`](acl-upload-s3.md) 77 | 78 | - [`acl-upload-http`](acl-upload-http.md) 79 | 80 | - [`acl-download-file`](acl-download-file.md) 81 | 82 | - [`acl-download-s3`](acl-download-s3.md) 83 | 84 | - [`acl-download-http`](acl-download-http.md) 85 | 86 | - [`acl-compare-file`](acl-compare-file.md) 87 | 88 | - [`acl-compare-s3`](acl-compare-s3.md) 89 | 90 | - [`acl-compare-http`](acl-compare-http.md) 91 | 92 | 93 | -------------------------------------------------------------------------------- /documentation/commands/acl-grant.md: -------------------------------------------------------------------------------- 1 | ### `acl-grant` 2 | 3 | Grants system-wide access control permissions for a card. The permissions are 4 | added to any existing permissions. 5 | 6 | 7 | ``` 8 | Request: 9 | 10 | topic: //acl/card:grant 11 | 12 | message: 13 | { 14 | "message": { 15 | "request": { 16 | "request-id": "", 17 | "client-id": "", 18 | "reply-to": "", 19 | "card-number": "uint32", 20 | "start-date": "date", 21 | "end-date": "date", 22 | "doors": "array of string", 23 | } 24 | } 25 | } 26 | 27 | request-id (optional) message ID, returned in the response 28 | client-id (required) client ID for authentication and authorisation (if enabled) 29 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 30 | configured reply topic) if not provided. 31 | card-number card number 32 | start-date card 'valid from' date (inclusive) 33 | end-date card 'valid until' date (inclusive) 34 | doors string list of door names 35 | ``` 36 | 37 | ``` 38 | Response: 39 | { 40 | "message": { 41 | "reply": { 42 | "request-id": , 43 | "client-id": , 44 | "method": "acl-grant", 45 | "response": { 46 | "granted": "bool", 47 | }, 48 | ... 49 | } 50 | }, 51 | ... 52 | } 53 | 54 | request-id message ID from the request 55 | client-id client ID from the request 56 | granted grant success/fail 57 | ``` 58 | 59 | 60 | Example: 61 | ``` 62 | topic: uhppoted/gateway/requests/acl/card:grant 63 | 64 | { 65 | "message": { 66 | "request": { 67 | "client-id": "QWERTY", 68 | "request-id": "AH173635G3", 69 | "reply-to": "uhppoted/reply/97531", 70 | "request": { 71 | "card-number": 8165538, 72 | "start-date": "2022-01-01", 73 | "end-date": "2022-12-31", 74 | "doors": [ 75 | "Gryffindor", 76 | "Slytherin" 77 | ] 78 | } 79 | } 80 | } 81 | 82 | { 83 | "message": { 84 | "reply": { 85 | "server-id": "uhppoted" 86 | "client-id": "QWERTY", 87 | "request-id": "AH173635G3", 88 | "method": "acl:grant", 89 | "response": { 90 | "granted": true 91 | } 92 | } 93 | } 94 | } 95 | ``` 96 | -------------------------------------------------------------------------------- /documentation/commands/acl-revoke.md: -------------------------------------------------------------------------------- 1 | ### `acl-revoke` 2 | 3 | Revokes system-wide access control permissions for a card. The permissions are 4 | removed from any existing permissions. 5 | 6 | 7 | ``` 8 | Request: 9 | 10 | topic: //acl/card:revoke 11 | 12 | message: 13 | { 14 | "message": { 15 | "request": { 16 | "request-id": "", 17 | "client-id": "", 18 | "reply-to": "", 19 | "card-number": "uint32", 20 | "start-date": "date", 21 | "end-date": "date", 22 | "doors": "array of string", 23 | } 24 | } 25 | } 26 | 27 | request-id (optional) message ID, returned in the response 28 | client-id (required) client ID for authentication and authorisation (if enabled) 29 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 30 | configured reply topic) if not provided. 31 | card-number card number 32 | start-date card 'valid from' date (inclusive) 33 | end-date card 'valid until' date (inclusive) 34 | doors string list of door names 35 | ``` 36 | 37 | ``` 38 | Response: 39 | { 40 | "message": { 41 | "reply": { 42 | "request-id": , 43 | "client-id": , 44 | "method": "acl-revoke", 45 | "response": { 46 | "revoked": "bool", 47 | }, 48 | ... 49 | } 50 | }, 51 | ... 52 | } 53 | 54 | request-id message ID from the request 55 | client-id client ID from the request 56 | revoked revoke success/fail 57 | ``` 58 | 59 | 60 | Example: 61 | ``` 62 | topic: uhppoted/gateway/requests/acl/card:revoke 63 | 64 | { 65 | "message": { 66 | "request": { 67 | "client-id": "QWERTY", 68 | "request-id": "AH173635G3", 69 | "reply-to": "uhppoted/reply/97531", 70 | "request": { 71 | "card-number": 8165538, 72 | "start-date": "2022-01-01", 73 | "end-date": "2022-12-31", 74 | "doors": [ 75 | "Dungeon" 76 | ] 77 | } 78 | } 79 | } 80 | 81 | { 82 | "message": { 83 | "reply": { 84 | "server-id": "uhppoted" 85 | "client-id": "QWERTY", 86 | "request-id": "AH173635G3", 87 | "method": "acl:grant", 88 | "response": { 89 | "revoked": true 90 | } 91 | } 92 | } 93 | } 94 | ``` 95 | -------------------------------------------------------------------------------- /documentation/commands/acl-show.md: -------------------------------------------------------------------------------- 1 | ### `acl-show` 2 | 3 | Retrieves the system-wide access control permissions for a card. 4 | 5 | 6 | ``` 7 | Request: 8 | 9 | topic: //acl/card:show 10 | 11 | message: 12 | { 13 | "message": { 14 | "request": { 15 | "request-id": "", 16 | "client-id": "", 17 | "reply-to": "", 18 | "card-number": "uint32", 19 | } 20 | } 21 | } 22 | 23 | request-id (optional) message ID, returned in the response 24 | client-id (required) client ID for authentication and authorisation (if enabled) 25 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 26 | configured reply topic) if not provided. 27 | card-number (required) card number 28 | ``` 29 | 30 | ``` 31 | Response: 32 | { 33 | "message": { 34 | "reply": { 35 | "request-id": , 36 | "client-id": , 37 | "method": "acl-show", 38 | "response": { 39 | "card-number": "uint32", 40 | "deleted": "bool", 41 | }, 42 | ... 43 | } 44 | }, 45 | ... 46 | } 47 | 48 | request-id message ID from the request 49 | client-id client ID from the request 50 | card-number card number 51 | deleted card delete success/fail 52 | ``` 53 | 54 | 55 | Example: 56 | ``` 57 | topic: uhppoted/gateway/requests/acl/card:show 58 | 59 | { 60 | "message": { 61 | "request": { 62 | "client-id": "QWERTY", 63 | "request-id": "AH173635G3", 64 | "reply-to": "uhppoted/reply/97531", 65 | "card-number": 8165538 66 | } 67 | } 68 | } 69 | 70 | { 71 | "message": { 72 | "reply": { 73 | "server-id": "uhppoted" 74 | "client-id": "QWERTY", 75 | "request-id": "AH173635G3", 76 | "method": "acl:show", 77 | "response": { 78 | "card-number": 8165538, 79 | "permissions": [ 80 | { 81 | "door": "Gryffindor", 82 | "end-date": "2021-12-31", 83 | "start-date": "2021-01-01" 84 | }, 85 | { 86 | "door": "Slytherin", 87 | "end-date": "2021-12-31", 88 | "start-date": "2021-01-01" 89 | } 90 | ] 91 | } 92 | } 93 | } 94 | } 95 | ``` 96 | -------------------------------------------------------------------------------- /documentation/commands/clear-time-profiles.md: -------------------------------------------------------------------------------- 1 | ### `clear-time-profiles` 2 | 3 | Clears all time profiles on a a controller. 4 | 5 | 6 | ``` 7 | Request: 8 | 9 | topic: //device/time-profiles:delete 10 | 11 | message: 12 | { 13 | "message": { 14 | "request": { 15 | "request-id": "", 16 | "client-id": "", 17 | "reply-to": "", 18 | "device-id": "uint32", 19 | } 20 | } 21 | } 22 | 23 | request-id (optional) message ID, returned in the response 24 | client-id (required) client ID for authentication and authorisation (if enabled) 25 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 26 | configured reply topic) if not provided. 27 | device-id (required) controller serial number 28 | ``` 29 | 30 | ``` 31 | Response: 32 | { 33 | "message": { 34 | "reply": { 35 | "request-id": , 36 | "client-id": , 37 | "method": "clear-time-profiles", 38 | "response": { 39 | "device-id": "", 40 | "deleted": "bool", 41 | }, 42 | ... 43 | } 44 | }, 45 | ... 46 | } 47 | 48 | request-id message ID from the request 49 | client-id client ID from the request 50 | device-id controller serial number 51 | deleted clear time profiles success/fail 52 | ``` 53 | 54 | 55 | Example: 56 | ``` 57 | topic: uhppoted/gateway/requests/device/time-profiles:delete 58 | 59 | "message": { 60 | "request": { 61 | "client-id": "QWERTY", 62 | "request-id": "AH173635G3", 63 | "reply-to": "uhppoted/reply/97531", 64 | "device-id": 405419896 65 | } 66 | } 67 | } 68 | 69 | { 70 | "message": { 71 | "reply": { 72 | "server-id": "uhppoted" 73 | "client-id": "QWERTY", 74 | "request-id": "AH173635G3", 75 | "method": "clear-time-profiles", 76 | "response": { 77 | "cleared": true, 78 | "device-id": 405419896 79 | }, 80 | } 81 | }, 82 | } 83 | ``` 84 | -------------------------------------------------------------------------------- /documentation/commands/delete-card.md: -------------------------------------------------------------------------------- 1 | ### `delete-card` 2 | 3 | Deletes a card from a controller. 4 | 5 | 6 | ``` 7 | Request: 8 | 9 | topic: //device/card:delete 10 | 11 | message: 12 | { 13 | "message": { 14 | "request": { 15 | "request-id": "", 16 | "client-id": "", 17 | "reply-to": "", 18 | "device-id": "uint32", 19 | "card-number": "uint32", 20 | } 21 | } 22 | } 23 | 24 | request-id (optional) message ID, returned in the response 25 | client-id (required) client ID for authentication and authorisation (if enabled) 26 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 27 | configured reply topic) if not provided. 28 | device-id (required) controller serial number 29 | card-number (required) card number 30 | ``` 31 | 32 | ``` 33 | Response: 34 | { 35 | "message": { 36 | "reply": { 37 | "request-id": , 38 | "client-id": , 39 | "method": "delete-card", 40 | "response": { 41 | "device-id": "", 42 | "card-number": "uint32", 43 | "deleted": "bool", 44 | }, 45 | ... 46 | } 47 | }, 48 | ... 49 | } 50 | 51 | request-id message ID from the request 52 | client-id client ID from the request 53 | device-id controller serial number 54 | card-number card number 55 | deleted card delete success/fail 56 | ``` 57 | 58 | 59 | Example: 60 | ``` 61 | topic: uhppoted/gateway/requests/device/card:delete 62 | 63 | { 64 | "message": { 65 | "request": { 66 | "client-id": "QWERTY", 67 | "request-id": "AH173635G3", 68 | "reply-to": "uhppoted/reply/97531", 69 | "device-id": 405419896, 70 | "card-number": 8165538 71 | } 72 | } 73 | } 74 | 75 | { 76 | "message": { 77 | "reply": { 78 | "server-id": "uhppoted" 79 | "client-id": "QWERTY", 80 | "request-id": "AH173635G3", 81 | "method": "delete-card", 82 | "response": { 83 | "device-id": 405419896, 84 | "card-number": 8165538, 85 | "deleted": true 86 | } 87 | } 88 | } 89 | } 90 | ``` 91 | -------------------------------------------------------------------------------- /documentation/commands/delete-cards.md: -------------------------------------------------------------------------------- 1 | ### `delete-cards` 2 | 3 | Deletes all cards from a controller. 4 | 5 | 6 | ``` 7 | Request: 8 | 9 | topic: //device/cards:delete 10 | 11 | message: 12 | { 13 | "message": { 14 | "request": { 15 | "request-id": "", 16 | "client-id": "", 17 | "reply-to": "", 18 | "device-id": "uint32", 19 | } 20 | } 21 | } 22 | 23 | request-id (optional) message ID, returned in the response 24 | client-id (required) client ID for authentication and authorisation (if enabled) 25 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 26 | configured reply topic) if not provided. 27 | device-id (required) controller serial number 28 | ``` 29 | 30 | ``` 31 | Response: 32 | { 33 | "message": { 34 | "reply": { 35 | "request-id": , 36 | "client-id": , 37 | "method": "delete-cards", 38 | "response": { 39 | "device-id": "", 40 | "deleted": "bool", 41 | }, 42 | ... 43 | } 44 | }, 45 | ... 46 | } 47 | 48 | request-id message ID from the request 49 | client-id client ID from the request 50 | device-id controller serial number 51 | deleted delete all cards success/fail 52 | ``` 53 | 54 | 55 | Example: 56 | ``` 57 | topic: uhppoted/gateway/requests/device/cards:delete 58 | 59 | { 60 | "message": { 61 | "request": { 62 | "client-id": "QWERTY", 63 | "request-id": "AH173635G3", 64 | "reply-to": "uhppoted/reply/97531", 65 | "device-id": 405419896 66 | } 67 | } 68 | } 69 | 70 | { 71 | "message": { 72 | "reply": { 73 | "server-id": "uhppoted" 74 | "client-id": "QWERTY", 75 | "request-id": "AH173635G3", 76 | "method": "delete-cards", 77 | "response": { 78 | "device-id": 405419896, 79 | "deleted": true 80 | } 81 | } 82 | } 83 | } 84 | ``` 85 | -------------------------------------------------------------------------------- /documentation/commands/get-cards.md: -------------------------------------------------------------------------------- 1 | ### `get-cards` 2 | 3 | Retrieves a list of the cards stored on a controller. 4 | 5 | 6 | ``` 7 | Request: 8 | 9 | topic: //device/cards:get 10 | 11 | message: 12 | { 13 | "message": { 14 | "request": { 15 | "request-id": "", 16 | "client-id": "", 17 | "reply-to": "", 18 | "device-id": "", 19 | } 20 | } 21 | } 22 | 23 | request-id (optional) message ID, returned in the response 24 | client-id (required) client ID for authentication and authorisation (if enabled) 25 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 26 | configured reply topic) if not provided. 27 | device-id (required) controller serial number 28 | ``` 29 | 30 | ``` 31 | Response: 32 | { 33 | "message": { 34 | "reply": { 35 | "request-id": , 36 | "client-id": , 37 | "method": "get-cards", 38 | "response": { 39 | "device-id": "", 40 | "cards": "[]uint32", 41 | }, 42 | ... 43 | } 44 | }, 45 | ... 46 | } 47 | 48 | request-id message ID from the request 49 | client-id client ID from the request 50 | device-id controller serial number 51 | cards list of card numbers 52 | ``` 53 | 54 | 55 | Example: 56 | ``` 57 | topic: uhppoted/gateway/requests/device/cards:get 58 | 59 | { 60 | "message": { 61 | "request": { 62 | "client-id": "QWERTY", 63 | "request-id": "AH173635G3", 64 | "reply-to": "uhppoted/reply/97531", 65 | "device-id": 405419896 66 | } 67 | } 68 | } 69 | 70 | { 71 | "message": { 72 | "reply": { 73 | "server-id": "uhppoted" 74 | "client-id": "QWERTY", 75 | "request-id": "AH173635G3", 76 | "method": "get-cards", 77 | "response": { 78 | "device-id": 405419896, 79 | "cards": [ 80 | 8165537, 81 | 8165539, 82 | 8165538 83 | ] 84 | } 85 | } 86 | } 87 | } 88 | ``` 89 | -------------------------------------------------------------------------------- /documentation/commands/get-devices.md: -------------------------------------------------------------------------------- 1 | ### `get-devices` 2 | 3 | Returns a list of all UHPPOTE controllers found via a UDP broadcast on the local LAN or specifically 4 | configured in _uhppoted.conf_. 5 | 6 | 7 | ``` 8 | Request: 9 | 10 | topic: //devices:get 11 | 12 | message: 13 | { 14 | "message": { 15 | "request": { 16 | "request-id": "", 17 | "client-id": "", 18 | "reply-to": "", 19 | } 20 | } 21 | } 22 | 23 | request-id (optional) message ID, returned in the response 24 | client-id (required) client ID for authentication and authorisation (if enabled) 25 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 26 | configured reply topic) if not provided. 27 | ``` 28 | 29 | ``` 30 | Response: 31 | { 32 | "message": { 33 | "reply": { 34 | "request-id": , 35 | "client-id": , 36 | "method": "get-devices", 37 | "response": { 38 | "device-id": "", 39 | "device-type": "", 40 | "ip-address": "
", 41 | "port": "", 42 | }, 43 | ... 44 | } 45 | }, 46 | ... 47 | } 48 | 49 | request-id message ID from the request 50 | client-id client ID from the request 51 | device-id controller serial number 52 | device-type controller type (UTO311-L0x) 53 | ip-address controller IPv4 address 54 | port UDP port for controller commands 55 | ``` 56 | 57 | 58 | Example: 59 | ``` 60 | topic: uhppoted/gateway/requests/devices:get 61 | 62 | { 63 | "message": { 64 | "request": { 65 | "client-id": "QWERTY", 66 | "request-id": "AH173635G3", 67 | "reply-to": "uhppoted/reply/97531", 68 | } 69 | } 70 | } 71 | 72 | { 73 | "message": { 74 | "reply": { 75 | "server-id": "uhppoted" 76 | "client-id": "QWERTY", 77 | "request-id": "AH173635G3", 78 | "method": "get-devices", 79 | "response": { 80 | "devices": { 81 | "201020304": { 82 | "device-type": "UTO311-L02", 83 | "ip-address": "192.168.1.101", 84 | "port": 60000 85 | }, 86 | "303986753": { 87 | "device-type": "UTO311-L03", 88 | "ip-address": "192.168.1.100", 89 | "port": 60000 90 | }, 91 | "405419896": { 92 | "device-type": "UTO311-L04", 93 | "ip-address": "192.168.1.100", 94 | "port": 60000 95 | } 96 | } 97 | } 98 | } 99 | } 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /documentation/commands/get-door-control.md: -------------------------------------------------------------------------------- 1 | ### `get-door-delay` 2 | 3 | Retrieves the control mode for a controller door. 4 | 5 | 6 | ``` 7 | Request: 8 | 9 | topic: //device/door/delay:get 10 | 11 | message: 12 | { 13 | "message": { 14 | "request": { 15 | "request-id": "", 16 | "client-id": "", 17 | "reply-to": "", 18 | "device-id": "", 19 | "door": "", 20 | } 21 | } 22 | } 23 | 24 | request-id (optional) message ID, returned in the response 25 | client-id (required) client ID for authentication and authorisation (if enabled) 26 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 27 | configured reply topic) if not provided. 28 | device-id (required) controller serial number 29 | door (required) door (1..4) 30 | ``` 31 | 32 | ``` 33 | Response: 34 | { 35 | "message": { 36 | "reply": { 37 | "request-id": , 38 | "client-id": , 39 | "method": "get-door-delay", 40 | "response": { 41 | "device-id": "", 42 | "door": "", 43 | "delay": "", 44 | }, 45 | ... 46 | } 47 | }, 48 | ... 49 | } 50 | 51 | request-id message ID from the request 52 | client-id client ID from the request 53 | device-id controller serial number 54 | door door (1..4) from the request 55 | delay door open delay 56 | ``` 57 | 58 | 59 | Example: 60 | ``` 61 | topic: uhppoted/gateway/requests/device/door/delay:get 62 | 63 | { 64 | "message": { 65 | "request": { 66 | "client-id": "QWERTY", 67 | "request-id": "AH173635G3", 68 | "reply-to": "uhppoted/reply/97531", 69 | "device-id": 405419896, 70 | "door": 3 71 | } 72 | } 73 | } 74 | 75 | { 76 | "message": { 77 | "reply": { 78 | "server-id": "uhppoted" 79 | "client-id": "QWERTY", 80 | "request-id": "AH173635G3", 81 | "method": "get-door-control", 82 | "response": { 83 | "device-id": 405419896, 84 | "door": 3, 85 | "control": "controlled" 86 | } 87 | } 88 | } 89 | } 90 | ``` 91 | -------------------------------------------------------------------------------- /documentation/commands/get-door-delay.md: -------------------------------------------------------------------------------- 1 | ### `get-door-delay` 2 | 3 | Retrieves the open delay for a controller door. 4 | 5 | 6 | ``` 7 | Request: 8 | 9 | topic: //device/door/delay:get 10 | 11 | message: 12 | { 13 | "message": { 14 | "request": { 15 | "request-id": "", 16 | "client-id": "", 17 | "reply-to": "", 18 | "device-id": "", 19 | "door": "", 20 | } 21 | } 22 | } 23 | 24 | request-id (optional) message ID, returned in the response 25 | client-id (required) client ID for authentication and authorisation (if enabled) 26 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 27 | configured reply topic) if not provided. 28 | device-id (required) controller serial number 29 | door (required) door (1..4) 30 | ``` 31 | 32 | ``` 33 | Response: 34 | { 35 | "message": { 36 | "reply": { 37 | "request-id": , 38 | "client-id": , 39 | "method": "get-door-delay", 40 | "response": { 41 | "device-id": "", 42 | "door": "", 43 | "delay": "", 44 | }, 45 | ... 46 | } 47 | }, 48 | ... 49 | } 50 | 51 | request-id message ID from the request 52 | client-id client ID from the request 53 | device-id controller serial number 54 | door door (1..4) from the request 55 | delay door open delay 56 | ``` 57 | 58 | 59 | Example: 60 | ``` 61 | topic: uhppoted/gateway/requests/device/door/delay:get 62 | 63 | { 64 | "message": { 65 | "request": { 66 | "client-id": "QWERTY", 67 | "request-id": "AH173635G3", 68 | "reply-to": "uhppoted/reply/97531", 69 | "device-id": 405419896, 70 | "door": 3 71 | } 72 | } 73 | } 74 | 75 | { 76 | "message": { 77 | "reply": { 78 | "server-id": "uhppoted" 79 | "client-id": "QWERTY", 80 | "request-id": "AH173635G3", 81 | "method": "get-door-delay", 82 | "response": { 83 | "device-id": 405419896, 84 | "door": 3, 85 | "delay": 7 86 | } 87 | } 88 | } 89 | } 90 | ``` 91 | -------------------------------------------------------------------------------- /documentation/commands/get-time.md: -------------------------------------------------------------------------------- 1 | ### `get-time` 2 | 3 | Returns the controller date and time. 4 | 5 | 6 | ``` 7 | Request: 8 | 9 | topic: //device/time:get 10 | 11 | message: 12 | { 13 | "message": { 14 | "request": { 15 | "request-id": "", 16 | "client-id": "", 17 | "reply-to": "", 18 | "device-id": "", 19 | } 20 | } 21 | } 22 | 23 | request-id (optional) message ID, returned in the response 24 | client-id (required) client ID for authentication and authorisation (if enabled) 25 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 26 | configured reply topic) if not provided. 27 | device-id (required) controller serial number 28 | ``` 29 | 30 | ``` 31 | Response: 32 | { 33 | "message": { 34 | "reply": { 35 | "request-id": , 36 | "client-id": , 37 | "method": "get-time", 38 | "response": { 39 | "device-id": "", 40 | "date-time": "", 41 | }, 42 | ... 43 | } 44 | }, 45 | ... 46 | } 47 | 48 | request-id message ID from the request 49 | client-id client ID from the request 50 | device-id controller serial number 51 | date-time controller system date and time 52 | ``` 53 | 54 | 55 | Example: 56 | ``` 57 | topic: uhppoted/gateway/requests/device/time:get 58 | 59 | { 60 | "message": { 61 | "request": { 62 | "client-id": "QWERTY", 63 | "request-id": "AH173635G3", 64 | "reply-to": "uhppoted/reply/97531", 65 | "device-id": 405419896 66 | } 67 | } 68 | } 69 | 70 | { 71 | "message": { 72 | "reply": { 73 | "server-id": "uhppoted" 74 | "client-id": "QWERTY", 75 | "request-id": "AH173635G3", 76 | "method": "get-time", 77 | "response": { 78 | "date-time": "2022-09-08 11:01:04 PDT", 79 | "device-id": 405419896 80 | } 81 | } 82 | } 83 | } 84 | ``` 85 | -------------------------------------------------------------------------------- /documentation/commands/record-special-events.md: -------------------------------------------------------------------------------- 1 | ### `record-special-events` 2 | 3 | Enables/disables event logging for door open, close and pushbutton events. 4 | 5 | 6 | ``` 7 | Request: 8 | 9 | topic: //device/special-events:set 10 | 11 | message: 12 | { 13 | "message": { 14 | "request": { 15 | "request-id": "", 16 | "client-id": "", 17 | "reply-to": "", 18 | "device-id": "", 19 | "enabled": "", 20 | } 21 | } 22 | } 23 | 24 | request-id (optional) message ID, returned in the response 25 | client-id (required) client ID for authentication and authorisation (if enabled) 26 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 27 | configured reply topic) if not provided. 28 | device-id (required) controller serial number 29 | enabled (required) true/false 30 | ``` 31 | 32 | ``` 33 | Response: 34 | { 35 | "message": { 36 | "reply": { 37 | "request-id": , 38 | "client-id": , 39 | "method": "record-special-events", 40 | "response": { 41 | "device-id": "", 42 | "door": "", 43 | "control": "", 44 | }, 45 | ... 46 | } 47 | }, 48 | ... 49 | } 50 | 51 | request-id message ID from the request 52 | client-id client ID from the request 53 | device-id controller serial number 54 | door door (1..4) from the request 55 | control door control mode (normally open, normally closed or controlled) 56 | ``` 57 | 58 | 59 | Example: 60 | ``` 61 | topic: uhppoted/gateway/requests/device/special-events:set 62 | 63 | { 64 | "message": { 65 | "request": { 66 | "client-id": "QWERTY", 67 | "request-id": "AH173635G3", 68 | "reply-to": "uhppoted/reply/97531", 69 | "device-id": 405419896, 70 | "enabled": true 71 | } 72 | } 73 | } 74 | 75 | { 76 | "message": { 77 | "reply": { 78 | "server-id": "uhppoted" 79 | "client-id": "QWERTY", 80 | "request-id": "AH173635G3", 81 | "method": "record-special-events", 82 | "response": { 83 | "DeviceID": 405419896, 84 | "Enable": true, 85 | "Updated": true 86 | } 87 | } 88 | } 89 | } 90 | ``` 91 | -------------------------------------------------------------------------------- /documentation/commands/restore-default-parameters.md: -------------------------------------------------------------------------------- 1 | ### `restore-default-parameters` 2 | 3 | Resets a controller to the manufacturer default configuration. 4 | 5 | 6 | ``` 7 | Request: 8 | 9 | topic: //device:reset 10 | 11 | message: 12 | { 13 | "message": { 14 | "request": { 15 | "request-id": "", 16 | "client-id": "", 17 | "reply-to": "", 18 | "device-id": "uint32", 19 | } 20 | } 21 | } 22 | 23 | request-id (optional) message ID, returned in the response 24 | client-id (required) client ID for authentication and authorisation (if enabled) 25 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 26 | configured reply topic) if not provided. 27 | device-id (required) controller serial number 28 | ``` 29 | 30 | ``` 31 | Response: 32 | { 33 | "message": { 34 | "reply": { 35 | "request-id": , 36 | "client-id": , 37 | "method": "restore-default-parameters", 38 | "response": { 39 | "device-id": "", 40 | "reset": "bool", 41 | }, 42 | ... 43 | } 44 | }, 45 | ... 46 | } 47 | 48 | request-id message ID from the request 49 | client-id client ID from the request 50 | device-id controller serial number 51 | reset reset controller success/fail 52 | ``` 53 | 54 | 55 | Example: 56 | ``` 57 | topic: uhppoted/gateway/requests/device:reset 58 | 59 | { 60 | "message": { 61 | "request": { 62 | "client-id": "QWERTY", 63 | "request-id": "AH173635G3", 64 | "reply-to": "uhppoted/reply/97531", 65 | "device-id": 405419896 66 | } 67 | } 68 | } 69 | 70 | { 71 | "message": { 72 | "reply": { 73 | "server-id": "uhppoted" 74 | "client-id": "QWERTY", 75 | "request-id": "AH173635G3", 76 | "method": "restore-default-parameters", 77 | "response": { 78 | "device-id": 405419896, 79 | "reset": true 80 | } 81 | } 82 | } 83 | } 84 | ``` 85 | -------------------------------------------------------------------------------- /documentation/commands/set-door-control.md: -------------------------------------------------------------------------------- 1 | ### `set-door-delay` 2 | 3 | Sets the control mode for a controller door. 4 | 5 | 6 | ``` 7 | Request: 8 | 9 | topic: //device/door/delay:set 10 | 11 | message: 12 | { 13 | "message": { 14 | "request": { 15 | "request-id": "", 16 | "client-id": "", 17 | "reply-to": "", 18 | "device-id": "", 19 | "door": "", 20 | "delay": "", 21 | } 22 | } 23 | } 24 | 25 | request-id (optional) message ID, returned in the response 26 | client-id (required) client ID for authentication and authorisation (if enabled) 27 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 28 | configured reply topic) if not provided. 29 | device-id (required) controller serial number 30 | door (required) door (1..4) to open 31 | delay door open delay 32 | ``` 33 | 34 | ``` 35 | Response: 36 | { 37 | "message": { 38 | "reply": { 39 | "request-id": , 40 | "client-id": , 41 | "method": "set-door-delay", 42 | "response": { 43 | "device-id": "", 44 | "door": "", 45 | "delay": "", 46 | }, 47 | ... 48 | } 49 | }, 50 | ... 51 | } 52 | 53 | request-id message ID from the request 54 | client-id client ID from the request 55 | device-id controller serial number 56 | door door (1..4) from the request 57 | delay door open duration 58 | ``` 59 | 60 | 61 | Example: 62 | ``` 63 | topic: uhppoted/gateway/requests/device/door/delay:set 64 | 65 | { 66 | "message": { 67 | "request": { 68 | "client-id": "QWERTY", 69 | "request-id": "AH173635G3", 70 | "reply-to": "uhppoted/reply/97531", 71 | "device-id": 405419896, 72 | "door": 3, 73 | "control": "normally closed" 74 | } 75 | } 76 | } 77 | 78 | { 79 | "message": { 80 | "reply": { 81 | "server-id": "uhppoted" 82 | "client-id": "QWERTY", 83 | "request-id": "AH173635G3", 84 | "method": "set-door-control", 85 | "response": { 86 | "device-id": 405419896, 87 | "door": 3, 88 | "control": "normally closed" 89 | } 90 | } 91 | } 92 | } 93 | ``` 94 | -------------------------------------------------------------------------------- /documentation/commands/set-door-delay.md: -------------------------------------------------------------------------------- 1 | ### `set-door-delay` 2 | 3 | Sets the open delay for a door on a controller. 4 | 5 | 6 | ``` 7 | Request: 8 | 9 | topic: //device/door/delay:set 10 | 11 | message: 12 | { 13 | "message": { 14 | "request": { 15 | "request-id": "", 16 | "client-id": "", 17 | "reply-to": "", 18 | "device-id": "", 19 | "door": "", 20 | "delay": "", 21 | } 22 | } 23 | } 24 | 25 | request-id (optional) message ID, returned in the response 26 | client-id (required) client ID for authentication and authorisation (if enabled) 27 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 28 | configured reply topic) if not provided. 29 | device-id (required) controller serial number 30 | door (required) door (1..4) to open 31 | delay door open delay 32 | ``` 33 | 34 | ``` 35 | Response: 36 | { 37 | "message": { 38 | "reply": { 39 | "request-id": , 40 | "client-id": , 41 | "method": "set-door-delay", 42 | "response": { 43 | "device-id": "", 44 | "door": "", 45 | "delay": "", 46 | }, 47 | ... 48 | } 49 | }, 50 | ... 51 | } 52 | 53 | request-id message ID from the request 54 | client-id client ID from the request 55 | device-id controller serial number 56 | door door (1..4) from the request 57 | delay door open duration 58 | ``` 59 | 60 | 61 | Example: 62 | ``` 63 | topic: uhppoted/gateway/requests/device/door/delay:set 64 | 65 | { 66 | "message": { 67 | "request": { 68 | "client-id": "QWERTY", 69 | "request-id": "AH173635G3", 70 | "reply-to": "uhppoted/reply/97531", 71 | "device-id": 405419896, 72 | "door": 3, 73 | "delay": 8 74 | } 75 | } 76 | } 77 | 78 | { 79 | "message": { 80 | "reply": { 81 | "server-id": "uhppoted" 82 | "client-id": "QWERTY", 83 | "request-id": "AH173635G3", 84 | "method": "set-door-delay", 85 | "response": { 86 | "device-id": 405419896, 87 | "door": 3, 88 | "delay": 8 89 | } 90 | } 91 | } 92 | } 93 | ``` 94 | -------------------------------------------------------------------------------- /documentation/commands/set-door-interlock.md: -------------------------------------------------------------------------------- 1 | ### `set-door-interlock` 2 | 3 | Sets the door interlock mode for a controller. 4 | 5 | 6 | ``` 7 | Request: 8 | 9 | topic: //device/door/interlock:set 10 | 11 | message: 12 | { 13 | "message": { 14 | "request": { 15 | "request-id": "", 16 | "client-id": "", 17 | "reply-to": "", 18 | "device-id": "", 19 | "interlock": "", 20 | } 21 | } 22 | } 23 | 24 | request-id (optional) message ID, returned in the response 25 | client-id (required) client ID for authentication and authorisation (if enabled) 26 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 27 | configured reply topic) if not provided. 28 | device-id (required) controller serial number 29 | interlock door interlock mode (0: none, 1:doors 1&2, 2:doors 3&4, 3:doors 1&2 and doors 3&4, 4:doors 1&2&3, 8:doors 1&2&3&4 30 | ``` 31 | 32 | ``` 33 | Response: 34 | { 35 | "message": { 36 | "reply": { 37 | "request-id": , 38 | "client-id": , 39 | "method": "set-door-interlock", 40 | "response": { 41 | "device-id": "", 42 | "interlock": "", 43 | }, 44 | ... 45 | } 46 | }, 47 | ... 48 | } 49 | 50 | request-id message ID from the request 51 | client-id client ID from the request 52 | device-id controller serial number 53 | interlock door interlock mode (from request) 54 | ``` 55 | 56 | 57 | Example: 58 | ``` 59 | topic: uhppoted/gateway/requests/device/door/interlock:set 60 | 61 | { 62 | "message": { 63 | "request": { 64 | "client-id": "QWERTY", 65 | "request-id": "AH173635G3", 66 | "reply-to": "uhppoted/reply/97531", 67 | "device-id": 405419896, 68 | "interlock": 4 69 | } 70 | } 71 | } 72 | 73 | { 74 | "message": { 75 | "reply": { 76 | "server-id": "uhppoted" 77 | "client-id": "QWERTY", 78 | "request-id": "AH173635G3", 79 | "method": "set-door-interlock", 80 | "response": { 81 | "device-id": 405419896, 82 | "interlock": 4 83 | } 84 | } 85 | } 86 | } 87 | ``` 88 | -------------------------------------------------------------------------------- /documentation/commands/set-door-keypads.md: -------------------------------------------------------------------------------- 1 | ### `activate-keypads` 2 | 3 | Activates/deactivates the reader access keypads for a controller. 4 | 5 | 6 | ``` 7 | Request: 8 | 9 | topic: //device/door/keypads:set 10 | 11 | message: 12 | { 13 | "message": { 14 | "request": { 15 | "request-id": "", 16 | "client-id": "", 17 | "reply-to": "", 18 | "device-id": "", 19 | "keypads": "map[uint8]bool", 20 | } 21 | } 22 | } 23 | 24 | request-id (optional) message ID, returned in the response 25 | client-id (required) client ID for authentication and authorisation (if enabled) 26 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 27 | configured reply topic) if not provided. 28 | device-id (required) controller serial number 29 | keypads map of activated readers (unlisted readers are deactivated) 30 | ``` 31 | 32 | ``` 33 | Response: 34 | { 35 | "message": { 36 | "reply": { 37 | "request-id": , 38 | "client-id": , 39 | "method": "activate-keypads", 40 | "response": { 41 | "device-id": "", 42 | "keypads": "map[uint8]bool", 43 | }, 44 | ... 45 | } 46 | }, 47 | ... 48 | } 49 | 50 | request-id message ID from the request 51 | client-id client ID from the request 52 | device-id controller serial number 53 | keypads map of readers to activated status 54 | ``` 55 | 56 | 57 | Example: 58 | ``` 59 | topic: uhppoted/gateway/requests/device/door/keypads:set 60 | 61 | { 62 | "message": { 63 | "request": { 64 | "client-id": "QWERTY", 65 | "request-id": "AH173635G3", 66 | "reply-to": "uhppoted/reply/97531", 67 | "device-id": 405419896, 68 | "keypads": { 69 | "1": true, 70 | "2": true, 71 | "3": false, 72 | "4": true 73 | } 74 | } 75 | } 76 | } 77 | 78 | { 79 | "message": { 80 | "reply": { 81 | "server-id": "uhppoted" 82 | "client-id": "QWERTY", 83 | "request-id": "AH173635G3", 84 | "method": "set-door-keypads", 85 | "response": { 86 | "device-id": 405419896, 87 | "keypads": { 88 | "1": true, 89 | "2": true, 90 | "3": false, 91 | "4": true 92 | } 93 | } 94 | } 95 | } 96 | } 97 | ``` 98 | -------------------------------------------------------------------------------- /documentation/commands/set-door-passcodes.md: -------------------------------------------------------------------------------- 1 | ### `set-door-passcodes` 2 | 3 | Sets up to four supervisor passcodes for a controller door, with valid passcodes being in the range [1..999999]. 4 | Invalid passcodes are replaces by 0 (no code). 5 | 6 | 7 | ``` 8 | Request: 9 | 10 | topic: //device/door/passcodes:set 11 | 12 | message: 13 | { 14 | "message": { 15 | "request": { 16 | "request-id": "", 17 | "client-id": "", 18 | "reply-to": "", 19 | "device-id": "", 20 | "door": "", 21 | "passcodes": "", 22 | } 23 | } 24 | } 25 | 26 | request-id (optional) message ID, returned in the response 27 | client-id (required) client ID for authentication and authorisation (if enabled) 28 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 29 | configured reply topic) if not provided. 30 | device-id (required) controller serial number 31 | door (required) door (1..4) 32 | passcodes array of passcodes ([1..999999]). Only the first four entries are used. 33 | ``` 34 | 35 | ``` 36 | Response: 37 | { 38 | "message": { 39 | "reply": { 40 | "request-id": , 41 | "client-id": , 42 | "method": "set-door-passcodes", 43 | "response": { 44 | "device-id": "", 45 | "door": "", 46 | "passcodes": "", 47 | }, 48 | ... 49 | } 50 | }, 51 | ... 52 | } 53 | 54 | request-id message ID from the request 55 | client-id client ID from the request 56 | device-id controller serial number 57 | door door (1..4) from the request 58 | passcodes passcodes assigned to door 59 | ``` 60 | 61 | 62 | Example: 63 | ``` 64 | topic: uhppoted/gateway/requests/device/door/passcodes:set 65 | 66 | { 67 | "message": { 68 | "request": { 69 | "client-id": "QWERTY", 70 | "request-id": "AH173635G3", 71 | "reply-to": "uhppoted/reply/97531", 72 | "device-id": 405419896, 73 | "door": 3, 74 | "passcodes": [12345,999999,54321] 75 | } 76 | } 77 | } 78 | 79 | { 80 | "message": { 81 | "reply": { 82 | "server-id": "uhppoted" 83 | "client-id": "QWERTY", 84 | "request-id": "AH173635G3", 85 | "method": "set-door-control", 86 | "response": { 87 | "device-id": 405419896, 88 | "door": 3, 89 | "passcodes": [12345,999999,54321] 90 | } 91 | } 92 | } 93 | } 94 | ``` 95 | -------------------------------------------------------------------------------- /documentation/commands/set-time.md: -------------------------------------------------------------------------------- 1 | ### `set-time` 2 | 3 | Sets the controller date and time. 4 | 5 | 6 | ``` 7 | Request: 8 | 9 | topic: //device/time:set 10 | 11 | message: 12 | { 13 | "message": { 14 | "request": { 15 | "request-id": "", 16 | "client-id": "", 17 | "reply-to": "", 18 | "device-id": "", 19 | "date-time": "", 20 | } 21 | } 22 | } 23 | 24 | request-id (optional) message ID, returned in the response 25 | client-id (required) client ID for authentication and authorisation (if enabled) 26 | reply-to (optional) topic for reply message. Defaults to uhppoted/gateway/replies (or the 27 | configured reply topic) if not provided. 28 | device-id (required) controller serial number 29 | date-time (required) date and time to set (YYYY-mm-dd HH:mm:ss) 30 | ``` 31 | 32 | ``` 33 | Response: 34 | { 35 | "message": { 36 | "reply": { 37 | "request-id": , 38 | "client-id": , 39 | "method": "set-time", 40 | "response": { 41 | "device-id": "", 42 | "date-time": "", 43 | }, 44 | ... 45 | } 46 | }, 47 | ... 48 | } 49 | 50 | request-id message ID from the request 51 | client-id client ID from the request 52 | device-id controller serial number 53 | date-time controller system date and time (YYYY-mm-dd HH:mm:ss) 54 | ``` 55 | 56 | 57 | Example: 58 | ``` 59 | topic: uhppoted/gateway/requests/device/time:set 60 | 61 | { 62 | "message": { 63 | "request": { 64 | "client-id": "QWERTY", 65 | "request-id": "AH173635G3", 66 | "reply-to": "uhppoted/reply/97531", 67 | "device-id": 405419896, 68 | "date-time": "2022-09-09 11:25:04" 69 | } 70 | } 71 | } 72 | 73 | { 74 | "message": { 75 | "reply": { 76 | "server-id": "uhppoted" 77 | "client-id": "QWERTY", 78 | "request-id": "AH173635G3", 79 | "method": "set-time", 80 | "response": { 81 | "device-id": 405419896, 82 | "date-time": "2022-09-09 11:25:04 PDT" 83 | } 84 | } 85 | } 86 | } 87 | ``` 88 | -------------------------------------------------------------------------------- /documentation/greengrass/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | NOTES.md 3 | clean.md 4 | -------------------------------------------------------------------------------- /documentation/greengrass/aws-lambda-tar.py: -------------------------------------------------------------------------------- 1 | # Ref. https://github.com/uhppoted/uhppoted/discussions/17 2 | 3 | import tarfile 4 | import boto3 5 | import io 6 | import time 7 | 8 | def make_tarfile(tarobj, files): 9 | def make_tarinfo(filename, filebody): 10 | file_tarinfo = tarfile.TarInfo(filename) 11 | file_tarinfo.size = len(filebody.encode('utf-8')) 12 | file_tarinfo.mtime = time.time() 13 | return(file_tarinfo) 14 | 15 | def make_fileobj(fileobj, filename): 16 | fileobj.name = filename 17 | fileobj.seek(0) 18 | return(fileobj) 19 | 20 | with tarfile.open(fileobj=tarobj, mode="w:gz") as tar: 21 | for (fname, body) in files: 22 | file_tarinfo = make_tarinfo(fname, body) 23 | file_obj = make_fileobj(io.BytesIO(body.encode('utf-8')), fname) 24 | logger.debug("Adding {}, size {} to tarfile".format(fname, file_tarinfo.size)) 25 | tar.addfile(file_tarinfo, fileobj=file_obj) 26 | tarobj.seek(0) 27 | return(tarobj) -------------------------------------------------------------------------------- /documentation/signatures.md: -------------------------------------------------------------------------------- 1 | # Creating a signed ACL file 2 | 3 | #### Create RSA signing keys 4 | 5 | ``` 6 | openssl genrsa -out QWERTY54.key 2048 7 | openssl rsa -in QWERTY54.key -out QWERTY54.pub -outform PEM -pubout 8 | ``` 9 | 10 | #### Copy the public signing key to the `mqttd` configuration directory 11 | 12 | ``` 13 | cp QWERTY54.pub /usr/local/etc/com.github.uhppoted/mqtt/rsa/signing/QWERTY54.pub 14 | ``` 15 | 16 | #### Sign the ACL file with the private key 17 | 18 | ``` 19 | openssl dgst -sha256 -sign QWERTY54.key -out signature hogwarts.acl 20 | ``` 21 | 22 | #### Package the ACL and signature as a .tar.gz file 23 | 24 | Create a _.tar.gz_ file containg the _ACL_ and _signature_ files, with the `uname` and `gname` set to the 25 | user name of the key used to sign the ACL file: 26 | 27 | ``` 28 | tar --uname=QWERTY54 --gname=QWERTY54 -cvzf hogwarts.tar.gz hogwarts.acl signature 29 | ``` 30 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/uhppoted/uhppoted-mqtt 2 | 3 | go 1.24 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.55.6 7 | github.com/eclipse/paho.mqtt.golang v1.5.0 8 | github.com/uhppoted/uhppote-core v0.8.11-0.20250331165159-e04fd7de7eab 9 | github.com/uhppoted/uhppoted-lib v0.8.11-0.20250331180353-7ccb6f69d17e 10 | golang.org/x/sys v0.31.0 11 | ) 12 | 13 | require ( 14 | github.com/gorilla/websocket v1.5.3 // indirect 15 | github.com/jmespath/go-jmespath v0.4.0 // indirect 16 | golang.org/x/net v0.38.0 // indirect 17 | golang.org/x/sync v0.12.0 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= 2 | github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/eclipse/paho.mqtt.golang v1.5.0 h1:EH+bUVJNgttidWFkLLVKaQPGmkTUfQQqjOsyvMGvD6o= 6 | github.com/eclipse/paho.mqtt.golang v1.5.0/go.mod h1:du/2qNQVqJf/Sqs4MEL77kR8QTqANF7XU7Fk0aOTAgk= 7 | github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 8 | github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 9 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 10 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 11 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 12 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 16 | github.com/uhppoted/uhppote-core v0.8.11-0.20250331165159-e04fd7de7eab h1:WNEcaWNUVuho2fxlXf4Uj+8K6mpO/lloQGkSjorHaAY= 17 | github.com/uhppoted/uhppote-core v0.8.11-0.20250331165159-e04fd7de7eab/go.mod h1:s6QikGwy+nS7nZjgba/k8ugszVreqgqGg7oxDgnLLGg= 18 | github.com/uhppoted/uhppoted-lib v0.8.11-0.20250331180353-7ccb6f69d17e h1:WZSCfdpqoQeRzNGC72RMS+iMESbkQ9POC25HICvBBe4= 19 | github.com/uhppoted/uhppoted-lib v0.8.11-0.20250331180353-7ccb6f69d17e/go.mod h1:l/PougoF5uQzmXRIQ5LPNnkAGFqrhv+rYWrG63xjGCc= 20 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 21 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 22 | golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= 23 | golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 24 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 25 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 26 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 27 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 28 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 29 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | syslog "log" 6 | 7 | "github.com/uhppoted/uhppoted-lib/log" 8 | ) 9 | 10 | const f = "%-12v %v" 11 | 12 | func SetDebug(enabled bool) { 13 | log.SetDebug(enabled) 14 | } 15 | 16 | func SetLevel(level string) { 17 | log.SetLevel(level) 18 | } 19 | 20 | func SetLogger(logger *syslog.Logger) { 21 | log.SetLogger(logger) 22 | } 23 | 24 | func AddFatalHook(f func()) { 25 | log.AddFatalHook(f) 26 | } 27 | 28 | func Debugf(tag string, format string, args ...any) { 29 | s := fmt.Sprintf(f, tag, format) 30 | 31 | log.Debugf(s, args...) 32 | } 33 | 34 | func Infof(tag string, format string, args ...any) { 35 | s := fmt.Sprintf(f, tag, format) 36 | 37 | log.Infof(s, args...) 38 | } 39 | 40 | func Warnf(tag string, format string, args ...any) { 41 | s := fmt.Sprintf(f, tag, format) 42 | 43 | log.Warnf(s, args...) 44 | } 45 | 46 | func Errorf(tag string, format string, args ...any) { 47 | s := fmt.Sprintf(f, tag, format) 48 | 49 | log.Errorf(s, args...) 50 | } 51 | 52 | func Fatalf(tag string, format string, args ...any) { 53 | s := fmt.Sprintf(f, tag, format) 54 | 55 | log.Fatalf(s, args...) 56 | } 57 | -------------------------------------------------------------------------------- /mqtt/monitor.go: -------------------------------------------------------------------------------- 1 | package mqtt 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/uhppoted/uhppoted-lib/monitoring" 8 | "github.com/uhppoted/uhppoted-mqtt/log" 9 | ) 10 | 11 | type SystemMonitor struct { 12 | mqttd *MQTTD 13 | } 14 | 15 | var alive = sync.Map{} 16 | 17 | func NewSystemMonitor(mqttd *MQTTD) *SystemMonitor { 18 | return &SystemMonitor{ 19 | mqttd: mqttd, 20 | } 21 | } 22 | 23 | func (m *SystemMonitor) Alive(monitor monitoring.Monitor, msg string) error { 24 | event := struct { 25 | Alive struct { 26 | SubSystem string `json:"subsystem"` 27 | Message string `json:"message"` 28 | } `json:"alive"` 29 | }{ 30 | Alive: struct { 31 | SubSystem string `json:"subsystem"` 32 | Message string `json:"message"` 33 | }{ 34 | SubSystem: monitor.ID(), 35 | Message: msg, 36 | }, 37 | } 38 | 39 | now := time.Now() 40 | last, ok := alive.Load(monitor.ID()) 41 | interval := 60 * time.Second 42 | 43 | if ok && time.Since(last.(time.Time)).Round(time.Second) < interval { 44 | return nil 45 | } 46 | 47 | if err := m.mqttd.send(&m.mqttd.Encryption.SystemKeyID, m.mqttd.Topics.System, nil, event, msgSystem, false); err != nil { 48 | log.Warnf("monitoring", "%v", err) 49 | return err 50 | } 51 | 52 | alive.Store(monitor.ID(), now) 53 | 54 | return nil 55 | } 56 | 57 | func (m *SystemMonitor) Alert(monitor monitoring.Monitor, msg string) error { 58 | event := struct { 59 | Alert struct { 60 | SubSystem string `json:"subsystem"` 61 | Message string `json:"message"` 62 | } `json:"alert"` 63 | }{ 64 | Alert: struct { 65 | SubSystem string `json:"subsystem"` 66 | Message string `json:"message"` 67 | }{ 68 | SubSystem: monitor.ID(), 69 | Message: msg, 70 | }, 71 | } 72 | 73 | if err := m.mqttd.send(&m.mqttd.Encryption.SystemKeyID, m.mqttd.Topics.System, nil, event, msgSystem, true); err != nil { 74 | log.Warnf("monitoring", "%v", err) 75 | return err 76 | } 77 | 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /mqtt/statistics.go: -------------------------------------------------------------------------------- 1 | package mqtt 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type statistics struct { 8 | enabled bool 9 | interval time.Duration 10 | max uint32 11 | 12 | disconnects []uint32 13 | disconnected chan uint32 14 | //tick <-chan time.Time 15 | tick *time.Ticker 16 | } 17 | 18 | var stats = statistics{ 19 | enabled: true, 20 | interval: 5 * time.Minute, 21 | max: 10, 22 | 23 | disconnects: make([]uint32, 60), 24 | disconnected: make(chan uint32), 25 | tick: time.NewTicker(1 * time.Second), 26 | } 27 | 28 | func init() { 29 | stats.monitor() 30 | } 31 | 32 | func SetDisconnectsEnabled(enabled bool) { 33 | stats.enabled = enabled 34 | } 35 | 36 | func SetDisconnectsInterval(interval time.Duration) { 37 | if interval > 60*time.Second { 38 | stats.interval = interval 39 | } else { 40 | stats.interval = 60 * time.Second 41 | } 42 | } 43 | 44 | func SetMaxDisconnects(N uint32) { 45 | stats.max = N 46 | } 47 | 48 | func (s *statistics) onDisconnected() { 49 | s.disconnected <- uint32(1) 50 | } 51 | 52 | func (s *statistics) monitor() { 53 | sum := func(b []uint32) uint64 { 54 | total := uint64(0) 55 | for _, v := range b { 56 | total += uint64(v) 57 | } 58 | 59 | return total 60 | } 61 | 62 | go func() { 63 | start := time.Now() 64 | index := 0 65 | 66 | for { 67 | select { 68 | case N := <-stats.disconnected: 69 | stats.disconnects[index] += N 70 | count := sum(stats.disconnects) 71 | infof("DISCONNECT %v of %v in %v", count, s.max, s.interval) 72 | if stats.enabled && count >= uint64(stats.max) { 73 | fatalf("DISCONNECT COUNT %v REACHED MAXIMUM ALLOWED (%v)", count, stats.max) 74 | } 75 | 76 | case <-stats.tick.C: 77 | N := len(stats.disconnects) 78 | step := stats.interval / time.Duration(N) 79 | delta := time.Since(start) 80 | bucket := float64(delta) / float64(step) 81 | next := int(bucket) % 60 82 | 83 | for next != index { 84 | index = (index + 1) % 60 85 | stats.disconnects[index] = 0 86 | } 87 | } 88 | } 89 | }() 90 | } 91 | --------------------------------------------------------------------------------