Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QR Code in R #90

Open
dmenne opened this issue Aug 18, 2020 · 5 comments
Open

QR Code in R #90

dmenne opened this issue Aug 18, 2020 · 5 comments
Assignees
Labels
feature a feature request or enhancement

Comments

@dmenne
Copy link
Contributor

dmenne commented Aug 18, 2020

The example below shows how to create QR Codes for test submissions. I post it here because there were are some missing parts in the documentation (base64 encoding), and there was a nasty gotcha in qrencode that stumbles over the allowed CRLF in base64 encoding. I have not tested it exactly as it is, because it is part of a R6 class that creates the warehouse SQLite database.

Some ideas from here

Maybe one day I will try to create a pull request - no now.

  #' @description
  #' Generates draft QR Code.
  #' \url{https://docs.getodk.org/collect-import-export/#list-of-keys-for-all-settings}
  #'
  #' @param xmlFormId The id of this Form as given in its XForms XML
  #' @param draftToken as obtained from `ru_draft_form_details`
  #' @param show_image Displays the QR-Code with image
  #' @param JSON file in inst/extdata as template for settings. Use **
  #' to delimit `draftToken` insert location.
  #' @return QR code for display with `image`
  ru_qr_code = function(xmlFormId, draftToken, pid,
                        show_image = FALSE,
                        collect_json = "collect_settings.json") {
    # Settings file must use ** to delimit replaceable tokens  to avoid
    # collisions with braces in json
    settings_file =
      system.file("extdata", collect_json, package = "sgformulare")
    if (!file.exists(settings_file))
      settings_file = rprojroot::find_package_root_file("inst", "extdata", collect_json)
    if (!file.exists(settings_file))
      stop("Settings file ", settings_file, " not found")
    url = ruODK::get_default_url()
    server_url = glue::glue(
      "{url}/v1/test/{draftToken}/projects/{pid}/forms/{xmlFormId}/draft")
    # Read settings with ** as glue delimiter
    settings = gsub("[\r\n\t ]", "",  readr::read_file(settings_file))
    qq = glue::glue(settings, .open = "**", .close = "**")

    # Linebreaks must be removed. qrencode does not follow standards here
    # https://github.com/jeroen/jsonlite/issues/220
    q64 = gsub("[\r\n]", "", jsonlite::base64_enc(memCompress(qq, "gzip")))
    qr = qrencoder::qrencode_raw(q64)
    if (show_image) {
      # pp = par(mar = c(0,0,0,0) )
      image(qr,  asp = 1, col = c("white", "black"), axes = FALSE, xlab = "", ylab = "")
      # par(pp)
    }
    qr
  }

Template collect_settings.json

{
	"admin": {
		"view_sent": false,
		"change_server": false,
		"analytics": false,
		"save_as": false
	},

	"general": {
		"server_url": "**server_url**",
		"navigation": "swipe_buttons",
		"periodic_form_updates_check": "every_fifteen_minutes",
		"autosend": "wifi_only",
		"guidance_hint": "yes_collapsed",
		"analytics": false
	}
}
@dmenne dmenne added the feature a feature request or enhancement label Aug 18, 2020
@florianm
Copy link
Collaborator

Thanks heaps for the implementation! Sounds like a good addition. I won't have bandwidth for the next two weeks but happy to work on this!

@florianm florianm added this to the Release 1.0 milestone Aug 19, 2020
@florianm
Copy link
Collaborator

florianm commented Nov 19, 2020

I'm revisiting this issue and am thinking that another great use for the function is to bake settings into the QR code, something that ODK Central doesn't yet do. In my day job every app user has different settings (e.g. auto send or not, theme, map base layers).

So this function could be complemented by a vignette "qr" explaining how to choose and write settings into one or several settings.json, then create a QR code for a given project using those settings files.

@dmenne
Copy link
Contributor Author

dmenne commented Nov 19, 2020

One caveat: I do not know how much free space is left in the compressed 64*64 code. However, it could be that other sizes are possible.

Slightly different QR-Code for app users

 # Higher level functions using REST API --------------------

  #' @description
  #' Returns App user qr code
  #' \url{https://docs.getodk.org/collect-import-export/#list-of-keys-for-all-settings}
  #'
  #' @param project_id The project ID
  #' @param user_id The id of the user, e.g. 115
  #' @param show_image Displays the QR-Code with image
  #' @param collect_json JSON file in inst/extdata as template for settings. Use **
  #' to delimit `draft_token` insert location.
  #' @return QR code for display with `image`
  #' @export
  app_user_qr_code = function(project_id,
                              user_id,
                              show_image = FALSE,
                              collect_json = "app_user_settings.json") {
    app_users = self$list_app_users(project_id)$content
    if (!is.null(app_users$message)) 
      stop("app_user_qr_code: Invalid project id")
    if (!(user_id %in% app_users$id))
        stop("app_user_qr_code: Invalid user id")
    key = app_users$token # Do not remove, used in glue
    # Settings file must use ** to delimit replaceable tokens  to avoid
    # collisions with braces in json
    settings_file =  private$get_extdata_file(collect_json)
    stopifnot(file.exists(settings_file))
    # Read settings with ** as glue delimiter
    # TODO : remove readr!
    settings = gsub("[\r\n\t ]", "",  readr::read_file(settings_file))
    qq = glue::glue(settings, .open = "**", .close = "**")
    
    # Line breaks must be removed. qrencode does not follow standards here
    # https://github.com/jeroen/jsonlite/issues/220
    q64 = gsub("[\r\n]", "", jsonlite::base64_enc(memCompress(qq, "gzip")))
    qr = qrencoder::qrencode_raw(q64)
    if (show_image) {
      # pp = par(mar = c(0,0,0,0) )
      image(qr,  asp = 1, col = c("white", "black"), axes = FALSE,  
            useRaster = TRUE,
            xlab = glue::glue(
              "Draft: Project ({project_id}), user_id: {user_id}"), ylab = "")
      # par(pp)
    }
    invisible(qr)
  }
  )

@florianm
Copy link
Collaborator

florianm commented Jun 10, 2023

Reviving Dieter's neat idea with the options to add more settings to the QR code than Central currently exports, and to add a plaintext version of the QR content (app user name, maybe some settings) to the exported QR code.

Thanks to @ivangayton for suggesting the Python package as alternative https://pypi.org/project/segno/

@florianm
Copy link
Collaborator

florianm commented Nov 6, 2024

Cross-linking the Python version https://gist.github.com/lognaturel/d538a40901aad8e5057bf0aeb8081ea6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature a feature request or enhancement
Projects
None yet
Development

No branches or pull requests

2 participants