tripmultiday

package
v0.40.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 30, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package tripmultiday orchestrates a multi-day trip plan over the trip planner (Rivian's planTripWithMultiStopV2, or the replay planners on installs with no Rivian access). The planner is single-day: it computes one route through up to ten waypoints with charging stops, but has no concept of "sleep here, depart tomorrow."

The orchestrator chains one planner call per day, daisied on departure SoC and departure time. Each day is a start, optional vias, and an end; day k+1 starts where day k ended. Between days an overnight model projects how much the truck charges while parked (see overnight).

Index

Constants

View Source
const (
	MaxDays           = 10
	MaxDayWaypoints   = 10   // the planner's limit per call, origin and destination included
	DefaultACMaxKW    = 11.5 // onboard AC charger ceiling when the model is unknown
	DefaultL2KW       = 7.0  // a typical hotel or home L2 when none is given
	DefaultReservePct = 10.0 // a day's arrival reserve when none was asked for
	MaxPlanTargetPct  = 90.0 // a raised reserve never asks for more than this

	DefaultCapPct = 80.0 // overnight charge stops here unless asked otherwise

)

Limits. A day is one planner call, so it takes at most the planner's waypoint count; the day count keeps one request inside its time budget.

Variables

This section is empty.

Functions

func OvernightSoC added in v0.37.0

func OvernightSoC(arrSoC, hours float64, l2kw *float64, packKWh, capPct float64) float64

OvernightSoC projects the state of charge after parking for hours at an L2 of l2kw (nil: the default; 0: none), from arrSoC, with the same model the planner chains days with. The AC ceiling is the default one and the charge stops at capPct (0: DefaultCapPct).

Types

type Day

type Day struct {
	Index        int              `json:"index"` // 1-based
	Plan         *rivian.TripPlan `json:"plan"`
	DepartAt     time.Time        `json:"depart_at"`
	ArriveAt     time.Time        `json:"arrive_at"`
	Start        Waypoint         `json:"start"`
	Vias         []Waypoint       `json:"vias,omitempty"`
	End          Waypoint         `json:"end"`
	DepartureSoC float64          `json:"departure_soc"` // SoC at day start
	ArrivalSoC   float64          `json:"arrival_soc"`   // SoC at day end (route 0)
	// ChainArrivalSoC is the arrival SoC the overnight and the next day were
	// worked from: the lower of the plan's and the corrected one. Set only
	// when a correction applied.
	ChainArrivalSoC    float64          `json:"chain_arrival_soc,omitempty"`
	DestinationReached bool             `json:"destination_reached"`
	SoCBelowLimit      bool             `json:"soc_below_limit"`
	Overnight          *OvernightResult `json:"overnight,omitempty"`
	// Problem is plain text for the user when the day cannot be driven as
	// planned: it does not reach its end, or ends below the reserve with no stop
	// that fixes it. Empty for a day that works.
	Problem string `json:"problem,omitempty"`
}

Day is one day's plan plus the overnight that ends it.

type DayInput added in v0.37.0

type DayInput struct {
	Vias []Waypoint
	End  Waypoint
	// DepartAt overrides when this day leaves (day 1: the request's StartAt).
	DepartAt *time.Time
	// TargetArrivalSoCPct is this day's reserve; nil takes the request's.
	TargetArrivalSoCPct *float64
	// Overnight ends the day; ignored on the last day.
	Overnight *Overnight
}

DayInput is one day of the trip.

type Overnight added in v0.37.0

type Overnight struct {
	// L2KW is the available AC power. nil means "not given" and takes
	// DefaultL2KW; an explicit 0 means no charging at this stop.
	L2KW *float64
	// Hookup is the connector an own stop was entered with ("nema_14_50", ...);
	// it is echoed in the result. L2KWGiven is whether the caller sent L2KW: when
	// it did not, the power is a default for the hookup and the result says so.
	Hookup    string
	L2KWGiven bool
	// Until is the local time ("15:04") the truck is wanted the next morning.
	// The next day departs at the next such time after arrival.
	Until string
	// ParkedHours, when positive, fixes the stay: the next day departs that
	// long after arrival (the older request shape).
	ParkedHours float64
	// MaxSoCPct caps the post-charge SoC; 0 takes the request's default.
	MaxSoCPct float64
	Name      string
}

Overnight describes the stop at the end of a day.

type OvernightResult

type OvernightResult struct {
	Name        string  `json:"name,omitempty"`
	ParkedHours float64 `json:"parked_hours"`
	L2KW        float64 `json:"l2_kw"`
	// Hookup echoes an own stop's connector; L2KWAssumed is true when the power
	// is the default for it rather than a value the user gave.
	Hookup      string `json:"hookup,omitempty"`
	L2KWAssumed bool   `json:"l2_kw_assumed,omitempty"`
	// PlannedKW is the power the plan charged at when the truck cannot draw all of
	// L2KW (it is limited to the truck's AC maximum); omitted when L2KW is what
	// was used. Show PlannedKW when present, else L2KW, as "planned at X kW".
	PlannedKW float64 `json:"planned_kw,omitempty"`
	// AddedKWh is the energy that went into the battery.
	AddedKWh         float64 `json:"added_kwh"`
	PostChargeSoCPct float64 `json:"post_charge_soc_pct"`
	// Capped reports the cap stopped the charge short of what the stay allowed.
	Capped bool `json:"capped"`
}

OvernightResult is the post-charge state at the end of a day. Always nil on the final day.

type Planner

type Planner = rivian.TripPlanner

Planner is what the orchestrator needs; tests plug a fake.

type Request

type Request struct {
	// Account-level inputs (forwarded to every day's planner call).
	VehicleID      string
	HasAdapter     *bool
	DriveMode      string
	TrailerProfile string

	// PackKWh is the usable pack, in kWh. Required for the overnight model.
	PackKWh float64
	// StartingSoC is the SoC at the start of day 1.
	StartingSoC float64

	// StartAt is when day 1 leaves; the zero value means Now.
	StartAt time.Time
	// Loc is the zone "until" times are read in; nil is UTC.
	Loc *time.Location
	// Now is the clock for an unset StartAt; nil is time.Now.
	Now func() time.Time

	Origin Waypoint
	Days   []DayInput

	// MaxOvernightSoCPct is the default overnight cap (0: DefaultCapPct).
	MaxOvernightSoCPct float64
	// ACMaxKW is the truck's AC ceiling (0: DefaultACMaxKW).
	ACMaxKW float64
	// TargetArrivalSoCPct is every day's reserve unless the day sets one.
	TargetArrivalSoCPct *float64

	NetworkPreferences []rivian.NetworkPreference

	// AdjustArrival, when set, is asked for each planned day (day is 1-based,
	// startSoC and target as the day was planned) for the arrival SoC after the
	// caller's corrections (weather, vehicle profile). The overnight and the
	// next day then start from the lower of that and the plan's own arrival, so
	// the chain is as conservative as what the user is shown. ok=false leaves
	// the plan's arrival in use.
	AdjustArrival func(ctx context.Context, day int, plan *rivian.TripPlan, startSoC float64, target *float64) (arrival float64, ok bool)
	// RoutePreferences applies to every day.
	RoutePreferences *rivian.RoutePreferences
}

Request is the orchestrator's input.

type RequestError added in v0.37.0

type RequestError struct {
	Day int
	Msg string
}

RequestError is a request the orchestrator refuses; Day is the 1-based day at fault, or 0 for the request as a whole. The handler answers it with 400.

func (*RequestError) Error added in v0.37.0

func (e *RequestError) Error() string

type Response

type Response struct {
	Days  []Day  `json:"days"`
	Total Totals `json:"total"`
	// Simulated is true when any day's plan was not the gateway's.
	Simulated bool `json:"simulated,omitempty"`
	// FailedDay is the day that could not be planned (1-based); the days
	// before it are intact and chaining stopped there, because the days after
	// cannot know their starting SoC. Error says why.
	FailedDay int    `json:"failed_day,omitempty"`
	Error     string `json:"error,omitempty"`
	// FailedErr is Error as an error, for the caller to classify. Not sent.
	FailedErr error `json:"-"`
	// PlannerFailure is true when FailedErr came from the planner rather than
	// from the orchestrator's own checks; its text is for the logs, not for
	// the user (the caller words the message).
	PlannerFailure bool `json:"-"`
}

Response is the aggregated multi-day plan.

func Plan

func Plan(ctx context.Context, p Planner, req Request) (*Response, error)

Plan runs the orchestrator. It returns an error only for a request it refuses (a *RequestError). A day the planner fails on is not an error: the days so far come back with FailedDay and Error set.

type Totals

type Totals struct {
	DistanceMeters      float64 `json:"distance_meters"`
	DriveDurationSec    int     `json:"drive_duration_sec"`
	ChargingDurationSec int     `json:"charging_duration_sec"`
}

Totals summarises the chained-day plan so the SPA can show one "Trip total: X mi, Y h drive, Z stops" line above the day cards.

type Waypoint

type Waypoint struct {
	Latitude  float64 `json:"latitude"`
	Longitude float64 `json:"longitude"`
	EntityID  string  `json:"entity_id,omitempty"`
	Label     string  `json:"label,omitempty"`
}

Waypoint is a place on the trip. Label is cosmetic: never sent to the planner, echoed back so the SPA can name the stop.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL