|
|
|
@@ -9,6 +9,8 @@ ADFieldSilos.PLAN_INTERVAL_MS = 60000
|
|
|
|
|
ADFieldSilos.CHECK_INTERVAL_MS = 2000
|
|
|
|
|
ADFieldSilos.STUCK_MS = 120000
|
|
|
|
|
ADFieldSilos.PAUSE_MS = 600000
|
|
|
|
|
ADFieldSilos.FAIL_MS = 30000 -- AD slukket inden for 30 s uden fremdrift = turen kunne ikke køres
|
|
|
|
|
ADFieldSilos.CARGO_MIN_LITERS = 100 -- rest under dette tæller ikke som last
|
|
|
|
|
ADFieldSilos.MOVING_SPEED = 0.00028 -- m/ms, samme grænse som AutoDrive
|
|
|
|
|
ADFieldSilos.FILE_NAME = "ADSmartPickup_fieldsilos.xml"
|
|
|
|
|
ADFieldSilos.ROOT = "ADSmartPickupFieldSilos"
|
|
|
|
@@ -22,13 +24,18 @@ local planTimerMs, checkTimerMs, nowMs = ADFieldSilos.PLAN_INTERVAL_MS, 0, 0
|
|
|
|
|
|
|
|
|
|
-- ---- ren frigivelsesregel ----
|
|
|
|
|
|
|
|
|
|
-- vehicleState = {adActive, controlled, fill, moving}
|
|
|
|
|
-- vehicleState = {adActive, controlled, fill, moving}. Fremdrift = kører, eller fyldningen ændrer sig (læsser
|
|
|
|
|
-- eller aflæsser). Vagten gælder kun før turen er kommet i gang — kø ved siloen/lageret er normalt.
|
|
|
|
|
function ADFieldSilos.releaseReason(loan, vehicleState, now)
|
|
|
|
|
if vehicleState.controlled then return "player" end
|
|
|
|
|
if not vehicleState.adActive then return "done" end
|
|
|
|
|
if vehicleState.moving then return nil end
|
|
|
|
|
if (vehicleState.fill or 0) > (loan.lastFill or 0) then return nil end
|
|
|
|
|
if now - (loan.lastProgressMs or loan.startedMs or 0) > ADFieldSilos.STUCK_MS then return "stuck" end
|
|
|
|
|
local progressing = vehicleState.moving or (vehicleState.fill or 0) ~= (loan.lastFill or 0)
|
|
|
|
|
local hasProgressed = loan.hasProgressed or progressing
|
|
|
|
|
local age = now - (loan.startedMs or 0)
|
|
|
|
|
if not vehicleState.adActive then
|
|
|
|
|
return (not hasProgressed and age < ADFieldSilos.FAIL_MS) and "failed" or "done"
|
|
|
|
|
end
|
|
|
|
|
if hasProgressed then return nil end
|
|
|
|
|
if age > ADFieldSilos.STUCK_MS then return "stuck" end
|
|
|
|
|
return nil
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
@@ -91,6 +98,18 @@ local function firstMarker(building, role)
|
|
|
|
|
return markers[1] ~= nil and markers[1].id or nil
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Bygninger der er kilde, mål eller lager i et flow: dem tømmer marksilo-flowet ikke (de hører til den drift).
|
|
|
|
|
local function runBuildingIds()
|
|
|
|
|
local ids = {}
|
|
|
|
|
local store = ADRunsController ~= nil and ADRunsController.store or nil
|
|
|
|
|
for _, run in ipairs(store ~= nil and store.runs or {}) do
|
|
|
|
|
for _, list in ipairs({run.sourceBuildingIds or {}, run.targetBuildingIds or {}, run.storeBuildingIds or {}}) do
|
|
|
|
|
for _, id in ipairs(list) do ids[id] = true end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return ids
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Gårdlagre fra opsætningen + marksiloer (alle markers marksiloer, uden dubletter og uden gårdlagre).
|
|
|
|
|
function ADFieldSilos.collectBuildings(adEnv)
|
|
|
|
|
local fid = farmId()
|
|
|
|
@@ -103,7 +122,7 @@ function ADFieldSilos.collectBuildings(adEnv)
|
|
|
|
|
x = x, z = z, free = siloFree(building.placeable)})
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
local silos, seen = {}, {}
|
|
|
|
|
local silos, seen = {}, runBuildingIds()
|
|
|
|
|
for _, info in ipairs(ADFields.list()) do
|
|
|
|
|
for _, building in ipairs(ADFields.fieldSilos(adEnv, fid, info)) do
|
|
|
|
|
if not seen[building.id] and not ADFieldSiloConfig.hasStore(ADFieldSilos.config, building.id) then
|
|
|
|
@@ -126,7 +145,7 @@ local function trailerInfo(adEnv, vehicle)
|
|
|
|
|
for fillType in pairs(object.getFillUnitSupportedFillTypes ~= nil and object:getFillUnitSupportedFillTypes(index) or {}) do
|
|
|
|
|
carries[fillType] = true
|
|
|
|
|
end
|
|
|
|
|
if (object:getFillUnitFillLevel(index) or 0) > 0 then cargo = object:getFillUnitFillType(index) end
|
|
|
|
|
if (object:getFillUnitFillLevel(index) or 0) >= ADFieldSilos.CARGO_MIN_LITERS then cargo = object:getFillUnitFillType(index) end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return capacity, carries, cargo
|
|
|
|
@@ -140,7 +159,9 @@ end
|
|
|
|
|
function ADFieldSilos.collectVehicles(adEnv)
|
|
|
|
|
local vehicles, byId = {}, {}
|
|
|
|
|
for _, rig in ipairs(ADFieldWork.getRigs(adEnv)) do
|
|
|
|
|
if rig.role == "unloader" and rig.enabled and not rig.busy and not rig.controlled then
|
|
|
|
|
-- uden AD-parkeringspunkt bliver vognen holdende på lagerets markør og spærrer for de næste
|
|
|
|
|
local canPark = ADFieldWork.parkMarkerId == nil or ADFieldWork.parkMarkerId(adEnv, rig.vehicle) ~= nil
|
|
|
|
|
if rig.role == "unloader" and rig.enabled and not rig.busy and not rig.controlled and canPark then
|
|
|
|
|
local capacity, carries, cargo = trailerInfo(adEnv, rig.vehicle)
|
|
|
|
|
table.insert(vehicles, {id = rig.id, x = rig.x, z = rig.z, capacity = capacity, carries = carries, cargo = cargo})
|
|
|
|
|
byId[rig.id] = rig.vehicle
|
|
|
|
@@ -166,10 +187,14 @@ local function dispatchOne(adEnv, order, vehicle, silosById, storesById)
|
|
|
|
|
module.loopCounter = 1
|
|
|
|
|
if module.setLoopsDone ~= nil then module:setLoopsDone(0) end
|
|
|
|
|
ADFieldWork.enableParkAtJobFinished(vehicle)
|
|
|
|
|
-- rotation fra: ellers kan modens egne pickup-hooks sende vognen til en anden silo i mappen
|
|
|
|
|
if ADRunsController ~= nil and ADRunsController.setSetting ~= nil then
|
|
|
|
|
ADRunsController.setSetting(adEnv, vehicle, "rotateTargets", adEnv.AutoDrive.RT_NONE)
|
|
|
|
|
end
|
|
|
|
|
module:raiseDirtyFlag()
|
|
|
|
|
ADFieldWork.startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_PICKUPANDDELIVER, order.siloMarkerId, order.storeMarkerId, order.fillType)
|
|
|
|
|
local silo, store = silosById[order.siloId], storesById[order.storeId]
|
|
|
|
|
loans[vehicle] = {siloId = order.siloId, siloName = silo ~= nil and silo.name or order.siloId, fillType = order.fillType,
|
|
|
|
|
loans[vehicle] = {siloId = order.siloId, storeId = order.storeId, siloName = silo ~= nil and silo.name or order.siloId, fillType = order.fillType,
|
|
|
|
|
liters = order.liters, startedMs = nowMs, lastProgressMs = nowMs, lastFill = fillLevel(adEnv, vehicle)}
|
|
|
|
|
Logging.info(ADFieldSilos.LOG_PREFIX .. "'%s' henter %d l %s i %s -> %s", tostring(ADRuns.vehicleLabel(vehicle)),
|
|
|
|
|
math.floor(order.liters), fillTypeName(order.fillType), tostring(silo ~= nil and silo.name or order.siloId),
|
|
|
|
@@ -180,12 +205,13 @@ local function release(adEnv, vehicle, reason)
|
|
|
|
|
local loan = loans[vehicle]
|
|
|
|
|
loans[vehicle] = nil
|
|
|
|
|
if loan == nil then return end
|
|
|
|
|
if reason == "stuck" then
|
|
|
|
|
if reason == "stuck" or reason == "failed" then
|
|
|
|
|
local k = ADFieldSiloPlanner.key(loan.siloId, loan.fillType)
|
|
|
|
|
paused[k] = nowMs + ADFieldSilos.PAUSE_MS
|
|
|
|
|
pcall(vehicle.stopAutoDrive, vehicle)
|
|
|
|
|
Logging.warning(ADFieldSilos.LOG_PREFIX .. "'%s' kom ikke i gang ved %s (%s) på 2 min — stoppet; siloen springes over i 10 min",
|
|
|
|
|
tostring(ADRuns.vehicleLabel(vehicle)), tostring(loan.siloName), fillTypeName(loan.fillType))
|
|
|
|
|
local why = reason == "stuck" and "kom ikke i gang på 2 min — stoppet" or "AutoDrive stoppede turen med det samme (rute/læssested?)"
|
|
|
|
|
Logging.warning(ADFieldSilos.LOG_PREFIX .. "'%s' ved %s (%s): %s; siloen springes over i 10 min",
|
|
|
|
|
tostring(ADRuns.vehicleLabel(vehicle)), tostring(loan.siloName), fillTypeName(loan.fillType), why)
|
|
|
|
|
elseif reason == "stopped" then
|
|
|
|
|
pcall(vehicle.stopAutoDrive, vehicle)
|
|
|
|
|
end
|
|
|
|
@@ -202,18 +228,28 @@ local function checkLoans(adEnv)
|
|
|
|
|
local list = {}
|
|
|
|
|
for vehicle in pairs(loans) do table.insert(list, vehicle) end
|
|
|
|
|
for _, vehicle in ipairs(list) do
|
|
|
|
|
local loan = loans[vehicle]
|
|
|
|
|
local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
|
|
|
|
|
local fill = fillLevel(adEnv, vehicle)
|
|
|
|
|
local vehicleState = {
|
|
|
|
|
adActive = stateModule ~= nil and stateModule:isActive(),
|
|
|
|
|
controlled = vehicle.getIsControlled ~= nil and vehicle:getIsControlled() or false,
|
|
|
|
|
fill = fill, moving = (vehicle.lastSpeedReal or 0) >= ADFieldSilos.MOVING_SPEED,
|
|
|
|
|
}
|
|
|
|
|
if vehicleState.moving or fill > (loan.lastFill or 0) then loan.lastProgressMs = nowMs end
|
|
|
|
|
loan.lastFill = math.max(loan.lastFill or 0, fill)
|
|
|
|
|
local reason = ADFieldSilos.releaseReason(loan, vehicleState, nowMs)
|
|
|
|
|
if reason ~= nil then release(adEnv, vehicle, reason) end
|
|
|
|
|
if vehicle.isDeleted then
|
|
|
|
|
loans[vehicle] = nil -- solgt/slettet mens den var lånt
|
|
|
|
|
else
|
|
|
|
|
local ok, err = pcall(function()
|
|
|
|
|
local loan = loans[vehicle]
|
|
|
|
|
local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
|
|
|
|
|
local fill = fillLevel(adEnv, vehicle)
|
|
|
|
|
local vehicleState = {
|
|
|
|
|
adActive = stateModule ~= nil and stateModule:isActive(),
|
|
|
|
|
controlled = vehicle.getIsControlled ~= nil and vehicle:getIsControlled() or false,
|
|
|
|
|
fill = fill, moving = (vehicle.lastSpeedReal or 0) >= ADFieldSilos.MOVING_SPEED,
|
|
|
|
|
}
|
|
|
|
|
local reason = ADFieldSilos.releaseReason(loan, vehicleState, nowMs)
|
|
|
|
|
if vehicleState.moving or fill ~= (loan.lastFill or 0) then loan.hasProgressed = true end
|
|
|
|
|
loan.lastFill = fill
|
|
|
|
|
if reason ~= nil then release(adEnv, vehicle, reason) end
|
|
|
|
|
end)
|
|
|
|
|
if not ok then
|
|
|
|
|
loans[vehicle] = nil
|
|
|
|
|
Logging.warning(ADFieldSilos.LOG_PREFIX .. "lån opgivet (fejl): %s", tostring(err))
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
@@ -227,10 +263,24 @@ end
|
|
|
|
|
|
|
|
|
|
local function loanList()
|
|
|
|
|
local list = {}
|
|
|
|
|
for _, loan in pairs(loans) do table.insert(list, {siloId = loan.siloId, fillType = loan.fillType, liters = loan.liters}) end
|
|
|
|
|
for _, loan in pairs(loans) do table.insert(list, {siloId = loan.siloId, storeId = loan.storeId, fillType = loan.fillType, liters = loan.liters}) end
|
|
|
|
|
return list
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Rute fra vognen til siloen og videre til lageret (samme opslag som Wait-punkterne); én advarsel pr. kombination.
|
|
|
|
|
local warnedRoute = {}
|
|
|
|
|
local function hasRoutes(adEnv, vehicle, order)
|
|
|
|
|
if ADUnloadWait == nil or ADUnloadWait.hasRouteTo == nil then return true end
|
|
|
|
|
local ok = ADUnloadWait.hasRouteTo(adEnv, vehicle, order.siloMarkerId) and ADUnloadWait.hasRouteTo(adEnv, vehicle, order.storeMarkerId)
|
|
|
|
|
local warnKey = tostring(order.vehicleId) .. ">" .. tostring(order.siloMarkerId)
|
|
|
|
|
if not ok and not warnedRoute[warnKey] then
|
|
|
|
|
warnedRoute[warnKey] = true
|
|
|
|
|
Logging.warning(ADFieldSilos.LOG_PREFIX .. "ingen AutoDrive-rute for '%s' til marksilo-markør %s eller gårdlager-markør %s",
|
|
|
|
|
tostring(ADRuns.vehicleLabel(vehicle)), tostring(order.siloMarkerId), tostring(order.storeMarkerId))
|
|
|
|
|
end
|
|
|
|
|
return ok
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function ADFieldSilos.planNow(adEnv)
|
|
|
|
|
local cfg = ADFieldSilos.config
|
|
|
|
|
local silos, stores = ADFieldSilos.collectBuildings(adEnv)
|
|
|
|
@@ -244,6 +294,9 @@ function ADFieldSilos.planNow(adEnv)
|
|
|
|
|
for _, store in ipairs(stores) do storesById[store.id] = store end
|
|
|
|
|
for _, order in ipairs(plan.dispatch) do
|
|
|
|
|
local vehicle = vehiclesById[order.vehicleId]
|
|
|
|
|
if vehicle ~= nil and not hasRoutes(adEnv, vehicle, order) then
|
|
|
|
|
vehicle = nil
|
|
|
|
|
end
|
|
|
|
|
if vehicle ~= nil then
|
|
|
|
|
local ok, err = pcall(dispatchOne, adEnv, order, vehicle, silosById, storesById)
|
|
|
|
|
if not ok then
|
|
|
|
|