-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathLoadSharing.kt
133 lines (109 loc) · 4.25 KB
/
LoadSharing.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package com.dehnes.smarthome.ev_charging
import java.time.Clock
const val LOWEST_CHARGE_RATE = 6
interface LoadSharing {
fun calculateLoadSharing(
currentData: List<LoadSharable>,
powerConnection: PowerConnection,
chargingEndingAmpDelta: Int
): List<LoadSharable>
}
interface LoadSharable {
val clientId: String
val loadSharingPriority: LoadSharingPriority
val usesThreePhase: Boolean
fun isCharging(): Boolean
fun isChargingOrChargingRequested(): Boolean
fun getConfiguredRateAmps(): Int
fun wantsMore(timestamp: Long): Boolean
fun setNoCapacityAvailable(timestamp: Long): LoadSharable
fun adjustMaxChargeCurrent(amps: Int, timestamp: Long): LoadSharable
}
interface PowerConnection {
fun availableAmpsCapacity(): Int
fun availablePowerCapacity(): Int
}
enum class LoadSharingPriority {
HIGH,
NORMAL,
LOW
}
/*
* Shares the available capacity evenly among those with the same priority; starting at the highest and going downwards if
* more capacity is available
*/
class PriorityLoadSharing(
private val clock: Clock
) : LoadSharing {
override fun calculateLoadSharing(
currentData: List<LoadSharable>,
powerConnection: PowerConnection,
chargingEndingAmpDelta: Int
): List<LoadSharable> {
/*
* Setup
*/
val loadSharableById = currentData.map { it.clientId to it }.toMap().toMutableMap()
var availableAmpsCapacity = powerConnection.availableAmpsCapacity()
check(availableAmpsCapacity in 0..1000) { "AvailableCapacity outside of reasonable range? $availableAmpsCapacity" }
var availablePowerCapacity = powerConnection.availablePowerCapacity()
val takeCapacity = { requestedAmps: Int, threePhase: Boolean ->
val phases = if (threePhase) 3 else 1
val requestedPower = (requestedAmps * 230) * phases
val hasEnoughAmps = requestedAmps <= availableAmpsCapacity
val hasEnoughPower = requestedPower <= availablePowerCapacity
if (hasEnoughAmps && hasEnoughPower) {
availableAmpsCapacity -= requestedAmps
availablePowerCapacity -= requestedPower
requestedAmps
} else {
0
}
}
// Initially - all zero
loadSharableById.values
.filter { it.isChargingOrChargingRequested() }
.forEach { ls ->
loadSharableById[ls.clientId] = ls.adjustMaxChargeCurrent(0, clock.millis())
}
// Spread the capacity
LoadSharingPriority.values().forEach { loadSharingPriority ->
var somethingChanged: Boolean
while (true) {
somethingChanged = false
loadSharableById.values
.filter { it.loadSharingPriority == loadSharingPriority }
.filter { it.isChargingOrChargingRequested() }
.sortedBy { it.clientId }
.filter { it.wantsMore(clock.millis()) }
.forEach { sh ->
val toBeAdded = if (sh.getConfiguredRateAmps() == 0) {
LOWEST_CHARGE_RATE
} else {
1
}
val addedAmps = takeCapacity(toBeAdded, sh.usesThreePhase)
if (addedAmps > 0) {
loadSharableById[sh.clientId] = sh.adjustMaxChargeCurrent(
sh.getConfiguredRateAmps() + addedAmps,
clock.millis()
)
somethingChanged = true
}
}
if (!somethingChanged) {
break
}
}
}
// those which remain at zero -> update state
loadSharableById.values
.filter { it.isChargingOrChargingRequested() }
.forEach { ls ->
if (ls.getConfiguredRateAmps() < LOWEST_CHARGE_RATE) {
loadSharableById[ls.clientId] = ls.setNoCapacityAvailable(clock.millis())
}
}
return loadSharableById.values.toList()
}
}