@@ -5,10 +5,11 @@ defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriber do
55 import Core.Dependency , only: [ call: 3 ]
66
77 alias TeslaMate.Mqtt.Publisher
8+ alias TeslaMate.RuntimeHealth
89 alias TeslaMate.Vehicles.Vehicle.Summary
910 alias TeslaMate.Vehicles
1011
11- defstruct [ :car_id , :last_values , :deps , :namespace ]
12+ defstruct [ :car_id , :last_values , :deps , :namespace , mqtt_generation: 0 ]
1213 alias __MODULE__ , as: State
1314
1415 def child_spec ( arg ) do
@@ -45,13 +46,31 @@ defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriber do
4546
4647 deps = % {
4748 vehicles: Keyword . get ( opts , :deps_vehicles , Vehicles ) ,
48- publisher: Keyword . get ( opts , :deps_publisher , Publisher )
49+ publisher: Keyword . get ( opts , :deps_publisher , Publisher ) ,
50+ runtime_health: Keyword . get ( opts , :deps_runtime_health , RuntimeHealth )
4951 }
5052
5153 :ok = call ( deps . vehicles , :subscribe_to_summary , [ car_id ] )
54+ :ok = call ( deps . runtime_health , :subscribe_mqtt , [ ] )
5255 :ok = clear_retained ( car_id , namespace , deps . publisher )
5356
54- { :ok , % State { car_id: car_id , namespace: namespace , deps: deps } }
57+ mqtt_snapshot = call ( deps . runtime_health , :mqtt_snapshot , [ ] )
58+
59+ mqtt_generation =
60+ if mqtt_snapshot . status == :ok do
61+ send ( self ( ) , { :mqtt_reconnected , mqtt_snapshot . generation } )
62+ nil
63+ else
64+ mqtt_snapshot . generation
65+ end
66+
67+ { :ok ,
68+ % State {
69+ car_id: car_id ,
70+ namespace: namespace ,
71+ deps: deps ,
72+ mqtt_generation: mqtt_generation
73+ } }
5574 end
5675
5776 @ publish_if_nil ~w( charge_energy_added charger_actual_current charger_phases
@@ -60,14 +79,36 @@ defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriber do
6079
6180 @ impl true
6281 def handle_info ( % Summary { } = summary , % State { } = state ) do
82+ state = reconcile_mqtt_generation ( state )
83+ publish_summary ( summary , state )
84+ end
85+
86+ def handle_info ( { :mqtt_reconnected , generation } , % State { } = state ) do
87+ mqtt_snapshot = current_mqtt_snapshot ( state )
88+
89+ if mqtt_snapshot . status == :ok and mqtt_snapshot . generation == generation and
90+ generation != state . mqtt_generation do
91+ state = % { state | last_values: nil , mqtt_generation: generation }
92+
93+ case current_summary ( state ) do
94+ % Summary { } = summary -> publish_summary ( summary , state )
95+ _other -> { :noreply , state }
96+ end
97+ else
98+ { :noreply , state }
99+ end
100+ end
101+
102+ defp publish_summary ( % Summary { } = summary , % State { } = state ) do
63103 values =
64104 % { }
65105 |> add_simple_values ( summary )
66106 |> add_car_latitude_longitude ( summary )
67107 |> add_geofence ( summary )
68108 |> add_active_route ( summary )
69109
70- last_values = publish_values ( values , state )
110+ { last_values , result } = publish_values ( values , state )
111+ :ok = call ( state . deps . runtime_health , :record_mqtt_publish , [ state . car_id , result ] )
71112 { :noreply , % { state | last_values: last_values } }
72113 end
73114
@@ -86,17 +127,21 @@ defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriber do
86127 on_timeout: :kill_task ,
87128 ordered: false
88129 )
89- |> Enum . reduce ( last_values , fn
90- { :ok , { { key , value } , :ok } } , acc ->
91- Map . put ( acc , key , value )
130+ |> Enum . reduce ( { last_values , 0 } , fn
131+ { :ok , { { key , value } , :ok } } , { acc , failures } ->
132+ { Map . put ( acc , key , value ) , failures }
92133
93- { :ok , { _entry , reason } } , acc ->
134+ { :ok , { _entry , reason } } , { acc , failures } ->
94135 Logger . warning ( "MQTT publishing failed: #{ inspect ( reason ) } " )
95- acc
136+ { acc , failures + 1 }
96137
97- { :exit , reason } , acc ->
138+ { :exit , reason } , { acc , failures } ->
98139 Logger . warning ( "MQTT publishing failed: #{ inspect ( reason ) } " )
99- acc
140+ { acc , failures + 1 }
141+ end )
142+ |> then ( fn
143+ { values , 0 } -> { values , :ok }
144+ { values , _failures } -> { values , { :error , :publish_failed } }
100145 end )
101146 end
102147
@@ -214,4 +259,29 @@ defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriber do
214259
215260 defp to_str ( % DateTime { } = datetime ) , do: DateTime . to_iso8601 ( datetime )
216261 defp to_str ( value ) , do: to_string ( value )
262+
263+ defp current_summary ( % State { car_id: car_id , deps: deps } ) do
264+ call ( deps . vehicles , :summary , [ car_id ] )
265+ catch
266+ :exit , reason ->
267+ Logger . warning ( "Could not republish MQTT snapshot: #{ inspect ( reason ) } " )
268+ nil
269+ end
270+
271+ defp reconcile_mqtt_generation ( % State { } = state ) do
272+ case current_mqtt_snapshot ( state ) do
273+ % { status: :ok , generation: generation }
274+ when not is_nil ( generation ) and generation != state . mqtt_generation ->
275+ % { state | last_values: nil , mqtt_generation: generation }
276+
277+ _snapshot ->
278+ state
279+ end
280+ end
281+
282+ defp current_mqtt_snapshot ( % State { deps: deps } ) do
283+ call ( deps . runtime_health , :mqtt_snapshot , [ ] )
284+ catch
285+ :exit , _reason -> % { status: :unavailable , generation: nil }
286+ end
217287end
0 commit comments