Skip to content
Snippets Groups Projects
Select Git revision
  • fbcf3a6254512a798879fc1b36259a48d05c94dd
  • main default protected
  • feature/export-filtering
  • feature/clear-schedule-button
  • fix/responsive-cols-in-polls
  • feature/preference-polling-form
  • feature/json-export-via-rest-framework
  • feature/json-schedule-import-tests
  • fix/add-room-import-only-once
  • ak-import
  • renovate/django-simple-history-3.x
  • renovate/django-debug-toolbar-4.x
  • renovate/django-5.x
  • renovate/mysqlclient-2.x
14 results

api.py

Blame
  • Forked from KIF / AKPlanning
    Source project has a limited visibility.
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    env_setup-fahrplan.lua 26.59 KiB
    -- Table of all lines and which stations/platforms they visit. According to S.stations (defined below)
    -- Specify ANY as entry point if the platform is always used.
    S.lines = {
      ["1"] = {
        circle=true,
        stations = {
          {name="Neuland HBF unten",entry="north", platform="U2", exit='south'},
          {name="Auditorium",entry="west", platform="2", exit='east'},
          {name="Freiluftkirche",entry="south", platform="1", exit='north'},
          {name="Spawn",entry="south", platform="1", exit='west'},
          {name="Schwimmbad",entry="east", platform="2", exit='west'},
        },
      },
      ["1R"] = {
    
        circle=true,
        stations=  {
          {name="Neuland HBF unten",entry="south", platform="U5", exit='north'},
          {name="Schwimmbad",entry="west", platform="2", exit='east'},
          {name="Spawn",entry="west", platform="1", exit='south'},
          {name="Freiluftkirche",entry="north", platform="2", exit='south'},
          {name="Auditorium",entry="east", platform="1", exit='west'},
          {name="Anmeldung",entry="south", platform="2", exit='north'},
        }
    
      },
      ["3"] = {
        circle = true,
        stations = {
          {name = "Neuland HBF unten",entry="west", platform="U3", exit="west"},
          {name = "Neues Lager",entry="west", platform="Sued", exit="sued"},
          {name = "Fewcraft Castle",entry="west", platform="3", exit="west"},
          {name = "Neues Lager",entry="east", platform="Nord", exit="nord"},
    
        },
      },
      ["5"] = {
        circle=false,
        stations=  {
          {name="Neuland HBF unten",entry="west", platform="U2", exit='south'},
          {name="Auditorium Sued",entry="west", platform="2", exit='east'},
          {name="Auditorium",entry="south", platform="1", exit='west'},
          {name="Anmeldung",entry="south", platform="2", exit='north'},
          {name="Neuland HBF unten",entry="south", platform="U6", exit='west'},
        },
        turns = {'Neuland HBF unten'}
      },
    
      ["7"] = {
        circle=true,
        stations=  {
          {name="Neuland HBF oben",entry="south", platform="3", exit='south'},
          {name="Anmeldung",entry="north", platform="1", exit='south'},
          {name="Auditorium Sued",entry="north-west", platform="1", exit='east'},
          {name="Umstieg Freefalltower",entry="west", platform="3", exit='east'},
          {name="Freefalltower",entry="south", platform="2", exit='south'},
          {name="Umstieg Freefalltower",entry="east", platform="2", exit='west'},
          {name="Auditorium Sued",entry="east", platform="1", exit='north-west'},
          {name="Anmeldung",entry="south", platform="2", exit='north'},
    
        },
      },
      ["12"] = {
        circle=true,
        stations = {
          {name = "Neuland HBF oben",entry="west", platform="2", exit="west"},
          {name = "Farm",entry="north", platform="1", exit="north"},
          {name = "Farm Nord",entry="west", platform="1", exit="east"},
        },
      },
      ["13"] = {
        circle = false,
        stations = {
          {name = "Neuland HBF oben",entry="north", platform="1", exit="north"},
          {name = "Schwimmbad",entry="west", platform="1", exit="east"},
          {name = "Spawn",entry="west", platform="2", exit="shunt", msg_outside="NICHT EINSTEIGEN"},
          {name = "Spawn",entry="shunt", platform="P2", exit="shunt"},
          {name = "Spawn",entry="shunt", platform="1", exit="west"},
          {name = "Schwimmbad",entry="east", platform="2", exit="west"},
        },
        turns = {'Neuland HBF oben','Spawn'}
    
      },
      ["D101"] = {
        circle = false,
        stations = {
          {name = "Dortruhe",entry="north-west", platform="2", exit="south"},
          {name = "Palm Beach",entry="north", platform="1", exit="north"},
          {name = "Dortruhe",entry="south", platform="3", exit="north-west"},
          {name = "Fnordlingen HBF",entry="??", platform="3", exit="??"},
          {name = "Schneckenstein",entry="??", platform="1", exit="??"},
          {name = "Schneckenstein",entry="shunt", platform="3", exit="shunt"},
          {name = "Schneckenstein",entry="??", platform="3", exit="??"},
          {name = "Fnordlingen HBF",entry="??", platform="3", exit="??"},
    
        },
        turns = {'Palm Beach','Schneckenstein'}
      },
      ["D102"] = {
        circle = true,
        stations = {
          {name = "Dortruhe",entry="north-east", platform="3", exit="north-west"},
          {name = "Fnordlingen HBF",entry="??", platform="3", exit="??"},
          {name = "Schneckenstein",entry="??", platform="1", exit="??"},
          {name = "Remouladingen",entry="??", platform="3", exit="??"},
          {name = "Dortsen",entry="north-west", platform="2", exit="south-west"},
    
        },
      },
    }
    
    -- Table of stations and which platforms and entries they have.
    -- Entries: these can then be used by lines to determine which platform they stop at.
    S.stations = {
    
      ["Spawn"] = {entries = {"west", "south"}, platforms = {"1","2","P1","P2"}},
      ["Schwimmbad"] = {
        entries = {"west", "east"},
        platforms = {"1", "2"}
      },
    
      ["Neuland HBF oben"] = {
        entries = {"west", "north", "south"},
        platforms = {"1", "2", "3", "P1"}},
      ["Neuland HBF unten"] = {
        entries = {"west", "north-west", "north", "south"},
        platforms = {"U1", "U2", "U3", "U4", "U5", "U6", "U7", "U8"}},
      ["Anmeldung"] = {
        entries = {"north", "south"},
        platforms = {"1", "2"}},
      ["Auditorium"] = {entries = {"west", "east", "south"}, platforms = {"1","2","P1","P2"}},
      ["Auditorium Sued"] = {entries = { "west","north-east","north-west","east"}, platforms = {"1","2"}},
      ["Freiluftkirche"] = {entries = {"north", "south"}, platforms = {"1","2"}},
      ["Dortenau"] = {entries = {"west", "east"}, platforms = {"1","2"}},
      ["Dortstadt"] = {entries = {"west", "east"}, platforms = {"1","2"}},
      ["Dortruhe"] = {entries = {"west", "north-west", "north-east", "south"}, platforms = {"1","2","3"}},
      ["Dortsen"] = {entries = {"north-west", "south-west", "north-east"}, platforms = {}},
      ["Farm"] = {entries = {"south", "west", "north"}, platforms = {"1","2","3","4","5","6"}},
      ["Farm Nord"] = {entries = {"west","east"}, platforms = {"1","2"}},
      ["Umstieg Freefalltower"] = {entries = {"east", "west", "north"}, platforms = {"1","2","3"}},
      ["Freefalltower"] = {entries = { "south"}, platforms = {"1"}},
      ["Palm Beach"] = {entries={"north"},platforms={"1"}},
      ["Fnordlingen HBF"] = {entries={},platforms={"1","2","3"}},
      ["Schneckenstein"] = {entries={},platforms={"1","2","3"}},
      ["Remouladingen"] = {entries={},platforms={"1","2"}},
      ["Neues Lager"] = {entries={"west","east"},{"nord","sued"}},
      ["Fewcraft Castle"] = {entries={"west"},platforms = {"3","33"}},
      ["Birkenwald"] = {entries={"east","west"},platforms = {"1","2"}},
      ["Ost-See"] = {entries={"west","south"},platforms={"1","2"}},
      ["West-See"] = {entries={"east","north"},platforms={"1","2"}},
    }
    
    -- Table of ATC blocks per station to call with interrupt_pos() when updating arrivals table
    S.callbacks = {
      ["Spawn"] = {POS(-146,8,-81)},
      ["Schwimmbad"] = {POS(-288,15,-130)},
      ["Neuland HBF oben"] = {POS(-439,4,-261), POS(-489, 6, -242)},
      ["Neuland HBF unten"] = {POS(-439,4,-261), POS(-489, 6, -242)},
      ["Anmeldung"] = {POS(-456,6,-317)},
      ["Auditorium"] = {POS(-291,6,-438)},
      ["Dortenau"] = {POS(864,35,-597)},
      ["Dortruhe"] ={POS(1248,35,-596)},
      ["Auditorium Sued"]={POS(-306,23,-511)},
    }
    
    -- deactivate everything?
    S.deactivated = false
    
    -- debugging stuff
    S.debug_train_id = "259435"
    S.check_line_data = {}
    
    --[[--------------------------------------------------------------------
    ----  Please don't touch the stuff below. Thanks!  ----
    ]]----------------------------------------------------------------------
    
    -- contains stationnames as key and lines on that station as value
    S.lines_at_stations = {}
    
    --[[
    Zugriff: S.lines_at_stations[$station_name][$line_name][$entry] = {platform="",next_stops={next1,next2,next3}}
    ]]--
    
    F._init_ = function()
      F._build_lines_at_stations_table()
      -- Convert platforms in S.stations to key-value pairs for easier checking
      local _by_key = {}
      for station, data in pairs(S.stations) do
        local keyed = {}
        for k, v in pairs(data) do
          if k == "platforms" then
            keyed[k] = {}
            if v == nil then
              print(F.util.pretty(station).." | "..F.util.pretty(data))
            end
            for i, platform in ipairs(v) do
              keyed[k][platform] = platform
            end
          else
            keyed[k] = v
          end
          _by_key[station] = keyed
        end
      end
      S.stations._by_key = _by_key
    end
    
    -- Table for arriving trains
    if type(S.arrivals) ~= "table" then
      S.arrivals = {}
    end
    
    --[[ example arrivals table NEW:
    {
      "$station_name" = {
        "$atc_id" = {
          status="[arriving|boarding|departing|parking|shunting]",
          platform="",
          next_stops={},
          line_number=""
    
      }
    }
    ]]--
    
    -- function definitions
    
    -- converts S.lines to easier searchable structure
    F._build_lines_at_stations_table = function()
      for line_number,data in pairs(S.lines) do
        for station_index,line_data in pairs(data.stations) do
          if S.lines_at_stations[line_data.name] == nil then
            S.lines_at_stations[line_data.name] = {}
          end
          if S.lines_at_stations[line_data.name][line_number] == nil then
            S.lines_at_stations[line_data.name][line_number] = {}
          end
          if S.lines_at_stations[line_data.name][line_number][line_data.entry] == nil then
            S.lines_at_stations[line_data.name][line_number][line_data.entry] = {}
          end
          S.lines_at_stations[line_data.name][line_number][line_data.entry] = {platform=line_data.platform,next_stops=F._get_next_stops(station_index,data.stations,data.turns)}
        end
      end
    end
    
    -- helper function for F._build_lines_at_stations_table
    F._get_next_stops = function(station_index,line_data,turns)
      next_stops={}
      for my_station_index = station_index +1, #line_data do
        table.insert(next_stops,line_data[my_station_index].name)
        if F.util.table_contains(turns,line_data[my_station_index].name) then
          return next_stops
        end
      end
      for my_station_index = 1, station_index -1 do
        table.insert(next_stops,line_data[my_station_index].name)
        if F.util.table_contains(turns,line_data[my_station_index].name) then
          return next_stops
        end
      end
      return next_stops
    end
    
    -- for a station, find matching info from S.lines
    F.entry_match = function(line, station, entry)
      if S.deactivated then
        return
      end
    
      if S.lines_at_stations[station] == nil or S.lines_at_stations[station][line] == nil or S.lines_at_stations[station][line][entry] == nil then
        return nil
      end
      return F.util.clone(S.lines_at_stations[station][line][entry])
    end
    
    -- for a station, find which platform a line stops at
    F.platform_for_line = function(line, station, entry)
      if S.deactivated then
        return
      end
    
      local info = F.entry_match(line, station, entry)
      if info ~= nil then
        return info.platform
      end
    end
    
    -- broadcast table changes via interrupt_pos() and digiline
    F.announce_table_changes = function(station, platform, line)
      if S.deactivated then
        return
      end
      --print("[ATC announce_table_changes("..F.util.pretty(station)..", "..F.util.pretty(platform)..", "..F.util.pretty(line)..")]")
      local data = {
        station = station,
        platform = platform,
        line = line,
      }
      digiline_send("station_updates", data)
      if S.callbacks[station] ~= nil then
        if #S.callbacks[station] > 0 then
          for i, pos in ipairs(S.callbacks[station]) do
            interrupt_pos(pos, data)
          end
        end
      end
    end
    
    -- ATC rail acting as entry, points towards (!) the station
    F.station_entry_trigger = function(station, entry, event, atc_arrow, get_line)
      if S.deactivated then
        return
      end
      __approach_callback_mode = 2
      if event.train then
        local line = get_line()
        if line ~= nil then
          local changed
          if atc_arrow then
            F.enter_station(station, entry, line, true)
          else
            F.exit_station(station, entry, line, true)
          end
        end
      elseif event.approach then
        local line = get_line()
        if line ~= nil then
          if atc_arrow then
            F.approach_station(station, entry, line, true)
          end
        end
      end
    end
    
    -- ATC rail on the platform observing outgoing
    F.platform_exit_trigger = function(station, platform, event, atc_arrow, get_line)
      if S.deactivated then
        return
      end
      __approach_callback_mode = 2
      if event.train then
        local line = get_line()
        if line ~= nil then
          local changed
          if atc_arrow then
            F.exit_platform(station, platform, line, true)
          else
            F.enter_platform(station, platform, line, true)
          end
        end
      elseif event.approach then
        if not atc_arrow then
          local line = get_line()
          if line ~= nil then
            F.approach_station(station, entry, line, true)
          end
        else
          F.exit_platform(station, platform, line, true)
        end
      end
    end
    
    -- When observing a train heading to the station
    F.enter_station = function(station, entry, line, announce_changes)
      if S.deactivated then
        return
      end
    
      if event.id == S.debug_train_id then
    
        --TODO
        S.debug_data = {station = station,entry=entry}
      end
    
      local info = F.entry_match(line, station, entry)
    
    
      --print("[ATC enter_station("..F.util.pretty(station)..", "..F.util.pretty(entry)..", "..F.util.pretty(line)..", "..F.util.pretty(announce_changes)..")]")
    
      if S.stations[station] == nil then
        error("[ATC enter_station("..station..")] station unknown")
      else
        if S.arrivals[station] == nil then
          S.arrivals[station] = {}
        end
        if S.arrivals[station][atc_id] == nil then
          S.arrivals[station][atc_id] = {}
        end
        local arrival = S.arrivals[station][atc_id]
        arrival.line = line
        arrival.status = "arriving"
        if info ~= nil then
          arrival.info = info
          arrival.platform = info.platform
        end
        if S.lines_at_stations[station]~= nil and S.lines_at_stations[station][line] ~= nil and S.lines_at_stations[station][line][entry] ~= nil then
          arrival.next_stops = S.lines_at_stations[station][line][entry].next_stops
        end
        if announce_changes and platform ~= nil then
          F.announce_table_changes(station, platform, line)
        end
      end
    end
    
    -- When a train is soon heading to the station
    F.approach_station = function(station, entry, line, announce_changes)
      if S.deactivated then
        return
      end
    
      if event.id ~= S.debug_train_id then
        return F.enter_station(station, entry, line, announce_changes)
      end
    end
    
    -- When a train leaves the station
    F.exit_station = function(station, entry, line, announce_changes)
      if S.deactivated then
        return
      end
    
      if event.id == S.debug_train_id then
        --TODO
        S.debug_data["exit"] = entry
        print("{name=\"" .. S.debug_data["station"] .. "\",entry=\""..S.debug_data["entry"].. "\",platform=\""..S.debug_data["platform"].. "\",exit=\""..S.debug_data["exit"].."\"}")
      end
    
      --print("[ATC exit_station("..F.util.pretty(station)..", "..F.util.pretty(entry)..", "..F.util.pretty(line)..", "..F.util.pretty(announce_changes)..")]")
    
      if S.stations[station] == nil then
        error("[ATC leave_station("..station..")] station unknown")
      else
        if S.arrivals[station] == nil then
          return
        end
        S.arrivals[station][atc_id] = nil
    
        if next(S.arrivals[station]) == nil then
          S.arrivals[station] = nil
        end
      end
      if announce_changes then
        F.announce_table_changes(station, platform, line)
      end
    end
    
    -- When a train enters the platform
    F.enter_platform = function(station, platform, line, announce_changes)
      if S.deactivated then
        return
      end
    
      if event.id == S.debug_train_id then
        --TODO
        S.debug_data["platform"] = platform
        --print(F.util.pretty(S.debug_data))
      end
    
      --print("[ATC enter_platform("..F.util.pretty(station)..", "..F.util.pretty(platform)..", "..F.util.pretty(line)..", "..F.util.pretty(announce_changes)..")]")
      if S.stations[station] == nil then
        error("[ATC enter_station("..station..")] station unknown")
      else
        if S.arrivals[station] == nil then
          S.arrivals[station] = {}
        end
        if S.arrivals[station][atc_id] == nil then
          S.arrivals[station][atc_id] = {}
        end
        local arrival = S.arrivals[station][atc_id]
        arrival.line = line
        arrival.status = "boarding"
        arrival.platform = platform
    
        if arrival.info then
          if arrival.info.msg_outside then
            arrival.info.reset_msg_outside = atc_get_text_outside()
            atc_set_text_outside(arrival.info.msg_outside)
          elseif arrival.info.msg_inside then
            arrival.info.reset_msg_inside = atc_get_text_inside()
            atc_set_text_inside(arrival.info.msg_inside)
          end
        end
    
        if announce_changes then
          F.announce_table_changes(station, platform, line)
        end
      end
    end
    
    F.approach_platform = function(station, platform, line, announce_changes)
      if S.deactivated then
        return
      end
    
      if event.id ~= S.debug_train_id then
        return enter_platform(station, platform, line, announce_changes)
      end
    end
    
    -- When a train leaves the platform
    F.exit_platform = function(station, platform, line, announce_changes)
      if S.deactivated then
        return
      end
      --print("[ATC leave_station("..F.util.pretty(station)..", "..F.util.pretty(platform)..", "..F.util.pretty(line)..", "..F.util.pretty(announce_changes)..")]")
      if S.stations[station] == nil then
        error("[ATC leave_station("..station..")] station unknown")
      else
        if S.arrivals[station] == nil then
          S.arrivals[station] = {}
        end
        if S.arrivals[station][atc_id] == nil then
          S.arrivals[station][atc_id] = {}
        end
        local arrival = S.arrivals[station][atc_id]
        arrival.line = line
        arrival.status = "departing"
        arrival.platform = platform
    
        if arrival.info then
          if arrival.info.reset_msg_outside then
            atc_set_text_outside(arrival.info.reset_msg_outside)
          elseif arrival.info.reset_msg_inside then
            atc_set_text_inside(arrival.info.reset_msg_inside)
          end
        end
    
        if announce_changes then
          F.announce_table_changes(station, platform, line)
        end
      end
    end
    
    -- Compile a list of currently arriving trains for a station
    -- Can be restricted to a number of platforms (as table). Uses all platforms in S.stations if nil.
    F.compile_arrivals = function(station, platforms)
      if S.deactivated then
        return
      end
      -- check if station exists
      if S.stations[station] == nil then
        error("[ATC compile_arrivals("..station..", "..tostring(platforms)..")] station unknown")
      else
        local arrivals = {by_platform={}, by_line={}}
        -- check for station in S.arrivals
        if S.arrivals[station] ~= nil then
          if platforms == nil then
            -- use all platforms
            platforms = S.stations[station].platforms
            --print("[ATC compile_arrivals()] using all declared platforms for "..F.util.pretty(station)..": "..F.util.pretty(platforms))
          else
            --print("[ATC compile_arrivals()] "..F.util.pretty(station)..": limiting platforms to "..F.util.pretty(platforms))
          end
          for i, platform in ipairs(platforms) do
            -- check if platform exists
            if S.stations._by_key[station].platforms[platform] == nil then
              print("[ATC compile_arrivals("..station..", "..tostring(platforms)..")] platform unknown: "..platform.." ")
              print(F.util.pretty(S.stations._by_key[station].platforms))
            else
              for atc_ids,data in pairs(S.arrivals[station]) do
                if data.platform == platform then
                  if data.status == "arriving" or data.status == "boarding" then
                    if arrivals.by_platform[platform] == nil then
                      arrivals.by_platform[platform] = {}
                    end
                    table.insert(arrivals.by_platform[platform], data.line)
                    arrivals.by_line[data.line] = platform
                  end
                end
              end
            end
          end
        end
        return arrivals
      end
    end
    
    -- typeset arrivals table to lines
    F.typeset_arrivals = function(arrivals, screen_width)
      if S.deactivated then
        return
      end
      local screen_width = screen_width or 12
      local screen_height = 6
      local pad_char = "."
      local screen_lines =  {}
      for platform, lines in pairs(arrivals.by_platform) do
        local start = platform .. ": "
        local new_lines = {}
        --for i, line in ipairs(lines) do
        --  new_lines[i] = "[" .. line .. "]"
        --end
        --new_lines = table.concat(new_lines, ", ")
        new_lines = "[" .. table.concat(lines, "][") .. "]"
        if #start + #new_lines <= screen_width then
          table.insert(screen_lines, start .. F.util.pad_left(new_lines, screen_width - #start - #new_lines, pad_char))
        else
          table.insert(screen_lines, F.util.pad_right(start, screen_width - #start, pad_char))
          table.insert(screen_lines, F.util.pad_left(new_lines, screen_width - #new_lines, pad_char))
        end
      end
      if #screen_lines < 1 then
        table.insert(screen_lines, "")
        table.insert(screen_lines, "")
        table.insert(screen_lines, "no arrivals")
      end
      return screen_lines
    end
    
    -- display a nice table over multiple screens
    F.display_arrivals = function(arrivals, lcds,screen_width,screen_height)
      if S.deactivated then
        return
      end
    
      screen_width = screen_width or 12
      screen_height = screen_height or 6
      local line_break = " | "
      local screen_lines = F.typeset_arrivals(arrivals, screen_width)
      for i, lcd in ipairs(lcds) do
        if type(lcd) == "table" then
    
          local multi_screen_width = #lcd[1] * (screen_width-1)
    
          local multi_screen_lines = F.typeset_arrivals(arrivals, multi_screen_width)
    
          local display_line = 1
    
          local lcd_strings = {}
    
          for i,line in ipairs(multi_screen_lines) do
    
            display_line = math.floor((i-1)/screen_height+1)
            --print("displayline " .. display_line)
            for j,lcd_name in ipairs(lcd[display_line]) do
    
              lcd_strings[lcd_name] = (lcd_strings[lcd_name] or "") .. string.sub(line,(j-1)*screen_width+1,j*screen_width-1) .. line_break
            end
    
          end
    
          for i,lcda in ipairs(lcd) do
    
            for j,lcdb in ipairs(lcda) do
              --print(lcdb .. '  ' .. (lcd_strings[lcdb] or "nil"))
              digiline_send(lcdb,lcd_strings[lcdb] or line_break)
    
            end
    
          end
        else
          digiline_send(lcd, table.concat(screen_lines, line_break))
        end
      end
    end
    
    F.platform_info = function(arrivals, lcds,screen_width,screen_height,platforms)
    
    end
    
    F.compile_fahrplan = function(station, line_break, line_width)
      --S.lines_at_stations[$station_name][$line_name][$entry] = {platform="",next_stops={next1,next2,next3}}
      if S.lines_at_stations == nil or S.lines_at_stations[station] == nil then
        return ""
      end
      local fahrplan_lines = {}
      for line,line_data in pairs(S.lines_at_stations[station]) do
        for entry,entry_data in pairs(line_data) do
          local text_line = F.util.autopad({'Linie ' .. line .. ' ', ' Gl. ' .. entry_data.platform}, line_width, ".")
          table.insert(fahrplan_lines,text_line)
          text_line = {}
          for i,next_stop in ipairs(entry_data.next_stops) do
            table.insert(text_line, next_stop)
          end
          text_line = table.concat(text_line, ', ')
          if #text_line < line_width then
            text_line = text_line .. ' '
            text_line = F.util.pad_right(text_line, line_width-#text_line, '.')
          end
          table.insert(fahrplan_lines, text_line)
        end
    
      end
      return fahrplan_lines
    end
    
    
    --[[----------------------------
    ----  utility functions  ----
    ]]------------------------------
    
    F.util = {}
    
    F.util.clone = function(tbl, n)
      local out = {}
      local i,v = next(tbl, nil)
      while i do
        if type(v) == "table" then
          out[i] = F.util.clone(v, (n or 0)+1)
        else
          out[i] = v
        end
        i,v = next(tbl, i)
      end
      return out
    end
    
    F.util.pretty = function(tbl, indent, lvl)
      local s = ""
      indent = indent or 2
      lvl = lvl or 0
    
      local append = function(line)
        if #s > 0 then
          s = s .. "\n"
        end
        s = s .. line
      end
      local pad = function(indent, lvl)
        return F.util.pad_left("", indent * lvl)
      end
    
      if type(tbl) == "table" then
        append(pad(indent, lvl) .. "{")
        for k, v in pairs(tbl) do
          local start = pad(indent, lvl+1) .. "["..F.util.pretty(k, indent, 0).."] = "
          local val = F.util.pretty(v, indent, lvl+2)
          if string.find(val, "\n") == nil then
            val = F.util.pretty(v, indent, 0)
          end
          append(start .. val)
        end
        append(pad(indent, lvl) .. "}")
      elseif type(tbl) == "string" then
        append(pad(indent, lvl) .. '"' .. tbl .. '"')
      else
        append(pad(indent, lvl) .. tostring(tbl))
      end
      return s
    end
    
    F.util.pad_left = function(s, times, char)
      char = char or " "
      return string.rep(char, times) .. tostring(s)
    end
    F.util.pad_right = function(s, times, char)
      char = char or " "
      return tostring(s) .. string.rep(char, times)
    end
    
    F.util.autopad = function(strings,padded_length,char)
    
      local padding_positions = #strings -1
      local stringlength = #(table.concat(strings))
      local padding_times = ( padded_length - stringlength ) / padding_positions
    
      local padded_string = strings[1]
      for i=2,#strings do
        padded_string = padded_string .. F.util.pad_left(strings[i],padding_times,char)
      end
      return padded_string
    end
    
    -- pad to the left and right to reach target width
    F.util.fill = function(s, width, char, prefer_right)
      width = width or #s
      char = char or " "
      local l = width - #s
      local r = l / 2
      if prefer_right then
        l = math.ceil(l)
        r = math.floor(r)
      else
        l = math.floor(l)
        r = math.ceil(r)
      end
      return string.rep(char, l) .. s .. string.rep(char, r)
    end
    
    -- check if table contains a value
    function F.util.table_contains(my_table, element)
      if my_table == nil then
        return false
      end
    
      for _, value in pairs(my_table) do
        if value == element then
          return true
        end
      end
      return false
    end
    
    --prints text on digiline lcds in multiline,multicolumn displayarray
    -- text can be table of strings with maxlength screen_width*number of screens in a row or string with $line_break
    F.util.multidisplay_print = function(displayarray,text, line_break, screen_width, screen_height)
    
      local screen_width = screen_width or 11
      local screen_height = screen_height or 2
      local line_break = line_break or " | "
    
      local multi_screen_width = #displayarray[1] * (screen_width)
    
      local multi_screen_lines = {}
    
      if type(text) == "table" then
        multi_screen_lines = text
        -- create lines from string
      elseif type(text) ~= "string" then
        text=F.util.pretty(text)
      end
    
      if type(text) == "string" then
        for i=1,#text,multi_screen_width do
          print(i)
          table.insert(multi_screen_lines,string.sub(text,i,i+multi_screen_width-1))
        end
        print(F.util.pretty(multi_screen_lines))
      end
    
      local display_line = 1
    
      local lcd_strings = {}
    
      for i,line in ipairs(multi_screen_lines) do
    
        display_line = math.floor((i-1)/screen_height+1)
        --print("displayline " .. display_line)
        if i > #displayarray*screen_height then
          break
        end
        for j,lcd_name in ipairs(displayarray[display_line]) do
    
          lcd_strings[lcd_name] = (lcd_strings[lcd_name] or "") .. string.sub(line,(j-1)*screen_width+1,j*screen_width) .. line_break
        end
    
      end
    
      for i,lcda in ipairs(displayarray) do
    
        for j,lcdb in ipairs(lcda) do
          --print(lcdb .. '  ' .. (lcd_strings[lcdb] or "nil"))
          digiline_send(lcdb,lcd_strings[lcdb] or line_break)
    
        end
    
      end
    end
    
    F._init_()