boB Rudis
5 years ago
16 changed files with 477 additions and 16 deletions
@ -0,0 +1,2 @@ |
|||||
|
YEAR: 2019 |
||||
|
COPYRIGHT HOLDER: Bob Rudis |
@ -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. |
@ -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) |
||||
|
@ -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" |
||||
|
@ -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) |
||||
|
|
||||
|
} |
@ -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) |
||||
|
} |
||||
|
|
@ -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. |
||||
|
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 267 KiB |
After Width: | Height: | Size: 575 KiB |
@ -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 |
||||
|
} |
@ -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…
Reference in new issue