Skip to content

Commit 7486216

Browse files
authored
Use sync.Map to avoid concurrent read/iterate and write errors (#32)
* Use sync.Map * Formatting * Handling ABRP defaults * Drop main binary
1 parent bd3eab2 commit 7486216

File tree

4 files changed

+97
-67
lines changed

4 files changed

+97
-67
lines changed

internal/app/abrp.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package app
33
import (
44
"bytes"
55
"encoding/json"
6+
"fmt"
67
"log"
78
"net/http"
89
"time"
@@ -48,10 +49,10 @@ func abrpSendLoop(car *Car, endTime time.Time) {
4849
timer = 30
4950
}
5051

51-
car.abrpData["utc"] = int(time.Now().UTC().Unix())
52+
car.abrpData.Store("utc", int(time.Now().UTC().Unix()))
5253

5354
if car.state == "parked" || car.state == "online" || car.state == "suspended" || car.state == "asleep" {
54-
delete(car.abrpData, "kwh_charged")
55+
car.abrpData.Delete("kwh_charged")
5556
if timer%30 == 0 || timer > 30 {
5657
// parked, update every 30 seconds (avoid being offline for ABRP)
5758
abrpSend(car)
@@ -82,7 +83,13 @@ const abrpUrl = "https://api.iternio.com/1/tlm/send"
8283
func abrpSend(car *Car) {
8384
log.Println("Sending to ABRP...")
8485

85-
data, error := json.Marshal(map[string]interface{}{"tlm": car.abrpData})
86+
m := map[string]interface{}{}
87+
car.abrpData.Range(func(key, value interface{}) bool {
88+
m[fmt.Sprint(key)] = value
89+
return true
90+
})
91+
92+
data, error := json.Marshal(map[string]interface{}{"tlm": m})
8693
if error != nil {
8794
log.Println("Error marshalling data: " + error.Error())
8895
return

internal/app/car.go

Lines changed: 73 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,139 +2,150 @@ package app
22

33
import (
44
"strconv"
5+
"sync"
56
"time"
67
)
78

89
type Car struct {
910
number string
1011
state string
1112
previousState string
12-
tmData map[string]interface{}
13-
abrpData map[string]interface{}
13+
tmData *sync.Map
14+
abrpData *sync.Map
1415
abrpSendActive bool
1516
abrpUpdatesEndTime time.Time
1617
abrpToken string
1718
abrpApiKey string
1819
}
1920

2021
func NewCar(number string, carModel string, abrpToken string, abrpApiKey string) *Car {
21-
return &Car{
22-
number: number,
23-
tmData: map[string]interface{}{},
24-
abrpData: map[string]interface{}{
25-
"car_model": carModel,
26-
"utc": 0,
27-
"soc": 0,
28-
"power": 0,
29-
"speed": 0,
30-
"lat": "",
31-
"lon": "",
32-
"elevation": "",
33-
"heading": "",
34-
"is_charging": 0,
35-
"is_dcfc": 0,
36-
"is_parked": 0,
37-
"est_battery_range": "",
38-
"ideal_battery_range": "",
39-
"ext_temp": "",
40-
"tlm_type": "api",
41-
"voltage": 0,
42-
"current": 0,
43-
"kwh_charged": 0,
44-
"odometer": "",
45-
},
22+
car := &Car{
23+
number: number,
24+
tmData: new(sync.Map),
25+
abrpData: new(sync.Map),
4626
abrpSendActive: false,
4727
abrpToken: abrpToken,
4828
abrpApiKey: abrpApiKey,
4929
}
30+
31+
abrpDataDefaults := map[string]interface{}{
32+
"car_model": carModel,
33+
"utc": 0,
34+
"soc": 0,
35+
"power": 0,
36+
"speed": 0,
37+
"lat": "",
38+
"lon": "",
39+
"elevation": "",
40+
"heading": "",
41+
"is_charging": 0,
42+
"is_dcfc": 0,
43+
"is_parked": 0,
44+
"est_battery_range": "",
45+
"ideal_battery_range": "",
46+
"ext_temp": "",
47+
"tlm_type": "api",
48+
"voltage": 0,
49+
"current": 0,
50+
"kwh_charged": 0,
51+
"odometer": "",
52+
}
53+
54+
for key, value := range abrpDataDefaults {
55+
car.abrpData.Store(key, value)
56+
}
57+
58+
return car
5059
}
5160

5261
func updateCarTmData(car *Car, topic string, payload string) {
53-
car.tmData[topic] = payload
62+
car.tmData.Store(topic, payload)
5463
}
5564

5665
func updateCarAbrpData(car *Car, topic string, payload string) {
5766
switch topic {
5867
case "latitude":
59-
car.abrpData["lat"] = payload
68+
car.abrpData.Store("lat", payload)
6069
case "longitude":
61-
car.abrpData["lon"] = payload
70+
car.abrpData.Store("lon", payload)
6271
case "elevation":
63-
car.abrpData["elevation"] = payload
72+
car.abrpData.Store("elevation", payload)
6473
case "heading":
65-
car.abrpData["heading"] = payload
74+
car.abrpData.Store("heading", payload)
6675
case "speed":
67-
car.abrpData["speed"], _ = strconv.Atoi(payload)
76+
value, _ := strconv.Atoi(payload)
77+
car.abrpData.Store("speed", value)
6878
case "outside_temp":
69-
car.abrpData["ext_temp"] = payload
79+
car.abrpData.Store("ext_temp", payload)
7080
case "odometer":
71-
car.abrpData["odometer"] = payload
81+
car.abrpData.Store("odometer", payload)
7282
case "ideal_battery_range_km":
73-
car.abrpData["ideal_battery_range"] = payload
83+
car.abrpData.Store("ideal_battery_range", payload)
7484
case "est_battery_range_km":
75-
car.abrpData["est_battery_range"] = payload
85+
car.abrpData.Store("est_battery_range", payload)
7686
case "usable_battery_level":
77-
car.abrpData["soc"] = payload
87+
car.abrpData.Store("soc", payload)
7888
case "charge_energy_added":
79-
car.abrpData["kwh_charged"] = payload
89+
car.abrpData.Store("kwh_charged", payload)
8090
case "power":
8191
power, _ := strconv.Atoi(payload)
82-
car.abrpData["power"] = power
92+
car.abrpData.Store("power", power)
8393

84-
if (car.abrpData["is_charging"] == 1) && (power < -22) {
85-
car.abrpData["is_dcfc"] = 1
94+
isCharging, _ := car.abrpData.Load("is_charging")
95+
if (isCharging == 1) && (power < -22) {
96+
car.abrpData.Store("is_dcfc", 1)
8697
}
8798
case "charger_power":
8899
if payload != "" && payload != "0" {
89-
car.abrpData["is_charging"] = 1
100+
car.abrpData.Store("is_charging", 1)
90101

91102
chargerPower, _ := strconv.Atoi(payload)
92103
if chargerPower > 22 {
93-
car.abrpData["is_dcfc"] = 1
104+
car.abrpData.Store("is_dcfc", 1)
94105
}
95106
}
96107
case "charger_actual_current":
97108
if payload != "" {
98109
current, _ := strconv.Atoi(payload)
99110
if current > 0 {
100-
car.abrpData["current"] = payload
111+
car.abrpData.Store("current", payload)
101112
} else {
102-
delete(car.abrpData, "current")
113+
car.abrpData.Delete("current")
103114
}
104115
}
105116
case "charger_voltage":
106117
if payload != "" {
107118
voltage, _ := strconv.Atoi(payload)
108119
if voltage > 4 {
109-
car.abrpData["voltage"] = payload
120+
car.abrpData.Store("voltage", payload)
110121
} else {
111-
delete(car.abrpData, "voltage")
122+
car.abrpData.Delete("voltage")
112123
}
113124
}
114125
case "state":
115126
car.state = payload
116127
if car.state == "driving" {
117-
car.abrpData["is_parked"] = 0
118-
car.abrpData["is_charging"] = 0
119-
car.abrpData["is_dcfc"] = 0
128+
car.abrpData.Store("is_parked", 0)
129+
car.abrpData.Store("is_charging", 0)
130+
car.abrpData.Store("is_dcfc", 0)
120131
} else if car.state == "charging" {
121-
car.abrpData["is_parked"] = 1
122-
car.abrpData["is_charging"] = 1
123-
car.abrpData["is_dcfc"] = 0
132+
car.abrpData.Store("is_parked", 1)
133+
car.abrpData.Store("is_charging", 1)
134+
car.abrpData.Store("is_dcfc", 0)
124135
} else if car.state == "supercharging" {
125-
car.abrpData["is_parked"] = 1
126-
car.abrpData["is_charging"] = 1
127-
car.abrpData["is_dcfc"] = 1
136+
car.abrpData.Store("is_parked", 1)
137+
car.abrpData.Store("is_charging", 1)
138+
car.abrpData.Store("is_dcfc", 1)
128139
} else if car.state == "online" || car.state == "suspended" || car.state == "asleep" {
129-
car.abrpData["is_parked"] = 1
130-
car.abrpData["is_charging"] = 0
131-
car.abrpData["is_dcfc"] = 0
140+
car.abrpData.Store("is_parked", 1)
141+
car.abrpData.Store("is_charging", 0)
142+
car.abrpData.Store("is_dcfc", 0)
132143
}
133144
case "shift_state":
134145
if payload == "P" {
135-
car.abrpData["is_parked"] = 1
146+
car.abrpData.Store("is_parked", 1)
136147
} else if payload == "D" || payload == "R" {
137-
car.abrpData["is_parked"] = 0
148+
car.abrpData.Store("is_parked", 0)
138149
}
139150
}
140151
}

internal/app/http.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package app
22

33
import (
44
"encoding/json"
5+
"fmt"
56
"log"
67
"net/http"
78
"os"
@@ -15,8 +16,19 @@ var indexTemplate = template.Must(template.ParseFiles("web/templates/index.html"
1516

1617
func indexHandler(car *Car) http.HandlerFunc {
1718
return func(w http.ResponseWriter, r *http.Request) {
18-
carTmData, _ := json.MarshalIndent(car.tmData, "", " ")
19-
carAbrpData, _ := json.MarshalIndent(car.abrpData, "", " ")
19+
tmDataMap := map[string]interface{}{}
20+
car.tmData.Range(func(key, value interface{}) bool {
21+
tmDataMap[fmt.Sprint(key)] = value
22+
return true
23+
})
24+
carTmData, _ := json.MarshalIndent(tmDataMap, "", " ")
25+
26+
abrpDataMap := map[string]interface{}{}
27+
car.abrpData.Range(func(key, value interface{}) bool {
28+
abrpDataMap[fmt.Sprint(key)] = value
29+
return true
30+
})
31+
carAbrpData, _ := json.MarshalIndent(abrpDataMap, "", " ")
2032

2133
var carAbrpSendContinuousButtonText string
2234
if car.abrpSendActive {

main

-9.39 MB
Binary file not shown.

0 commit comments

Comments
 (0)