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 |
|||
|
|||
import(httr) |
|||
importFrom(jsonlite,fromJSON) |
|||
export(from_coredata_ts) |
|||
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> |
|||
#' - BugReports: <https://gitlab.com/hrbrmstr/deere/issues> |
|||
#' |
|||
#' @md |
|||
#' @name deere |
|||
#' @docType package |
|||
#' @keywords internal |
|||
#' @author Bob Rudis (bob@@rud.is) |
|||
#' @import httr |
|||
#' @importFrom jsonlite fromJSON |
|||
NULL |
|||
#' @import tibble |
|||
#' @importFrom dplyr tbl src_sqlite |
|||
"_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 |
|||
|
|||
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