A Spring Framework 6 / Spring Boot 3 service that reads your calendar, predicts commute timing with Google Maps Routes, checks recent weather via Google Weather, and automatically manages one-time preconditioning schedules on your Tesla (via your Fleet/Tessie integration). It persists schedule links in PostgreSQL and uses Liquibase for schema management.
- Calendar ingest: Google Calendar (service account + optional domain-wide delegation/impersonation).
- Route ETA: Google Maps Routes API with field masks for minimal response size.
- Weather-aware: Google Weather API (current, forecast, hourly history). Built-in heuristic for snow/ice accumulation risk over the last 12h (recency- and temperature-weighted).
- Tesla scheduling: Adds one-time preconditioning entries; then asynchronously verifies creation after a configurable delay using a new DB transaction.
- PostgreSQL + Liquibase: Reliable persistence and versioned schema.
- Testcontainers in tests; Java 24 toolchain.
- Google Calendar -> Event window & attendees
- Google Routes -> ETA / duration (field-masked)
- Google Weather -> Current + Forecast + Hourly History (prev 24h)
- Service Logic -> preconditioning window, buffer, weather risk
- Tesla Fleet/Tessie -> add one-time preconditioning schedule
- PostgreSQL -> CalendarPreConditionLinkEntity (schedule linkage)
- Java 24 (JDK 24)
- Maven 3.9+
- PostgreSQL 14+
- Google Cloud project with:
- Calendar API, Routes API, Weather API enabled
- Service account credentials (JSON)
- Teslemetry API token
# build
./mvnw clean package
# run
java -jar target/Tesla-Automatic-Preconditioning-0.0.39.jarThis application requires sensitive credentials. NEVER commit these to version control!
src/main/resources/google/*.json- Google service account credentials (you should consider mounting these as a separate dir in a deployment)src/main/resources/application.yml- Your actual configuration with secrets.envfiles - Environment-specific configurations*.pem,*.key,*.p12- Any private keys or certificates
-
Copy the template configuration:
cp application.yml.template src/main/resources/application.yml
-
Edit
src/main/resources/application.ymlwith your actual values:- Google API credentials
- Teslemetry token
- Email/SMTP credentials
- Tesla VINs
- Home coordinates
- SMS notification recipients
-
Store Google service account JSON:
- Place your service account JSON file in
src/main/resources/google/ - Update
google.calendar.credentials-pathin application.yml to point to it - This file is automatically ignored by git
- Place your service account JSON file in
-
Never commit secrets:
- The
.gitignoreis configured to exclude all credential files - Always use the template file (
application.yml.template) as a reference - For production, consider using environment variables or a secrets manager
- The
Instead of storing secrets in application.yml, use environment variables:
export GOOGLE_WEATHER_API_KEY="your-api-key"
export TESLEMETRY_TOKEN="your-token"
export SPRING_MAIL_USERNAME="your-email@example.com"
export SPRING_MAIL_PASSWORD="your-password"
export APP_NOTIFICATIONS_SMS_RECIPIENTS="phone1@gateway.com,phone2@gateway.com"Then reference them in application.yml:
google:
weather:
api-key: ${GOOGLE_WEATHER_API_KEY}google:
calendar:
credentials-path: file:/path/to/your/service-account.json #get one by creating a service account with access to routes/calendar apis in google cloud console
application-name: Tesla Automatic Preconditioning
name: REPLACE_WITH_YOURS@group.calendar.google.com
maps:
routes:
field-mask: routes.distanceMeters,routes.duration,routes.routeLabels,routes.routeToken
weather:
api-key: YOUR_GOOGLE_WEATHER_API_KEY #https://developers.google.com/maps/documentation/weather and "get started", lots of free usage included
base-url: https://weather.googleapis.com/v1
units: IMPERIAL
teslemetry:
oauth:
token: YOUR_TESLEMETRY_TOKEN #change me, get yours @ https://teslemetry.com/
spring:
mail:
host: smtp.gmail.com
port: 587
username: YOUR_EMAIL_USERNAME #change me
password: YOUR_EMAIL_PASSWORD #change me
properties:
mail.smtp.auth: true
mail.smtp.starttls.enable: true
datasource:
url: jdbc:h2:mem:testdb
username: sa
password: password
liquibase:
enabled: true
change-log: file:/resources/db/changelog/17-01-changelog.yaml
tesla:
vin:
csv: ASSIGNEE_EMAIL_ONE:TESLA_VIN_ONE,ASSIGNEE_EMAIL_ONE:TESLA_VIN_TWO
home:
lat: YOUR_HOME_LAT #change me, 12.3456789
lon: YOUR_HOME_LON #change me, -12.3456789
preconditioning:
buffer:
minutes: 10
scheduling:
out-of-metro-threshold-miles: 100
rescheduling:
enabled: true
movement-threshold-miles: 25
time-savings-threshold-minutes: 30
minimum-time-remaining-hours: 2
app:
api:
secret-key: TO_SECURE_ENDPOINTS #change me
export GOOGLE_WEATHER_API_KEY="..."
export TESSIE_TOKEN="..."- Enable Calendar, Routes, Weather APIs in GCP Console.
- Create an API key for Weather in APIs & Services → Credentials.
- Restrict the key (application & API restrictions).
- For Calendar, provision a service account JSON, optionally with domain-wide delegation.
- Load Calendar events.
- For each:
- Compute route ETA (Google Routes).
- Derive preconditioning start (ETA - buffer - duration).
- Fetch recent weather, compute snow/ice risk with recency & temperature weighting.
- If appropriate, schedule preconditioning.
- After a configurable delay, verify with a new transaction that the schedule exists.
boolean highRisk = weatherService.hasHighSnowOrIceCoverageLast12h(
lat, lon, Optional.of(WeatherService.UnitsSystem.IMPERIAL)
);- Uses /history/hours:lookup for last 12h.
- Weighted by recency (half-life ~6h) and temperature (melt/boost factors).
- Score normalized [0,1], high risk threshold ~0.55.
./mvnw test
./mvnw packageTests use Testcontainers for PostgreSQL.
- 404 → ensure :lookup isn’t escaped (use .build(true)).
- 401/403 → check key restrictions & billing.
- Calendar issues → verify JSON path & impersonation.
- Tesla schedule missing → increase completion delay.
export GHCR_NS=javadevjt
export IMAGE_NAME=tesla-automatic-preconditioning
export IMAGE_ID=ghcr.io/${GHCR_NS}/${IMAGE_NAME}
export VERSION=0.0.39podman build --arch amd64 -t ${IMAGE_ID}:amd64-${VERSION} .
podman build --arch arm64 -t ${IMAGE_ID}:arm64-${VERSION} .
podman push ${IMAGE_ID}:amd64-${VERSION}
podman push ${IMAGE_ID}:arm64-${VERSION}
podman manifest rm ${IMAGE_ID}:${VERSION} 2>/dev/null || true
podman manifest create ${IMAGE_ID}:${VERSION}
podman manifest add ${IMAGE_ID}:${VERSION} ${IMAGE_ID}:amd64-${VERSION}
podman manifest add ${IMAGE_ID}:${VERSION} ${IMAGE_ID}:arm64-${VERSION}
podman manifest push ${IMAGE_ID}:${VERSION}
podman manifest rm ${IMAGE_ID}:latest 2>/dev/null || true
podman manifest create ${IMAGE_ID}:latest
podman manifest add ${IMAGE_ID}:latest ${IMAGE_ID}:amd64-${VERSION}
podman manifest add ${IMAGE_ID}:latest ${IMAGE_ID}:arm64-${VERSION}
podman manifest push ${IMAGE_ID}:latest
For efficiency, all container operations can be executed in a single command sequence:
export GHCR_NS=javadevjt && export IMAGE_NAME=tesla-automatic-preconditioning && export IMAGE_ID=ghcr.io/${GHCR_NS}/${IMAGE_NAME} && export VERSION=0.0.39 && \
echo "Starting container build and push process for version ${VERSION}" && \
echo "Building AMD64 image..." && podman build --arch amd64 -t ${IMAGE_ID}:amd64-${VERSION} . && \
echo "Building ARM64 image..." && podman build --arch arm64 -t ${IMAGE_ID}:arm64-${VERSION} . && \
echo "Pushing AMD64 image..." && podman push ${IMAGE_ID}:amd64-${VERSION} && \
echo "Pushing ARM64 image..." && podman push ${IMAGE_ID}:arm64-${VERSION} && \
echo "Creating version manifest..." && podman manifest rm ${IMAGE_ID}:${VERSION} 2>/dev/null || true && \
podman manifest create ${IMAGE_ID}:${VERSION} && \
podman manifest add ${IMAGE_ID}:${VERSION} ${IMAGE_ID}:amd64-${VERSION} && \
podman manifest add ${IMAGE_ID}:${VERSION} ${IMAGE_ID}:arm64-${VERSION} && \
echo "Pushing version manifest..." && podman manifest push ${IMAGE_ID}:${VERSION} && \
echo "Creating latest manifest..." && podman manifest rm ${IMAGE_ID}:latest 2>/dev/null || true && \
podman manifest create ${IMAGE_ID}:latest && \
podman manifest add ${IMAGE_ID}:latest ${IMAGE_ID}:amd64-${VERSION} && \
podman manifest add ${IMAGE_ID}:latest ${IMAGE_ID}:arm64-${VERSION} && \
echo "Pushing latest manifest..." && podman manifest push ${IMAGE_ID}:latest && \
echo "✅ All container operations completed successfully!"- Single Command Execution: Run all build and push operations without manual intervention
- Progress Monitoring: Clear echo statements show current operation status
- Error Handling: Uses
&&to fail fast on any error - Manifest Cleanup: Automatically removes existing manifests before recreation
- Version Flexibility: Simply update the
VERSIONvariable for new releases
- Podman/Docker: Container runtime installed and authenticated to GHCR
- Build Context: Run from project root directory
- Network: Stable internet connection for image pulls/pushes
- GHCR Access: Proper permissions to push to
ghcr.io/javadevjt/tesla-automatic-preconditioning