Browse Source

initial commit

master
boB Rudis 5 years ago
parent
commit
311d5d70bd
No known key found for this signature in database GPG Key ID: 1D7529BE14E2BBA9
  1. 1
      .Rbuildignore
  2. 12
      DESCRIPTION
  3. 2
      LICENSE
  4. 21
      LICENSE.md
  5. 7
      NAMESPACE
  6. 23
      R/deere-package.R
  7. 52
      R/src_mowerplus.R
  8. 14
      R/utils.R
  9. 90
      README.Rmd
  10. 203
      README.md
  11. BIN
      README_files/figure-gfm/mow-1.png
  12. BIN
      README_files/figure-gfm/mow-2.png
  13. BIN
      README_files/figure-gfm/mow-3.png
  14. 18
      man/deere.Rd
  15. 16
      man/from_coredata_ts.Rd
  16. 34
      man/src_mowerplus.Rd

1
.Rbuildignore

@ -15,3 +15,4 @@
^CONDUCT.*$ ^CONDUCT.*$
^CODE.*$ ^CODE.*$
^\.gitlab-ci\.yml$ ^\.gitlab-ci\.yml$
^LICENSE\.md$

12
DESCRIPTION

@ -1,6 +1,6 @@
Package: deere Package: deere
Type: Package Type: Package
Title: deere title goes here otherwise CRAN checks fail Title: Catchall Functions for All Things 'John Deere'
Version: 0.1.0 Version: 0.1.0
Date: 2019-06-10 Date: 2019-06-10
Authors@R: c( Authors@R: c(
@ -8,18 +8,20 @@ Authors@R: c(
comment = c(ORCID = "0000-0001-5670-2640")) comment = c(ORCID = "0000-0001-5670-2640"))
) )
Maintainer: Bob Rudis <bob@rud.is> Maintainer: Bob Rudis <bob@rud.is>
Description: A good description goes here otherwise CRAN checks fail. Description: Initially a convenience package to access 'John Deere' 'MowerPlus' databases
from 'iOS' backups but perpaps will be something more all-encompassing.
URL: https://gitlab.com/hrbrmstr/deere URL: https://gitlab.com/hrbrmstr/deere
BugReports: https://gitlab.com/hrbrmstr/deere/issues BugReports: https://gitlab.com/hrbrmstr/deere/issues
Encoding: UTF-8 Encoding: UTF-8
License: AGPL License: MIT + file LICENSE
Suggests: Suggests:
testthat, testthat,
covr covr
Depends: Depends:
R (>= 3.2.0) R (>= 3.2.0)
Imports: Imports:
httr, tibble,
jsonlite dplyr,
RSQLite
Roxygen: list(markdown = TRUE) Roxygen: list(markdown = TRUE)
RoxygenNote: 6.1.1 RoxygenNote: 6.1.1

2
LICENSE

@ -0,0 +1,2 @@
YEAR: 2019
COPYRIGHT HOLDER: Bob Rudis

21
LICENSE.md

@ -0,0 +1,21 @@
# MIT License
Copyright (c) 2019 Bob Rudis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

7
NAMESPACE

@ -1,4 +1,7 @@
# Generated by roxygen2: do not edit by hand # Generated by roxygen2: do not edit by hand
import(httr) export(from_coredata_ts)
importFrom(jsonlite,fromJSON) export(src_mowerplus)
import(tibble)
importFrom(dplyr,src_sqlite)
importFrom(dplyr,tbl)

23
R/deere-package.R

@ -1,12 +1,21 @@
#' ... #' Catchall Functions for All Things 'John Deere'
#' #'
#' Initially a convenience package to access 'John Deere' 'MowerPlus' databases from
#' 'iOS' backups but perpaps will be something more all-encompassing.
#'
#' Ref:
#'
#' - <https://rud.is/b/2019/06/02/trawling-through-ios-backups-for-treasure-a-k-a-how-to-fish-for-target-files-in-ios-backups-with-r/>
#' - <https://rud.is/b/2019/06/09/wrapping-up-exploration-of-john-deeres-mowerplus-database/>
#'
#'
#' - URL: <https://gitlab.com/hrbrmstr/deere> #' - URL: <https://gitlab.com/hrbrmstr/deere>
#' - BugReports: <https://gitlab.com/hrbrmstr/deere/issues> #' - BugReports: <https://gitlab.com/hrbrmstr/deere/issues>
#' #'
#' @md #' @md
#' @name deere #' @name deere
#' @docType package #' @keywords internal
#' @author Bob Rudis (bob@@rud.is) #' @author Bob Rudis (bob@@rud.is)
#' @import httr #' @import tibble
#' @importFrom jsonlite fromJSON #' @importFrom dplyr tbl src_sqlite
NULL "_PACKAGE"

52
R/src_mowerplus.R

@ -0,0 +1,52 @@
#' Find and sync a copy of the latest MowerPlus database file from an iOS backup
#'
#' @md
#' @note You may need to [setup permissions](https://rud.is/b/2019/06/02/trawling-through-ios-backups-for-treasure-a-k-a-how-to-fish-for-target-files-in-ios-backups-with-r/)
#' to be able to use this method depending on which macOS version you're on.
#' @param backup_id the giant hex string of a folder name
#' @param data_loc where `mowtrack.sqlite` will be sync'd
#' @param overwrite nuke ^^ if present (def: `TRUE`)
#' @export
#' @examples \dontrun{
#' mow_db <- src_mowerplus("28500cd31b9580aaf5815c695ebd3ea5f7455628")
#'
#' mow_db
#'
#' glimpse(tbl(mow_db, "ZMOWER"))
#'
#' glimpse(tbl(mow_db, "ZACTIVITY"))
#'
#' }
src_mowerplus <- function(backup_id, data_loc = "~/Data", overwrite = TRUE) {
# root of mobile backup dir for `backup_id`
mb <- path.expand(file.path("~/Library/Application Support/MobileSync/Backup", backup_id))
stopifnot(dir.exists(mb))
data_loc <- path.expand(data_loc)
stopifnot(dir.exists(data_loc))
tf <- tempfile(fileext = ".sqlite")
on.exit(unlink(tf), add=TRUE)
# path to the extracted sqlite file
out_db <- file.path(data_loc, "mowtrack.sqlite")
file.copy(file.path(mb, "Manifest.db"), tf, overwrite = TRUE)
manifest_db <- src_sqlite(tf)
fils <- tbl(manifest_db, "Files")
filter(fils, relativePath == "Library/Application Support/MowTracking.sqlite") %>%
pull(fileID) -> mowtrackdb_loc
file.copy(
file.path(mb, sprintf("%s/%s", substr(mowtrackdb_loc, 1, 2), mowtrackdb_loc)),
file.path(data_loc, "mowtrack.sqlite"),
overwrite = overwrite
)
src_sqlite(out_db)
}

14
R/utils.R

@ -0,0 +1,14 @@
#' Convert timestampes from Apple "CoreData" format to something usable
#'
#' @md
#' @param x timestamps in Apple "CoreData" format(dates or times)
#' @param tz passed on to the convertion to a `POSIXct` object. Def: `NULL`.
#' @export
from_coredata_ts <- function(x, tz = NULL) {
.POSIXct(ifelse(
test = floor(log10(x)) >= 10, # If you're still using R in 2317 then good on ya and edit this
yes = as.POSIXct(x/10e8, origin = "2001-01-01"), # nanoseconds coredata
no = as.POSIXct(x, origin = "2001-01-01") # seconds coredata
), tz = tz)
}

90
README.Rmd

@ -4,7 +4,7 @@ editor_options:
chunk_output_type: inline chunk_output_type: inline
--- ---
```{r pkg-knitr-opts, include=FALSE} ```{r pkg-knitr-opts, include=FALSE}
knitr$opts_chunk$set(collapse=TRUE, fig.retina=2, message=FALSE, warning=FALSE) knitr::opts_chunk$set(collapse=TRUE, fig.retina=2, message=FALSE, warning=FALSE)
options(width=120) options(width=120)
``` ```
@ -14,8 +14,17 @@ options(width=120)
# deere # deere
Catchall Functions for All Things 'John Deere'
## Description ## Description
Initially a convenience package to access 'John Deere' 'MowerPlus' databases from 'iOS' backups but perpaps will be something more all-encompassing.
Ref:
- <https://rud.is/b/2019/06/02/trawling-through-ios-backups-for-treasure-a-k-a-how-to-fish-for-target-files-in-ios-backups-with-r/>
- <https://rud.is/b/2019/06/09/wrapping-up-exploration-of-john-deeres-mowerplus-database/>
## What's Inside The Tin ## What's Inside The Tin
The following functions are implemented: The following functions are implemented:
@ -38,12 +47,91 @@ devtools::install_github("hrbrmstr/deere")
```{r lib-ex} ```{r lib-ex}
library(deere) library(deere)
library(hrbrthemes)
library(tidyverse)
# current version # current version
packageVersion("deere") packageVersion("deere")
``` ```
### Sample from a recent mow
```{r mow, cache=TRUE}
mow_db <- src_mowerplus("28500cd31b9580aaf5815c695ebd3ea5f7455628")
mow_db
glimpse(tbl(mow_db, "ZMOWER"))
glimpse(tbl(mow_db, "ZACTIVITY"))
tbl(mow_db, "ZACTIVITY")%>%
collect() -> activity
activity %>%
select(
mow_date = ZCREATEDAT,
area_covered = ZAREACOVERED,
avg_speed = ZAVERAGESPEED,
distance = ZDISTANCEMOWED,
duration = ZMOWINGTIME
) %>%
arrange(mow_date) %>%
mutate(
duration = duration / 60 / 60, # hours
mow_date = format(from_coredata_ts(mow_date), "%b %d"), # factors make better bars
mow_date = factor(mow_date, levels = unique(mow_date)) # when there are just 2-of-em
) %>%
gather(measure, value, -mow_date) %>%
ggplot(aes(mow_date, value)) +
geom_col(aes(fill = measure), width = 0.5, show.legend = FALSE) +
scale_y_comma() +
scale_fill_ipsum() +
facet_wrap(~measure, scales = "free") +
theme_ipsum_rc(grid="Y")
zloc <- tbl(mow_db, "ZMOWLOCATION")
zloc %>%
select(
id = ZSESSION,
zorder = ZORDER,
lat = ZLATITUDE,
lng = ZLONGITUDE,
speed = ZSPEED,
ts = ZTIMESTAMP
) %>%
collect() %>%
mutate(
id = factor(id),
ts = from_coredata_ts(ts)
) -> sessions
ggplot(sessions, aes(id, speed)) +
ggbeeswarm::geom_quasirandom(
aes(fill = id), show.legend = FALSE,
shape = 21, size = 2, color = "white", stroke = 0.75
) +
scale_fill_ipsum() +
labs(x = "Mowing Session", y = "MPH", title = "Mowing Speed Comparison (mph)") +
theme_ipsum_rc(grid="Y")
arrange(sessions, ts) %>%
ggplot(aes(lng, lat)) +
geom_path(
aes(color = id, group = id), show.legend = FALSE,
size = 1, alpha = 1/2
) +
scale_color_ipsum() +
coord_quickmap() +
facet_wrap(~id) +
labs(title = "Mowing Path Comparison") +
theme_ipsum_rc(grid="Y") +
ggthemes::theme_map()
```
## deere Metrics ## deere Metrics
```{r cloc, echo=FALSE} ```{r cloc, echo=FALSE}

203
README.md

@ -1,2 +1,205 @@
[![Travis-CI Build
Status](https://travis-ci.org/hrbrmstr/deere.svg?branch=master)](https://travis-ci.org/hrbrmstr/deere)
[![Coverage
Status](https://codecov.io/gh/hrbrmstr/deere/branch/master/graph/badge.svg)](https://codecov.io/gh/hrbrmstr/deere)
[![CRAN\_Status\_Badge](http://www.r-pkg.org/badges/version/deere)](https://cran.r-project.org/package=deere)
# deere # deere
Catchall Functions for All Things ‘John Deere’
## Description
Initially a convenience package to access ‘John Deere’ ‘MowerPlus’
databases from ‘iOS’ backups but perpaps will be something more
all-encompassing.
Ref:
- <https://rud.is/b/2019/06/02/trawling-through-ios-backups-for-treasure-a-k-a-how-to-fish-for-target-files-in-ios-backups-with-r/>
- <https://rud.is/b/2019/06/09/wrapping-up-exploration-of-john-deeres-mowerplus-database/>
## What’s Inside The Tin
The following functions are implemented:
## Installation
``` r
devtools::install_git("https://git.sr.ht/~hrbrmstr/deere.git")
# or
devtools::install_git("https://git.rud.is/hrbrmstr/deere.git")
# or
devtools::install_gitlab("hrbrmstr/deere")
# or
devtools::install_bitbucket("hrbrmstr/deere")
# or
devtools::install_github("hrbrmstr/deere")
```
## Usage
``` r
library(deere)
library(hrbrthemes)
library(tidyverse)
# current version
packageVersion("deere")
## [1] '0.1.0'
```
### Sample from a recent mow
``` r
mow_db <- src_mowerplus("28500cd31b9580aaf5815c695ebd3ea5f7455628")
mow_db
## src: sqlite 3.22.0 [/Users/hrbrmstr/Data/mowtrack.sqlite]
## tbls: Z_METADATA, Z_MODELCACHE, Z_PRIMARYKEY, ZACTIVITY, ZDEALER, ZMOWALERT, ZMOWER, ZMOWLOCATION, ZSMARTCONNECTOR,
## ZUSER
glimpse(tbl(mow_db, "ZMOWER"))
## Observations: ??
## Variables: 23
## Database: sqlite 3.22.0 [/Users/hrbrmstr/Data/mowtrack.sqlite]
## $ Z_PK <int> 1
## $ Z_ENT <int> 7
## $ Z_OPT <int> 11
## $ ZDECKSIZEINCHES <int> 48
## $ ZDISMISSEDFULLSERVICETASK <int> 0
## $ ZDISMISSEDPERIODICTASK <int> 0
## $ ZSMARTCONNECTOR <int> NA
## $ ZUSER <int> 1
## $ ZBATTERYCHARGE <dbl> NA
## $ ZENGINEHOURS <dbl> 3.474705
## $ ZFULLSERVICEPERFORMED <dbl> NA
## $ ZHMCLASTSEEN <dbl> NA
## $ ZHMCOFFSET <dbl> 0
## $ ZPERIODICSERVICEPERFORMED <dbl> NA
## $ ZSCLASTCONNECTED <dbl> NA
## $ ZGENERICTYPE <chr> NA
## $ ZHMCIDENTIFIER <chr> NA
## $ ZMODEL <chr> "E140"
## $ ZSCPIN <chr> NA
## $ ZSCPERIPHERALID <chr> NA
## $ ZSERIALNUMBER <chr> "1GXE140EKKK116940"
## $ ZSERIES <chr> "E100"
## $ ZSCDATADICTIONARY <blob> <NA>
glimpse(tbl(mow_db, "ZACTIVITY"))
## Observations: ??
## Variables: 20
## Database: sqlite 3.22.0 [/Users/hrbrmstr/Data/mowtrack.sqlite]
## $ Z_PK <int> 1, 2
## $ Z_ENT <int> 3, 3
## $ Z_OPT <int> 124, 93
## $ ZMONTH <int> 6, 6
## $ ZYEAR <int> 2019, 2019
## $ ZMOWER <int> 1, 1
## $ ZUSER <int> 1, 1
## $ ZISCOMPLETE <int> 1, 1
## $ ZISMISSEDMOW <int> 0, 0
## $ ZLASTLOCATION <int> 7016, 12548
## $ ZCREATEDAT <dbl> 581100260, 581778616
## $ ZENGINEHOURS <dbl> NA, NA
## $ ZAREACOVERED <dbl> 3.761875, 2.286811
## $ ZAVERAGESPEED <dbl> 3.727754, 2.894269
## $ ZDISTANCEMOWED <dbl> 7.758894, 4.716564
## $ ZMOWINGTIME <dbl> 6960.000, 5548.939
## $ ZNOTES <chr> "First mow!", NA
## $ ZINTERVALNAME <chr> NA, NA
## $ ZTYPE <chr> NA, NA
## $ ZUUID <blob> blob[238 B], blob[238 B]
tbl(mow_db, "ZACTIVITY")%>%
collect() -> activity
activity %>%
select(
mow_date = ZCREATEDAT,
area_covered = ZAREACOVERED,
avg_speed = ZAVERAGESPEED,
distance = ZDISTANCEMOWED,
duration = ZMOWINGTIME
) %>%
arrange(mow_date) %>%
mutate(
duration = duration / 60 / 60, # hours
mow_date = format(from_coredata_ts(mow_date), "%b %d"), # factors make better bars
mow_date = factor(mow_date, levels = unique(mow_date)) # when there are just 2-of-em
) %>%
gather(measure, value, -mow_date) %>%
ggplot(aes(mow_date, value)) +
geom_col(aes(fill = measure), width = 0.5, show.legend = FALSE) +
scale_y_comma() +
scale_fill_ipsum() +
facet_wrap(~measure, scales = "free") +
theme_ipsum_rc(grid="Y")
```
<img src="README_files/figure-gfm/mow-1.png" width="672" />
``` r
zloc <- tbl(mow_db, "ZMOWLOCATION")
zloc %>%
select(
id = ZSESSION,
zorder = ZORDER,
lat = ZLATITUDE,
lng = ZLONGITUDE,
speed = ZSPEED,
ts = ZTIMESTAMP
) %>%
collect() %>%
mutate(
id = factor(id),
ts = from_coredata_ts(ts)
) -> sessions
ggplot(sessions, aes(id, speed)) +
ggbeeswarm::geom_quasirandom(
aes(fill = id), show.legend = FALSE,
shape = 21, size = 2, color = "white", stroke = 0.75
) +
scale_fill_ipsum() +
labs(x = "Mowing Session", y = "MPH", title = "Mowing Speed Comparison (mph)") +
theme_ipsum_rc(grid="Y")
```
<img src="README_files/figure-gfm/mow-2.png" width="672" />
``` r
arrange(sessions, ts) %>%
ggplot(aes(lng, lat)) +
geom_path(
aes(color = id, group = id), show.legend = FALSE,
size = 1, alpha = 1/2
) +
scale_color_ipsum() +
coord_quickmap() +
facet_wrap(~id) +
labs(title = "Mowing Path Comparison") +
theme_ipsum_rc(grid="Y") +
ggthemes::theme_map()
```
<img src="README_files/figure-gfm/mow-3.png" width="672" />
## deere Metrics
| Lang | \# Files | (%) | LoC | (%) | Blank lines | (%) | \# Lines | (%) |
| :--- | -------: | ---: | --: | ---: | ----------: | ---: | -------: | ---: |
| Rmd | 1 | 0.17 | 74 | 0.69 | 31 | 0.69 | 39 | 0.45 |
| R | 5 | 0.83 | 33 | 0.31 | 14 | 0.31 | 48 | 0.55 |
## Code of Conduct
Please note that this project is released with a [Contributor Code of
Conduct](CONDUCT.md). By participating in this project you agree to
abide by its terms.

BIN
README_files/figure-gfm/mow-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
README_files/figure-gfm/mow-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

BIN
README_files/figure-gfm/mow-3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 KiB

18
man/deere.Rd

@ -4,13 +4,29 @@
\name{deere} \name{deere}
\alias{deere} \alias{deere}
\alias{deere-package} \alias{deere-package}
\title{...} \title{Catchall Functions for All Things 'John Deere'}
\description{ \description{
Initially a convenience package to access 'John Deere' 'MowerPlus' databases from
'iOS' backups but perpaps will be something more all-encompassing.
}
\details{
Ref:
\itemize{ \itemize{
\item \url{https://rud.is/b/2019/06/02/trawling-through-ios-backups-for-treasure-a-k-a-how-to-fish-for-target-files-in-ios-backups-with-r/}
\item \url{https://rud.is/b/2019/06/09/wrapping-up-exploration-of-john-deeres-mowerplus-database/}
\item URL: \url{https://gitlab.com/hrbrmstr/deere} \item URL: \url{https://gitlab.com/hrbrmstr/deere}
\item BugReports: \url{https://gitlab.com/hrbrmstr/deere/issues} \item BugReports: \url{https://gitlab.com/hrbrmstr/deere/issues}
} }
} }
\seealso{
Useful links:
\itemize{
\item \url{https://gitlab.com/hrbrmstr/deere}
\item Report bugs at \url{https://gitlab.com/hrbrmstr/deere/issues}
}
}
\author{ \author{
Bob Rudis (bob@rud.is) Bob Rudis (bob@rud.is)
} }
\keyword{internal}

16
man/from_coredata_ts.Rd

@ -0,0 +1,16 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{from_coredata_ts}
\alias{from_coredata_ts}
\title{Convert timestampes from Apple "CoreData" format to something usable}
\usage{
from_coredata_ts(x, tz = NULL)
}
\arguments{
\item{x}{timestamps in Apple "CoreData" format(dates or times)}
\item{tz}{passed on to the convertion to a \code{POSIXct} object. Def: \code{NULL}.}
}
\description{
Convert timestampes from Apple "CoreData" format to something usable
}

34
man/src_mowerplus.Rd

@ -0,0 +1,34 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/src_mowerplus.R
\name{src_mowerplus}
\alias{src_mowerplus}
\title{Find and sync a copy of the latest MowerPlus database file from an iOS backup}
\usage{
src_mowerplus(backup_id, data_loc = "~/Data", overwrite = TRUE)
}
\arguments{
\item{backup_id}{the giant hex string of a folder name}
\item{data_loc}{where \code{mowtrack.sqlite} will be sync'd}
\item{overwrite}{nuke ^^ if present (def: \code{TRUE})}
}
\description{
Find and sync a copy of the latest MowerPlus database file from an iOS backup
}
\note{
You may need to \href{https://rud.is/b/2019/06/02/trawling-through-ios-backups-for-treasure-a-k-a-how-to-fish-for-target-files-in-ios-backups-with-r/}{setup permissions}
to be able to use this method depending on which macOS version you're on.
}
\examples{
\dontrun{
mow_db <- src_mowerplus("28500cd31b9580aaf5815c695ebd3ea5f7455628")
mow_db
glimpse(tbl(mow_db, "ZMOWER"))
glimpse(tbl(mow_db, "ZACTIVITY"))
}
}
Loading…
Cancel
Save