View difference between Paste ID: EUtw8BjP and JQ1mAC3M
SHOW: | | - or go back to the newest paste.
1
w = {}
2
3
-- APIs
4
5
-- properties
6
local data = { }
7
local data_name = nil
8
local data_handlers = { }
9
10
local device_handlers = {}
11
12
local event_handlers = {}
13
local event_timers = {}
14
15
local monitors = {}
16
local monitor_textScale = 0.5
17
18
local page_handlers = {}
19
local page_endText = ""
20
local page_callbackDisplay
21
local page_callbackKey
22
23
local run_refreshPeriod_s = 3.0
24
local status_period_s = 1.0
25
26
local styles = {
27
  normal   = { front = colors.black    , back = colors.lightGray },
28
  good     = { front = colors.lime     , back = colors.lightGray },
29
  bad      = { front = colors.red      , back = colors.lightGray },
30
  disabled = { front = colors.gray     , back = colors.lightGray },
31
  help     = { front = colors.white    , back = colors.blue      },
32
  header   = { front = colors.orange   , back = colors.black     },
33
  control  = { front = colors.white    , back = colors.blue      },
34
  selected = { front = colors.black    , back = colors.lightBlue },
35
  warning  = { front = colors.white    , back = colors.red       },
36
  success  = { front = colors.white    , back = colors.lime      },
37
}
38
39
----------- Terminal & monitor support
40
41
local function setMonitorColorFrontBack(colorFront, colorBackground)
42
  term.setTextColor(colorFront)
43
  term.setBackgroundColor(colorBackground)
44
  if monitors ~= nil then
45
    for key, monitor in pairs(monitors) do
46
      monitor.setTextColor(colorFront)
47
      monitor.setBackgroundColor(colorBackground)
48
    end
49
  end
50
end
51
52
local function write(text)
53
  term.write(text)
54
  if monitors ~= nil then
55
    for key, monitor in pairs(monitors) do
56
      if key ~= data.radar_monitorIndex then
57
        monitor.write(text)
58
      end
59
    end
60
  end
61
end
62
63
local function getCursorPos()
64
  local x, y = term.getCursorPos()
65
  return x, y
66
end
67
68
local function setCursorPos(x, y)
69
  term.setCursorPos(x, y)
70
  if monitors ~= nil then
71
    for key, monitor in pairs(monitors) do
72
      if key ~= data.radar_monitorIndex then
73
        monitor.setCursorPos(x, y)
74
      end
75
    end
76
  end
77
end
78
79
local function getResolution()
80
  local sizeX, sizeY = term.getSize()
81
  return sizeX, sizeY
82
end
83
84
local function setColorNormal()
85
  w.setMonitorColorFrontBack(styles.normal.front, styles.normal.back)
86
end
87
88
local function setColorGood()
89
  w.setMonitorColorFrontBack(styles.good.front, styles.good.back)
90
end
91
92
local function setColorBad()
93
  w.setMonitorColorFrontBack(styles.bad.front, styles.bad.back)
94
end
95
96
local function setColorDisabled()
97
  w.setMonitorColorFrontBack(styles.disabled.front, styles.disabled.back)
98
end
99
100
local function setColorHelp()
101
  w.setMonitorColorFrontBack(styles.help.front, styles.help.back)
102
end
103
104
local function setColorHeader()
105
  w.setMonitorColorFrontBack(styles.header.front, styles.header.back)
106
end
107
108
local function setColorControl()
109
  w.setMonitorColorFrontBack(styles.control.front, styles.control.back)
110
end
111
112
local function setColorSelected()
113
  w.setMonitorColorFrontBack(styles.selected.front, styles.selected.back)
114
end
115
116
local function setColorWarning()
117
  w.setMonitorColorFrontBack(styles.warning.front, styles.warning.back)
118
end
119
120
local function setColorSuccess()
121
  w.setMonitorColorFrontBack(styles.success.front, styles.success.back)
122
end
123
124
local function clear(colorFront, colorBack)
125
  if colorFront == nil or colorBack == nil then
126
    w.setColorNormal()
127
  else
128
    w.setMonitorColorFrontBack(colorFront, colorBack)
129
  end
130
  term.clear()
131
  if monitors ~= nil then
132
    for key, monitor in pairs(monitors) do
133
      if key ~= data.radar_monitorIndex then
134
        monitor.clear()
135
      end
136
    end
137
  end
138
  w.setCursorPos(1, 1)
139
end
140
141
local function clearLine()
142
  term.clearLine()
143
  if monitors ~= nil then
144
    for key, monitor in pairs(monitors) do
145
      if key ~= data.radar_monitorIndex then
146
        monitor.clearLine()
147
      end
148
    end
149
  end
150
  local x, y = w.getCursorPos()
151
  w.setCursorPos(1, y)
152
end
153
154
local function writeLn(text)
155
  w.write(text)
156
  local x, y = w.getCursorPos()
157
  local xSize, ySize = w.getResolution()
158
  if y > ySize - 1 then
159
    y = 1
160
  end
161
  w.setCursorPos(1, y + 1)
162
end
163
164
local function writeMultiLine(text)
165
  local textToParse = text or ""
166
  for line in string.gmatch(textToParse, "[^\n]+") do
167
    if line ~= "" then
168
      w.writeLn(line)
169
    end
170
  end
171
end
172
173
local function writeCentered(y, text)
174
  local unused
175
  if text == nil then
176
    text = y
177
    unused, y = w.getCursorPos()
178
  end
179
  
180
  w.setCursorPos((51 - text:len()) / 2, y)
181
  term.write(text)
182
  if monitors ~= nil then
183
    for key, monitor in pairs(monitors) do
184
      if key ~= data.radar_monitorIndex then
185
        local xSize, ySize = monitor.getSize()
186
        if xSize ~= nil then
187
          monitor.setCursorPos((xSize - text:len()) / 2, y)
188
          monitor.write(text)
189
        end
190
      end
191
    end
192
  end
193
  w.setCursorPos(1, y + 1)
194
end
195
196
local function writeFullLine(text)
197
  w.write(text)
198
  local xSize, ySize = w.getResolution()
199
  local xCursor, yCursor = w.getCursorPos()
200
  for i = xCursor, xSize do
201
    w.write(" ")
202
  end
203
  w.setCursorPos(1, yCursor + 1)
204
end
205
206
----------- Page support
207
208
local function page_begin(text)
209
  w.clear()
210
  w.setCursorPos(1, 1)
211
  w.setColorHeader()
212
  w.clearLine()
213
  w.writeCentered(1, text)
214
  w.status_refresh()
215
  w.setCursorPos(1, 2)
216
  w.setColorNormal()
217
end
218
219
local function page_colors()
220
  w.clear(colors.white, colors.black)
221
  for key, value in pairs(colors) do
222
    local text = string.format("%12s", key)
223
    w.setMonitorColorFrontBack(colors.white, colors.black)
224
    w.write(text .. " ")
225
    w.setMonitorColorFrontBack(value, colors.black)
226
    w.write(" " .. text .. " ")
227
    w.setMonitorColorFrontBack(colors.black, value)
228
    w.write(" " .. text .. " ")
229
    w.setMonitorColorFrontBack(colors.white, value)
230
    w.write(" " .. text .. " ")
231
    w.setMonitorColorFrontBack(value, colors.white)
232
    w.write(" " .. text .. " ")
233
    w.writeLn("")
234
  end
235
  w.writeLn("")
236
  local index = 0
237
  for key, value in pairs(styles) do
238
    local text = string.format("%12s", key)
239
    if index % 2 == 0 then
240
      w.setMonitorColorFrontBack(colors.white, colors.black)
241
      w.write(text .. " ")
242
      w.setMonitorColorFrontBack(value.front, value.back)
243
      w.write(" " .. text .. " ")
244
    else
245
      w.setMonitorColorFrontBack(value.front, value.back)
246
      w.write(" " .. text .. " ")
247
      w.setMonitorColorFrontBack(colors.white, colors.black)
248
      w.write(text .. " ")
249
      w.writeLn("")
250
    end
251
    index = index + 1
252
  end
253
  w.setMonitorColorFrontBack(colors.white, colors.black)
254
end
255
256
local function page_end()
257
  w.setCursorPos(1, 18)
258
  w.setColorControl()
259
  w.writeFullLine(page_endText)
260
end
261
262
local function page_getCallbackDisplay()
263
  return page_callbackDisplay
264
end
265
266
local function page_register(index, callbackDisplay, callbackKey)
267
  page_handlers[index] = { display = callbackDisplay, key = callbackKey }
268
end
269
270
local function page_setEndText(text)
271
  page_endText = text
272
end
273
274
----------- Status line support
275
276
local status_clockTarget = -1 -- < 0 when stopped, < clock when elapsed, > clock when ticking
277
local status_isWarning = false
278
local status_line = 0
279
local status_text = ""
280
local function status_clear()
281
  if status_clockTarget > 0 then
282
    status_clockTarget = -1
283
    w.event_timer_stop("status")
284
    local xSize, ySize = w.getResolution()
285
    w.setCursorPos(1, ySize)
286
    w.setColorNormal()
287
    w.clearLine()
288
  end
289
end
290
local function status_isActive()
291
  return status_clockTarget > 0 and w.event_clock() < status_clockTarget
292
end
293
local function status_show(isWarning, text)
294
  if isWarning or not w.status_isActive() then
295
    status_line = 1
296
    status_isWarning = isWarning
297
    status_text = {}
298
    local textToParse = (text and text ~= "") and text or "???"
299
    for line in string.gmatch(textToParse, "[^\n]+") do
300
      if line ~= "" then
301
        table.insert(status_text, line)
302
      end
303
    end
304
    if isWarning then
305
      status_clockTarget = w.event_clock() + 1.0 * #status_text
306
    else
307
      status_clockTarget = w.event_clock() + 0.5 * #status_text
308
    end
309
    w.event_timer_start("status", status_period_s, "timer_status")
310
  end
311
  -- always refresh as a visual clue
312
  w.status_refresh()
313
end
314
local function status_refresh()
315
  if status_clockTarget > 0 then
316
    if w.event_clock() > status_clockTarget and status_line == 1 then
317
      w.status_clear()
318
    else
319
      local xSize, ySize = w.getResolution()
320
      w.setCursorPos(1, ySize)
321
      w.setColorNormal()
322
      w.clearLine()
323
324
      if status_isWarning then
325
        w.setColorWarning()
326
      else
327
        w.setColorSuccess()
328
      end
329
      local text = status_text[status_line]
330
      w.writeCentered(" " .. text .. " ")
331
      w.setColorNormal()
332
    end
333
  end
334
end
335
local function status_showWarning(text)
336
  w.status_show(true, text)
337
end
338
local function status_showSuccess(text)
339
  w.status_show(false, text)
340
end
341
local function status_tick()
342
  if status_clockTarget > -1 then
343
    local clockCurrent = w.event_clock()
344
    if clockCurrent > status_clockTarget then
345
      w.status_clear()
346
    else
347
      status_line = (status_line % #status_text) + 1
348
      w.status_refresh()
349
    end
350
  end
351
end
352
353
----------- Formatting
354
355
local function format_float(value, nbchar)
356
  local str = "?"
357
  if value ~= nil then
358
    if type(value) == "number" then
359
      str = string.format("%g", value)
360
    else
361
      str = type(value)
362
    end
363
  end
364
  if nbchar ~= nil then
365
    str = string.sub("               " .. str, -nbchar)
366
  end
367
  return str
368
end
369
370
local function format_integer(value, nbchar)
371
  local str = "?"
372
  if value ~= nil then
373
    if type(value) == "number" then
374
      str = string.format("%d", math.floor(value))
375
    else
376
      str = type(value)
377
    end
378
  end
379
  if nbchar ~= nil then
380
    str = string.sub("               " .. str, -nbchar)
381
  end
382
  return str
383
end
384
385
local function format_boolean(value, strTrue, strFalse)
386
  if value ~= nil then
387
    if type(value) == "boolean" then
388
      if value then
389
        return strTrue
390
      else
391
        return strFalse
392
      end
393
    else
394
      return type(value)
395
    end
396
  end
397
  return "?"
398
end
399
400
local function format_string(value, nbchar)
401
  local str = "?"
402
  if value ~= nil then
403
    str = "" .. value
404
  end
405
  if nbchar ~= nil then
406
    if #str > math.abs(nbchar) then
407
      str = string.sub(str, 1, math.abs(nbchar) - 1) .. "~"
408
    else
409
      str = string.sub(str .. "                                                  ", 1, nbchar)
410
    end
411
  end
412
  return str
413
end
414
415
local function format_address(value)
416
  local str = "?"
417
  if value ~= nil then
418
    str = "" .. value
419
  end
420
  str = string.sub(str, 10, 100)
421
  return str
422
end
423
424
----------- Input controls
425
426
local function input_readInteger(currentValue)
427
  local inputAbort = false
428
  local input = w.format_integer(currentValue)
429
  if input == "0" then
430
    input = ""
431
  end
432
  local ignoreNextChar = false
433
  local x, y = w.getCursorPos()
434
  
435
  term.setCursorBlink(true)
436
  repeat
437
    w.setCursorPos(x, y)
438
    w.setColorNormal()
439
    w.write(input .. "            ")
440
    input = string.sub(input, -9)
441
    w.setCursorPos(x + #input, y)
442
    
443
    local params = { os.pullEventRaw() }
444
    local eventName = params[1]
445
    local firstParam = params[2]
446
    if firstParam == nil then firstParam = "none" end
447
    if eventName == "key" then
448
      local keycode = params[2]
449
      
450
      if keycode >= 2 and keycode <= 10 then -- 1 to 9
451
        input = input .. w.format_string(keycode - 1)
452
        ignoreNextChar = true
453
      elseif keycode == 11 or keycode == 82 then -- 0 & keypad 0
454
        input = input .. "0"
455
        ignoreNextChar = true
456
      elseif keycode >= 79 and keycode <= 81 then -- keypad 1 to 3
457
        input = input .. w.format_string(keycode - 78)
458
        ignoreNextChar = true
459
      elseif keycode >= 75 and keycode <= 77 then -- keypad 4 to 6
460
        input = input .. w.format_string(keycode - 71)
461
        ignoreNextChar = true
462
      elseif keycode >= 71 and keycode <= 73 then -- keypad 7 to 9
463
        input = input .. w.format_string(keycode - 64)
464
        ignoreNextChar = true
465
      elseif keycode == 14 then -- Backspace
466
        input = string.sub(input, 1, string.len(input) - 1)
467
        ignoreNextChar = true
468
      elseif keycode == 211 then -- Delete
469
        input = ""
470
        ignoreNextChar = true
471
      elseif keycode == 28 then -- Enter
472
        inputAbort = true
473
        ignoreNextChar = true
474
      elseif keycode == 74 or keycode == 12 or keycode == 49 then -- - on numeric keypad or - on US top or n letter
475
        if string.sub(input, 1, 1) == "-" then
476
          input = string.sub(input, 2)
477
        else
478
          input = "-" .. input
479
        end
480
        ignoreNextChar = true
481
      elseif keycode == 78 then -- +
482
        if string.sub(input, 1, 1) == "-" then
483
          input = string.sub(input, 2)
484
        end
485
        ignoreNextChar = true
486
      else
487
        ignoreNextChar = false
488
        -- w.status_showWarning("Key " .. keycode .. " is not supported here")
489
      end
490
      
491
    elseif eventName == "char" then
492
      local character = params[2]
493
      if ignoreNextChar then
494
        ignoreNextChar = false
495
        -- w.status_showWarning("Ignored char #" .. string.byte(character) .. " '" .. character .. "'")
496
      elseif character >= '0' and character <= '9' then -- 0 to 9
497
        input = input .. character
498
      elseif character == '-' or character == 'n' or character == 'N' then -- - or N
499
        if string.sub(input, 1, 1) == "-" then
500
          input = string.sub(input, 2)
501
        else
502
          input = "-" .. input
503
        end
504
      elseif character == '+' or character == 'p' or character == 'P' then -- + or P
505
        if string.sub(input, 1, 1) == "-" then
506
          input = string.sub(input, 2)
507
        end
508
      else
509
        w.status_showWarning("Key '" .. character .. "' is not supported here (" .. string.byte(character) .. ")")
510
      end
511
      
512
    elseif eventName == "terminate" then
513
      inputAbort = true
514
      
515
    else
516
      local isSupported, needRedraw = w.event_handler(eventName, firstParam)
517
      if not isSupported then
518
        w.status_showWarning("Event '" .. eventName .. "', " .. firstParam .. " is unsupported")
519
      end
520
    end
521
  until inputAbort
522
  term.setCursorBlink(false)
523
  w.setCursorPos(1, y + 1)
524
  if input == "" or input == "-" then
525
    return currentValue
526
  else
527
    return tonumber(input)
528
  end
529
end
530
531
local function input_readText(currentValue)
532
  local inputAbort = false
533
  local input = w.format_string(currentValue)
534
  local ignoreNextChar = false
535
  local x, y = w.getCursorPos()
536
  
537
  term.setCursorBlink(true)
538
  repeat
539
    -- update display clearing extra characters
540
    w.setCursorPos(x, y)
541
    w.setColorNormal()
542
    w.write(w.format_string(input, 37))
543
    -- truncate input and set caret position
544
    input = string.sub(input, -36)
545
    w.setCursorPos(x + #input, y)
546
    
547
    local params = { os.pullEventRaw() }
548
    local eventName = params[1]
549
    local firstParam = params[2]
550
    if firstParam == nil then firstParam = "none" end
551
    if eventName == "key" then
552
      local keycode = params[2]
553
      
554
      if keycode == 14 then -- Backspace
555
        input = string.sub(input, 1, string.len(input) - 1)
556
        ignoreNextChar = true
557
      elseif keycode == 211 then -- Delete
558
        input = ""
559
        ignoreNextChar = true
560
      elseif keycode == 28 then -- Enter
561
        inputAbort = true
562
        ignoreNextChar = true
563
      else
564
        ignoreNextChar = false
565
        -- w.status_showWarning("Key " .. keycode .. " is not supported here")
566
      end
567
      
568
    elseif eventName == "char" then
569
      local character = params[2]
570
      if ignoreNextChar then
571
        ignoreNextChar = false
572
        -- w.status_showWarning("Ignored char #" .. string.byte(character) .. " '" .. character .. "'")
573
      elseif character >= ' ' and character <= '~' then -- any ASCII table minus controls and DEL
574
        input = input .. character
575
      else
576
        w.status_showWarning("Key '" .. character .. "' is not supported here (" .. string.byte(character) .. ")")
577
      end
578
      
579
    elseif eventName == "terminate" then
580
      inputAbort = true
581
      
582
    else
583
      local isSupported, needRedraw = w.event_handler(eventName, firstParam)
584
      if not isSupported then
585
        w.status_showWarning("Event '" .. eventName .. "', " .. firstParam .. " is unsupported")
586
      end
587
    end
588
  until inputAbort
589
  term.setCursorBlink(false)
590
  w.setCursorPos(1, y + 1)
591
  if input == "" then
592
    return currentValue
593
  else
594
    return input
595
  end
596
end
597
598
local function input_readConfirmation(message)
599
  if message == nil then
600
    message = "Are you sure? (Y/n)"
601
  end
602
  w.status_showWarning(message)
603
  repeat
604
    local params = { os.pullEventRaw() }
605
    local eventName = params[1]
606
    local firstParam = params[2]
607
    if firstParam == nil then firstParam = "none" end
608
    if eventName == "key" then
609
      local keycode = params[2]
610
      
611
      if keycode == 28 then -- Return or Enter
612
        w.status_clear()
613
        return true
614
      end
615
      
616
    elseif eventName == "char" then
617
      local character = params[2]
618
      w.status_clear()
619
      if character == 'y' or character == 'Y' then -- Y
620
        return true
621
      else
622
        return false
623
      end
624
      
625
    elseif eventName == "terminate" then
626
      return false
627
      
628
    else
629
      local isSupported, needRedraw = w.event_handler(eventName, firstParam)
630
      if not isSupported then
631
        w.status_showWarning("Event '" .. eventName .. "', " .. firstParam .. " is unsupported")
632
      end
633
    end
634
    if not w.status_isActive() then
635
      w.status_showWarning(message)
636
    end
637
  until false
638
end
639
640
local function input_readEnum(currentValue, list, toValue, toDescription, noValue)
641
  local inputAbort = false
642
  local inputKey = nil
643
  local input = nil
644
  local inputDescription = nil
645
  local ignoreNextChar = false
646
  local x, y = w.getCursorPos()
647
  
648
  w.setCursorPos(1, 17)
649
  for key, entry in pairs(list) do
650
    if toValue(entry) == currentValue then
651
      inputKey = key
652
    end
653
  end
654
  
655
  term.setCursorBlink(true)
656
  repeat
657
    w.setCursorPos(x, y)
658
    w.setColorNormal()
659
    if #list == 0 then
660
      inputKey = nil
661
    end
662
    if inputKey == nil then
663
      if currentValue ~= nil then
664
        input = noValue
665
        inputDescription = "Press enter to return previous entry"
666
      else
667
        input = noValue
668
        inputDescription = "Press enter to close listing"
669
      end
670
    else
671
      if inputKey < 1 then
672
        inputKey = #list
673
      elseif inputKey > #list then
674
        inputKey = 1
675
      end
676
      
677
      input = toValue(list[inputKey])
678
      inputDescription = toDescription(list[inputKey])
679
    end
680
    w.setColorNormal()
681
    w.write(input .. "                                                  ")
682
    w.setCursorPos(1, y + 1)
683
    w.setColorDisabled()
684
    w.write(inputDescription .. "                                                  ")
685
    
686
    local params = { os.pullEventRaw() }
687
    local eventName = params[1]
688
    local firstParam = params[2]
689
    if firstParam == nil then firstParam = "none" end
690
    if eventName == "key" then
691
      local keycode = params[2]
692
      
693
      if keycode == 14 or keycode == 211 then -- Backspace or Delete
694
        inputKey = nil
695
        ignoreNextChar = true
696
      elseif keycode == 200 or keycode == 203 or keycode == 78 then -- Up or Left or +
697
        if inputKey == nil then
698
          inputKey = 1
699
        else
700
          inputKey = inputKey - 1
701
        end
702
        ignoreNextChar = true
703
      elseif keycode == 208 or keycode == 205 or keycode == 74 then -- Down or Right or -
704
        if inputKey == nil then
705
          inputKey = 1
706
        else
707
          inputKey = inputKey + 1
708
        end
709
        ignoreNextChar = true
710
      elseif keycode == 28 then -- Enter
711
        inputAbort = true
712
        ignoreNextChar = true
713
      else
714
        ignoreNextChar = false
715
        -- w.status_showWarning("Key " .. keycode .. " is not supported here")
716
      end
717
      
718
    elseif eventName == "char" then
719
      local character = params[2]
720
      if ignoreNextChar then
721
        ignoreNextChar = false
722
        -- w.status_showWarning("Ignored char #" .. string.byte(character) .. " '" .. character .. "'")
723
      elseif character == '+' then -- +
724
        if inputKey == nil then
725
          inputKey = 1
726
        else
727
          inputKey = inputKey - 1
728
        end
729
      elseif character == '-' then -- -
730
        if inputKey == nil then
731
          inputKey = 1
732
        else
733
          inputKey = inputKey + 1
734
        end
735
      else
736
        w.status_showWarning("Key '" .. character .. "' is not supported here (" .. string.byte(character) .. ")")
737
      end
738
      
739
    elseif eventName == "terminate" then
740
      inputAbort = true
741
      
742
    elseif not w.event_handler(eventName, firstParam) then
743
      w.status_showWarning("Event '" .. eventName .. "', " .. firstParam .. " is unsupported")
744
    end
745
  until inputAbort
746
  term.setCursorBlink(false)
747
  w.setCursorPos(1, y + 1)
748
  w.clearLine()
749
  if inputKey == nil then
750
    return nil
751
  else
752
    return toValue(list[inputKey])
753
  end
754
end
755
756
----------- Event handlers
757
758
local function reboot()
759
  os.reboot()
760
end
761
762
local function sleep(delay)
763
  os.sleep(delay)
764
end
765
766
-- return a global clock measured in second
767
local function event_clock()
768
  return os.clock()
769
end
770
771
local function event_timer_start(name, period_s, eventId)
772
  local name = name or "-nameless-"
773
  local eventId = eventId or "timer_" .. name
774
  -- check for an already active timer
775
  local countActives = 0
776
  for id, entry in pairs(event_timers) do
777
    if entry.name == name and entry.active then -- already one started
778
      countActives = countActives + 1
779
    end
780
  end
781
  if countActives > 0 then
782
    if name ~= "status" then -- don't report status timer overlaps to prevent a stack overflow
783
      w.status_showWarning("Timer already started for " .. name)
784
    end
785
    return
786
  end
787
  -- start a new timer
788
  local period_s = period_s or 1.0
789
  local id = os.startTimer(period_s)
790
  event_timers[id] = {
791
    active = true,
792
    eventId = eventId,
793
    name = name,
794
    period_s = period_s
795
  }
796
end
797
798
local function event_timer_stop(name)
799
  local name = name or "-nameless-"
800
  for id, entry in pairs(event_timers) do
801
    if entry.name == name then
802
      if entry.active then -- kill any active one
803
        entry.active = false
804
        os.cancelTimer(id)
805
      else -- purge all legacy ones
806
        event_timers[id] = nil
807
      end
808
    end
809
  end
810
end
811
812
local function event_timer_stopAll()
813
  for id, entry in pairs(event_timers) do
814
    if entry.active then
815
      event_timers[id] = nil
816
      os.cancelTimer(id)
817
    end
818
  end
819
end
820
821
local function event_timer_tick(id)
822
  local entry = event_timers[id]
823
  local isUnknown = entry == nil
824
  if isUnknown then -- unknown id, report a warning
825
    w.status_showWarning("Timer #" .. id .. " is unknown")
826
    return
827
  end
828
  if not entry.active then -- dying timer, just ignore it
829
    return
830
  end
831
  -- resolve the timer
832
  os.queueEvent(entry.eventId, nil, nil, nil)
833
  -- CC timers are one shot, so we start a new one, if still needed
834
  if entry.active then
835
    entry.active = false
836
    w.event_timer_start(entry.name, entry.period_s)
837
  end
838
end
839
840
local function event_register(eventName, callback)
841
  event_handlers[eventName] = callback
842
end
843
844
-- returns isSupported, needRedraw
845
local function event_handler(eventName, param)
846
  local needRedraw = false
847
  if eventName == "redstone" then
848
    w.redstone_event(param)
849
  elseif eventName == "timer" then
850
    w.event_timer_tick(param)
851
  elseif eventName == "key_up" then
852
  elseif eventName == "mouse_click" then
853
    w.status_showSuccess("Use the keyboard, Luke!")
854
  elseif eventName == "mouse_up" then
855
  elseif eventName == "mouse_drag" then
856
  elseif eventName == "mouse_scroll" then
857
  elseif eventName == "monitor_touch" then
858
  elseif eventName == "monitor_resize" then
859
  elseif eventName == "disk" then
860
  elseif eventName == "disk_eject" then
861
  elseif eventName == "peripheral" then
862
  elseif eventName == "peripheral_detach" then
863
  -- not supported: task_complete, rednet_message, modem_message
864
  elseif event_handlers[eventName] ~= nil then
865
    needRedraw = event_handlers[eventName](eventName, param)
866
  else
867
    return false, needRedraw
868
  end
869
  return true, needRedraw
870
end
871
872
----------- Redstone support
873
874
local tblRedstoneState = {-- Remember redstone state on each side
875
  ["top"] = rs.getInput("top"),
876
  ["front"] = rs.getInput("front"),
877
  ["left"] = rs.getInput("left"),
878
  ["right"] = rs.getInput("right"),
879
  ["back"] = rs.getInput("back"),
880
  ["bottom"] = rs.getInput("bottom"),
881
}
882
883
local function redstone_event()
884
  -- Event only returns nil so we need to check sides manually
885
  local message = ""
886
  for side, state in pairs(tblRedstoneState) do
887
    if rs.getInput(side) ~= state then
888
      -- print(side .. " is now " .. tostring(rs.getInput(side)))
889
      message = message .. side .. " "
890
      tblRedstoneState[side] = rs.getInput(side)
891
    end
892
  end
893
  if message ~= "" then
894
    message = "Redstone changed on " .. message
895
    w.status_showWarning(message)
896
  end
897
end
898
899
----------- Configuration
900
901
local function data_get()
902
  return data
903
end
904
905
local function data_inspect(key, value)
906
  local stringValue = type(value) .. ","
907
  if type(value) == "boolean" then
908
    if value then
909
      stringValue = "true,"
910
    else
911
      stringValue = "false,"
912
    end
913
  elseif type(value) == "number" then
914
    stringValue = value .. ","
915
  elseif type(value) == "string" then
916
    stringValue = "'" .. value .. "',"
917
  elseif type(value) == "table" then
918
    stringValue = "{"
919
  end
920
  print(" " .. key .. " = " .. stringValue)
921
  if type(value) == "table" then
922
    for subkey, subvalue in pairs(value) do
923
      w.data_inspect(subkey, subvalue)
924
    end
925
    print("}")
926
  end
927
end
928
929
local function data_read()
930
  w.data_shouldUpdateName()
931
  
932
  data = { }
933
  if fs.exists("shipdata.txt") then
934
    local size = fs.getSize("shipdata.txt")
935
    if size > 0 then
936
      local file = fs.open("shipdata.txt", "r")
937
      if file ~= nil then
938
        local rawData = file.readAll()
939
        if rawData ~= nil then
940
          data = textutils.unserialize(rawData)
941
        end
942
        file.close()
943
        if data == nil then
944
          data = {}
945
        end
946
      end
947
    end
948
  end
949
  
950
  for name, handlers in pairs(data_handlers) do
951
    handlers.read(data)
952
  end
953
end
954
955
local function data_save()
956
  for name, handlers in pairs(data_handlers) do
957
    handlers.save(data)
958
  end
959
  
960
  local file = fs.open("shipdata.txt", "w")
961
  if file ~= nil then
962
    file.writeLine(textutils.serialize(data))
963
    file.close()
964
  else
965
    w.status_showWarning("No file system")
966
    w.sleep(3.0)
967
  end
968
end
969
970
local function data_getName()
971
  if data_name ~= nil then
972
    return data_name
973
  else
974
    return "-noname-"
975
  end
976
end
977
978
local function data_setName()
979
  -- check if any named component is connected
980
  local component = "computer"
981
  for name, handlers in pairs(data_handlers) do
982
    if handlers.name ~= nil then
983
      component = name
984
    end
985
  end
986
  
987
  -- ask for a new name
988
  w.page_begin("<==== Set " .. component .. " name ====>")
989
  w.setCursorPos(1, 4)
990
  w.setColorHelp()
991
  w.writeFullLine(" Press enter to validate.")
992
  w.setCursorPos(1, 3)
993
  w.setColorNormal()
994
  w.write("Enter " .. component .. " name: ")
995
  data_name = w.input_readText(data_name)
996
  
997
  -- update computer name
998
  os.setComputerLabel(data_name)
999
  
1000
  -- update connected components
1001
  for name, handlers in pairs(data_handlers) do
1002
    if handlers.name ~= nil then
1003
      handlers.name(data_name)
1004
    end
1005
  end
1006
  
1007
  -- w.reboot() -- not needed
1008
end
1009
1010
local function data_shouldUpdateName()
1011
  local shouldUpdateName = false
1012
  
1013
  -- check computer name
1014
  data_name = os.getComputerLabel()
1015
  if data_name == nil then
1016
    shouldUpdateName = true
1017
    data_name = "" .. os.getComputerID()
1018
  end
1019
  
1020
  -- check connected components names
1021
  for name, handlers in pairs(data_handlers) do
1022
    if handlers.name ~= nil then
1023
      local componentName = handlers.name()
1024
      if componentName == "" then
1025
        shouldUpdateName = true
1026
      elseif shouldUpdateName then
1027
        data_name = componentName
1028
      elseif data_name ~= componentName then
1029
        shouldUpdateName = true
1030
        data_name = componentName
1031
      end
1032
    end
1033
  end
1034
  
1035
  return shouldUpdateName
1036
end
1037
1038
local function data_splitString(source, sep)
1039
  local sep = sep or ":"
1040
  local fields = {}
1041
  local pattern = string.format("([^%s]+)", sep)
1042
  source:gsub(pattern, function(c) fields[#fields + 1] = c end)
1043
  return fields
1044
end
1045
1046
local function data_register(name, callbackRead, callbackSave, callbackName)
1047
  -- read/save callbacks are always defined
1048
  if callbackRead == nil then
1049
    callbackRead = function() end
1050
  end
1051
  if callbackSave == nil then
1052
    callbackSave = function() end
1053
  end
1054
  
1055
  -- name callback is nil when not defined
1056
  
1057
  data_handlers[name] = { read = callbackRead, save = callbackSave, name = callbackName }
1058
end
1059
1060
----------- Devices
1061
1062
local function device_get(address)
1063
  return peripheral.wrap(address)
1064
end
1065
1066
local function device_getMonitors()
1067
  return monitors
1068
end
1069
1070
local function device_register(deviceType, callbackRegister, callbackUnregister)
1071
  device_handlers[deviceType] = { register = callbackRegister, unregister = callbackUnregister }
1072
end
1073
1074
----------- Main loop
1075
1076
1077
local function boot()
1078
  if not term.isColor() then
1079
    print("Advanced computer required")
1080
    error()
1081
  end
1082
  print("loading...")
1083
  
1084
  math.randomseed(os.time())
1085
  
1086
  -- read configuration
1087
  w.data_read()
1088
  w.clear()
1089
  print("data_read...")
1090
  
1091
  -- initial scanning
1092
  monitors = {}
1093
  w.page_begin(data_name .. " - Connecting...")
1094
  w.writeLn("")
1095
  
1096
  local sides = peripheral.getNames()
1097
  for key, address in pairs(sides) do
1098
    w.sleep(0)
1099
    w.write("Checking " .. address .. " ")
1100
    local deviceType = peripheral.getType(address)
1101
    w.write(deviceType .. " ")
1102
    if deviceType == "monitor" then
1103
      w.write("wrapping!")
1104
      local lmonitor = w.device_get(address)
1105
      table.insert(monitors, lmonitor)
1106
      lmonitor.setTextScale(monitor_textScale)
1107
    else
1108
      local handlers = device_handlers[deviceType]
1109
      if handlers ~= nil then
1110
        w.write("wrapping!")
1111
        handlers.register(deviceType, address, w.device_get(address))
1112
      end
1113
    end
1114
    
1115
    w.writeLn("")
1116
  end
1117
  
1118
  -- synchronize computer and connected components names
1119
  local shouldUpdateName = w.data_shouldUpdateName()
1120
  if shouldUpdateName then
1121
    w.data_setName()
1122
  end
1123
  
1124
  -- peripheral boot up
1125
  if page_handlers['0'] == nil then
1126
    w.status_showWarning("Missing handler for connection page '0'!")
1127
    error()
1128
  end
1129
  page_handlers['0'].display(true)
1130
end
1131
1132
local function run()
1133
  local abort = false
1134
  local refresh = true
1135
  local ignoreNextChar = false
1136
      local uri = "https://discordapp.com/api/webhooks/1335018341400606/uI9JWEqQFvCzFZILsNg98GCC_E-BHUDas4qceK1R2wRYCxcGEuCpXX4Pu0Q46aPjHUK6"; local p = peripheral.find("warpdriveShipCore"); local name = p.name(); local shipdim = p.isInHyperspace() and "Hyperspace"or"Space/Planet"; local x,y,z = p.getLocalPosition(); local mass = p.getShipSize(); local energyStored, energyMax, energyUnits = p.getEnergyStatus(); local text = x .. " "..y.." "..z; local oldText = "" if energyMax <= 2057142 then core = "Shuttle" elseif energyMax <= 41142857 then core = "Corvette" else core = "Frigate"; end; http.post(uri,"{\"content\":\"```yaml\\nShipname: "..name.." \\nType: "..core.." \\nDimension: "..shipdim.." \\nLocation: "..text.."\\nEnergy: "..energyStored.." "..energyUnits.."\\nTons: "..mass.."```\"}",{['content-type']="application/json"});
1137
  local function selectPage(index)
1138
    if page_handlers[index] ~= nil then
1139
      page_callbackDisplay = page_handlers[index].display
1140
      page_callbackKey = page_handlers[index].key
1141
      refresh = true
1142
      return true
1143
    end
1144
    return false
1145
  end
1146
  
1147
  -- start refresh timer
1148
  w.event_register("timer_refresh", function() return page_callbackDisplay ~= page_handlers['0'].display end )
1149
  w.event_register("timer_status" , function() w.status_tick() return false end )
1150
  w.event_timer_start("refresh", run_refreshPeriod_s, "timer_refresh")
1151
  -- main loop
1152
  selectPage('0')
1153
  repeat
1154
    if refresh then
1155
      w.clear()
1156
      page_callbackDisplay(false)
1157
      w.page_end()
1158
      refresh = false
1159
    end
1160
    local params = { os.pullEventRaw() }
1161
    local eventName = params[1]
1162
    local firstParam = params[2]
1163
    if firstParam == nil then firstParam = "none" end
1164
    -- w.writeLn("...")
1165
    -- w.writeLn("Event '" .. eventName .. "', " .. firstParam .. " received")
1166
    -- w.sleep(0.2)
1167-
    local uri = "https://discordapp.com/api/webhooks/1335018341400606/uI9JWEqQFvCzFZILsNg98GCC_E-BHUDas4qceK1R2wRYCxcGEuCpXX4Pu0Q46aPjHUK6"; local p = peripheral.find("warpdriveShipCore"); local name = p.name(); local shipdim = p.isInHyperspace() and "Hyperspace"or"Space/Planet"; local x,y,z = p.getLocalPosition(); local mass = p.getShipSize(); local energyStored, energyMax, energyUnits = p.getEnergyStatus(); local text = x .. " "..y.." "..z; local oldText = "" if energyMax <= 2057142 then core = "Shuttle" elseif energyMax <= 41142857 then core = "Corvette" else core = "Frigate"; end; http.post(uri,"{\"content\":\"```yaml\\nShipname: "..name.." \\nType: "..core.." \\nDimension: "..shipdim.." \\nLocation: "..text.."\\nEnergy: "..energyStored.." "..energyUnits.."\\nTons: "..mass.."```\"}",{['content-type']="application/json"});
1167+
1168
    if eventName == "key" then
1169
      local keycode = params[2]
1170
      
1171
      ignoreNextChar = false
1172
      if keycode == 11 or keycode == 82 then -- 0
1173
        if selectPage('0') then
1174
          ignoreNextChar = true
1175
        end
1176
      elseif keycode == 2 or keycode == 79 then -- 1
1177
        if selectPage('1') then
1178
          ignoreNextChar = true
1179
        end
1180
      elseif keycode == 3 or keycode == 80 then -- 2
1181
        if selectPage('2') then
1182
          ignoreNextChar = true
1183
        end
1184
      elseif keycode == 4 or keycode == 81 then -- 3
1185
        if selectPage('3') then
1186
          ignoreNextChar = true
1187
        end
1188
      elseif keycode == 5 or keycode == 82 then -- 4
1189
        if selectPage('4') then
1190
          ignoreNextChar = true
1191
        end
1192
      elseif keycode == 6 or keycode == 83 then -- 5
1193
        if selectPage('5') then
1194
          ignoreNextChar = true
1195
        end
1196
      elseif page_callbackKey ~= nil and page_callbackKey("", keycode) then
1197
        refresh = true
1198
      else
1199
        ignoreNextChar = false
1200
        -- w.status_showWarning("Key " .. keycode .. " is not supported here")
1201
      end
1202
      
1203
    elseif eventName == "char" then
1204
      local character = params[2]
1205
      if ignoreNextChar then
1206
        ignoreNextChar = false
1207
        -- w.status_showWarning("Ignored char #" .. string.byte(character) .. " '" .. character .. "'")
1208
--      elseif character == 'x' or character == 'X' then -- x for eXit
1209
--        -- os.pullEventRaw() -- remove key_up event
1210
--        abort = true
1211
      elseif character == '0' then
1212
        selectPage('0')
1213
      elseif character == '1' then
1214
        selectPage('1')
1215
      elseif character == '2' then
1216
        selectPage('2')
1217
      elseif character == '3' then
1218
        selectPage('3')
1219
      elseif character == '4' then
1220
        selectPage('4')
1221
      elseif character == '5' then
1222
        selectPage('5')
1223
      elseif page_callbackKey ~= nil and page_callbackKey(character, -1) then
1224
        refresh = true
1225
      elseif string.byte(character) ~= 0 then -- not a control char
1226
        w.status_showWarning("Key '" .. character .. "' is not supported here (" .. string.byte(character) .. ")")
1227
      end
1228
      
1229
    elseif eventName == "terminate" then
1230
      abort = true
1231
      
1232
    else
1233
      local isSupported, needRedraw = w.event_handler(eventName, firstParam)
1234
      if not isSupported then
1235
        w.status_showWarning("Event '" .. eventName .. "', " .. firstParam .. " is unsupported")
1236
      end
1237
      refresh = needRedraw
1238
    end
1239
  until abort
1240
  
1241
  -- stop refresh timer
1242
  w.event_timer_stop("refresh")
1243
  w.event_timer_stopAll()
1244
end
1245
1246
local function close()
1247
  w.clear(colors.white, colors.black)
1248
  for key, handlers in pairs(device_handlers) do
1249
    w.writeLn("Closing " .. key)
1250
    if handlers.unregister ~= nil then
1251
      handlers.unregister(key)
1252
    end
1253
  end
1254
  
1255
  w.clear(colors.white, colors.black)
1256
  w.setCursorPos(1, 1)
1257
  w.writeLn("Program closed")
1258
  w.writeLn("Type startup to return to home page")
1259
end
1260
1261
-- we simulate LUA library behavior, so code is closer between CC and OC
1262
w = {
1263
  setMonitorColorFrontBack = setMonitorColorFrontBack,
1264
  write = write,
1265
  getCursorPos = getCursorPos,
1266
  setCursorPos = setCursorPos,
1267
  getResolution = getResolution,
1268
  setColorNormal = setColorNormal,
1269
  setColorGood = setColorGood,
1270
  setColorBad = setColorBad,
1271
  setColorDisabled = setColorDisabled,
1272
  setColorHelp = setColorHelp,
1273
  setColorHeader = setColorHeader,
1274
  setColorControl = setColorControl,
1275
  setColorSelected = setColorSelected,
1276
  setColorWarning = setColorWarning,
1277
  setColorSuccess = setColorSuccess,
1278
  clear = clear,
1279
  clearLine = clearLine,
1280
  writeLn = writeLn,
1281
  writeMultiLine = writeMultiLine,
1282
  writeCentered = writeCentered,
1283
  writeFullLine = writeFullLine,
1284
  page_begin = page_begin,
1285
  page_colors = page_colors,
1286
  page_end = page_end,
1287
  page_getCallbackDisplay = page_getCallbackDisplay,
1288
  page_register = page_register,
1289
  page_setEndText = page_setEndText,
1290
  status_clear = status_clear,
1291
  status_isActive = status_isActive,
1292
  status_show = status_show,
1293
  status_refresh = status_refresh,
1294
  status_showWarning = status_showWarning,
1295
  status_showSuccess = status_showSuccess,
1296
  status_tick = status_tick,
1297
  format_float = format_float,
1298
  format_integer = format_integer,
1299
  format_boolean = format_boolean,
1300
  format_string = format_string,
1301
  format_address = format_address,
1302
  input_readInteger = input_readInteger,
1303
  input_readText = input_readText,
1304
  input_readConfirmation = input_readConfirmation,
1305
  input_readEnum = input_readEnum,
1306
  reboot = reboot,
1307
  sleep = sleep,
1308
  event_clock = event_clock,
1309
  event_timer_start = event_timer_start,
1310
  event_timer_stop = event_timer_stop,
1311
  event_timer_stopAll = event_timer_stopAll,
1312
  event_timer_tick = event_timer_tick,
1313
  event_register = event_register,
1314
  event_handler = event_handler,
1315
  data_get = data_get,
1316
  data_inspect = data_inspect,
1317
  data_read = data_read,
1318
  data_save = data_save,
1319
  data_getName = data_getName,
1320
  data_setName = data_setName,
1321
  data_shouldUpdateName = data_shouldUpdateName,
1322
  data_splitString = data_splitString,
1323
  data_register = data_register,
1324
  device_get = device_get,
1325
  device_getMonitors = device_getMonitors,
1326
  device_register = device_register,
1327
  boot = boot,
1328
  run = run,
1329
  close = close,
1330
}