summaryrefslogtreecommitdiffstats
path: root/README.md
blob: bd1aed063f84c6a5ddc8777c85a113d662e28de5 (plain)
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
**Note: Folk is in a *pre-alpha* state and isn't yet well-documented
or well-exampled.**

**We're making Folk's source code free and available to the public in
a [read-only form](https://git.folk.computer/folk/about/), in case
you're already excited about trying it, but we haven't formally
announced it or made it ready for public use. We make no guarantee of
support, of usability, or of continuing backward compatibility. Try at
your own risk!**

We're working on a more complete open-source release for 2024, which
would open up our internal GitHub repository, document the
installation process, and provide canonical examples/demos to show
what's possible. If you don't know what this is, then you might want
to wait for that release.

-----

# [Folk](https://folk.computer)

## Hardware

You'll need to set up a dedicated PC to run Folk and connect to
webcam+projector+printer+etc.

We tend to recommend a Beelink mini-PC (or _maybe_ a Pi 4).

See <https://folk.computer/pilot/>

## Linux tabletop installation using live USB

**Experimental:** If you have an amd64 PC, you can use the live USB
image which has Folk and all dependencies pre-installed.

**See <https://github.com/FolkComputer/folk-live-build/releases> to
get the Linux live USB image.**

You can update Folk by running `git pull` in the `folk` subfolder of
the FOLK-LIVE partition once you've flashed the live USB.

## Manual Linux tabletop installation

Set up [Ubuntu **Server** 23.04 Lunar
Lobster](https://ubuntu.com/download/server#releases).

(for a PC, get the amd64 version; for a Pi 4, use Raspberry Pi Imager
and get the 64-bit version [also see [this
issue](https://github.com/raspberrypi/rpi-imager/issues/466#issuecomment-1207107554)
if flashing from a Mac])

1. Install Linux with username `folk`, hostname
   `folk-SOMETHING`? (check hosts.tcl in this repo to make sure
   you're not reusing one)

   If no `folk` user, then:

        sudo useradd -m folk; sudo passwd folk;
        sudo usermod -a -G adm,dialout,cdrom,sudo,audio,video,plugdev,games,users,input,render,netdev,lpadmin,gpio,i2c,spi folk

   (If you get errors from usermod like `group 'gpio' does not exist`,
   try running again omitting the groups that don't exist from the
   command.)

1. `sudo apt update`

1. Set up OpenSSH server if needed; connect to network. To ssh into
   `folk@folk-WHATEVER.local` by name, `sudo apt install avahi-daemon`
   and then on your laptop: `ssh-copy-id folk@folk-WHATEVER.local`

1. `sudo adduser folk video` & `sudo adduser folk render` & `sudo
   adduser folk input` (?) & log out and log back in (re-ssh)

1. Install dependencies: `sudo apt install rsync tcl-thread tcl8.6-dev
   git libjpeg-dev libpng-dev fbset libdrm-dev pkg-config v4l-utils
   mesa-vulkan-drivers vulkan-tools libvulkan-dev libvulkan1 meson
   libgbm-dev glslc vulkan-validationlayers`

   (glslc may not be available if you're not on Ubuntu 23.04; on ARM
   like Pi 4 you need to build it from source; [binaries are
   available](https://github.com/google/shaderc/blob/main/downloads.md)
   otherwise)

1. Vulkan testing (optional):
     1. Try `vulkaninfo` and see if it works.
          1. On a Pi 4, if vulkaninfo reports "Failed to detect any
             valid GPUs in the current config", add
             `dtoverlay=vc4-fkms-v3d` or `dtoverlay=vc4-kms-v3d` (I
             think this one is more recommended now?) to the bottom of
             `/boot/firmware/config.txt` or `/boot/config.txt`,
             whichever exists
             (<https://raspberrypi.stackexchange.com/questions/116507/open-dev-dri-card0-no-such-file-or-directory-on-rpi4>)
     1. Try `vkcube`:

            git clone https://github.com/krh/vkcube
            cd vkcube
            mkdir build; cd build; meson .. && ninja
            ./vkcube -m khr -k 0:0:0
      
        If vkcube says `Assertion ``vc->image_count > 0' failed`, you
        might be able to still skip vkcube and continue the install
        process. See [this
        bug](https://github.com/FolkComputer/folk/issues/109#issuecomment-1788085237)
     1. See [notes](https://folk.computer/notes/vulkan) and [Naveen's
        notes](https://gist.github.com/nmichaud/1c08821833449bdd3ac70dcb28486539).

1. `sudo nano /etc/udev/rules.d/99-input.rules`. add
   `SUBSYSTEM=="input", GROUP="input", MODE="0666"`. `sudo udevadm
   control --reload-rules && sudo udevadm trigger`

1. Get AprilTags: `cd ~ && git clone
   https://github.com/FolkComputer/apriltag.git && cd apriltag && make`
   (you can probably ignore errors at the end of this if they're just
   for the OpenCV demo)

1. Add the systemd service so it starts on boot and can be managed
   when you run it from laptop. On Ubuntu Server or Raspberry Pi OS
   (as root) ([from
   here](https://medium.com/@benmorel/creating-a-linux-service-with-systemd-611b5c8b91d6)):

       # cat >/etc/systemd/system/folk.service
       [Unit]
       Description=Folk service
       After=network.target
       StartLimitIntervalSec=0

       [Service]
       Type=simple
       Restart=always
       RestartSec=1
       User=folk
       ExecStart=make -C /home/folk/folk

       [Install]
       WantedBy=multi-user.target

       # systemctl start folk
       # systemctl enable folk

Use `visudo` to add `folk ALL=(ALL) NOPASSWD: /usr/bin/systemctl` to
the bottom of `/etc/sudoers` on the tabletop. (This lets the `make`
scripts from your laptop manage the Folk service by running
`systemctl` without needing a password.)

Then, _on your laptop_, clone this repository:

```
$ git clone https://git.folk.computer/folk
```

And run `make sync-restart FOLK_SHARE_NODE=folk-WHATEVER.local`. This
will rsync folk to the tabletop and run it there as well as running it
on your laptop.

### How to control tabletop Folk from your laptop

On your laptop Web browser, go to http://folk-WHATEVER.local:4273 --
click New Program, hit Save, drag it around. You should see the
program move on your table as you drag it around on your laptop.

Does it work? Add your tabletop to hosts.tcl! Send in a patch!
Celebrate!

### General debugging

You can run `make journal` to see stdout/stderr output from the
tabletop machine. If you need to pass in a specific hostname, `make
journal FOLK_SHARE_NODE=folk-whatever.local`.

`make repl` will give you a dialed-in Tcl REPL.

### Printer support

On the tabletop:
```
$ sudo apt update
$ sudo apt install cups cups-bsd
$ sudo usermod -a -G lpadmin folk
```

(`cups-bsd` provides the `lpr` command that we use to print)

ssh tunnel to get access to CUPS Web UI: run on your laptop `ssh -L 6310:localhost:631
folk@folk-WHATEVER.local`, leave it open

Go to http://localhost:6310 on your computer, go to Printers,
hopefully it shows up there automatically, try printing test page. _I
could not get that implicitclass:// automatically-added printer in
CUPS to work for my printer at all_, so I did the below:

If job is paused due to `cups-browsed` issue or otherwise doesn't
work, try
https://askubuntu.com/questions/1128164/no-suitable-destination-host-found-by-cups-browsed :
remove `cups-browsed` `sudo apt-get purge --autoremove cups-browsed`
then add printer manually via IPP in Add Printer in Administration tab
of CUPS Web UI (it might automatically show up under Discovered
Network Printers there using dnssd)

Once printer is working, go to Administration dropdown on printer page
and Set as Server Default.

Try printing from Folk!

You can also test printing again with `lpr
~/folk-printed-programs/SOMETHING.pdf` (you have to print the PDF and
not the PS for it to work, probably)

### Projector-camera calibration

1. Print at least 4 AprilTags (either print throwaway programs from Folk or
   manually print tagStandard52h13 tags yourself).

1. Let's position the camera. Make sure Folk is running (ssh in, `cd
   ~/folk`, `./folk.tcl start`). Go to your Folk server's Web page
   http://whatever.local:4273 and make a new program and save it:
   
   ```
   When the camera frame is /im/ {
     Wish the web server handles route "/frame-image/$" with handler [list apply {{im} {
       # set width [dict get $im width]
       # set height [dict get $im height]
       set filename "/tmp/web-image-frame.jpg"
       image saveAsJpeg $im $filename
       set fsize [file size $filename]
       set fd [open $filename r]
       fconfigure $fd -encoding binary -translation binary
       set body [read $fd $fsize]
       close $fd
       dict create statusAndHeaders "HTTP/1.1 200 OK\nConnection: close\nContent-Type: image/jpeg\nContent-Length: $fsize\n\n" body $body
     }} $im]
   }
   ```

   Go to http://whatever.local:4273/frame-image/ to see the camera's
   current field of view. Reposition your camera to cover your table.

1. Place the 4 AprilTags around your table. On the tabletop, run
   `./folk.tcl calibrate`. Wait.

1. You should see red triangles projected on each of your 4 tags. Then
   you're done! Run Folk! If not, rerun calibration until you do see a
   red triangle on each tag.

1. When you've successfully calibrated, start Folk back up with
   `./folk.tcl start`.

### Connect a keyboard

Follow [the instructions on this Folk wiki page](https://folk.computer/guides/keyboard)
to connect a new keyboard to your system.

### Bluetooth keyboards

Install `bluetoothctl`. Follow the instructions in
https://wiki.archlinux.org/title/bluetooth_keyboard to pair and trust
and connect.

(FIXME: Write down the Bluetooth MAC address of your keyboard. We'll
proceed as though it's "f4:73:35:93:7f:9d" (it's important that you
turn it into lowercase).)

### Potentially useful

Potentially useful for graphs: `graphviz`

Potentially useful:  `gdb`, `streamer`, `cec-utils`,
`file`, `strace`

Potentially useful: add `folk-WHATEVER` shortcut to your laptop `~/.ssh/config`:
```
Host folk-WHATEVER
     HostName folk-WHATEVER.local
     User folk
```

Potentially useful: `journalctl -f -u folk` to see log of folk service

For audio:
https://askubuntu.com/questions/1349221/which-packages-should-be-installed-to-have-sound-output-working-on-minimal-ubunt

### HDMI No signal on Pi 4

Edit /boot/cmdline.txt https://github.com/raspberrypi/firmware/issues/1647#issuecomment-971500256
(HDMI-A-1 or HDMI-A-2 depending on which port)

### Ubuntu Server boots slowly

https://askubuntu.com/questions/1321443/very-long-startup-time-on-ubuntu-server-network-configuration
(add `optional: true` to all netplan interfaces)

## Troubleshooting

### Why is my camera slow (why is tracking janky or laggy, why is camera time high)

#### Check that camera is plugged into a USB3 port

#### Turn off autoexposure and autofocus

for example, install `v4l-utils` and:

```
v4l2-ctl -c auto_exposure=1
v4l2-ctl -c focus_automatic_continuous=0
v4l2-ctl -c white_balance_automatic=0
```

### Tcl troubleshooting

You can build Tcl with `TCL_MEM_DEBUG`. Download Tcl source code. (On
Mac, _do not_ go to the macosx/ subdir; go to the unix/ subdir.) Do
`./configure --enable-symbols=all`, do `make`, `make install`

## License

Folk is available under the Apache 2.0 license. See the [LICENSE](LICENSE) file
for more information.

## Language reference

Folk is built around Tcl. We don't add any additional syntax or
preprocessing to the basic Tcl language; all our 'language constructs'
like `When` and `Wish` are really just plain Tcl functions that we've
created. Therefore, it will eventually be useful for you to know
[basic](http://antirez.com/articoli/tclmisunderstood.html) [Tcl
syntax](https://www.ee.columbia.edu/~shane/projects/sensornet/part1.pdf).

These are all implemented in `main.tcl`. For most things, you'll
probably only need `Wish`, `Claim`, `When`, and maybe `Commit`.

### Wish and Claim

```
Wish $this is labelled "Hello, world!"
```

```
Claim $this is cool
Claim Omar is cool
```

### When

```
When /actor/ is cool {
   Wish $this is labelled "$actor seems pretty cool"
   Wish $actor is outlined red
}
```

The inside block (body) of the `When` gets executed for each claim
that is being made that it matches. It will get reactively rerun
whenever a new matching claim is introduced.

Any wishes/claims you make in the body will get automatically revoked
if the claim that the `When` was matching is revoked. (so if Omar stops
being cool, the downstream label `Omar seems pretty cool` will go away
automatically)

The `/actor/` in the `When` binds the variable `actor` to whatever is
at that position in the statement.

It's like variables in Datalog, or parentheses in regular expressions.

#### Non-capturing

`/someone/`, `/something/`, `/anyone/`, `/anything/` are special cases
if you want a wildcard that _does not bind_ (you don't care about the
value, like non-capturing groups `(?:)` in regex), so you don't get access
to `$someone` or `$something` inside the When.

#### Negation

`/nobody/`, `/nothing/` invert the polarity of the match, so it'll run
only when no statements exist that it would match.

This When will stop labelling if someone does `Claim Omar is cool`:

```
When /nobody/ is cool {
   Wish $this is labelled "nobody is cool"
}
```

#### `&` joins

You can match multiple patterns at once:

```
Claim Omar is cool
Claim Omar is a person with 2 legs
When /x/ is cool & /x/ is a person with /n/ legs {
   Wish $this is labelled "$x is a cool person with $n legs"
}
```

Notice that `x` here will have to be the same in both arms of the
match.

You can join as many patterns as you want, separated by `&`.

If you want to break your `When` onto multiple lines, remember to
terminate each line with a `\` so you can continue onto the next
line:

```
When /x/ is cool & \
    /x/ is a person with /n/ legs {
  Wish $this is labelled "$x is a cool person with $n legs"
}
```

### Collecting matches

```
When the collected matches for [list /actor/ is cool] are /matches/ {
   Wish $this is labelled [join $matches "\n"]
}
```

This gets you an array of all matches for the pattern `/actor/ is
cool`.

(We use the Tcl `list` function to construct a pattern as a
first-class object. You can use `&` joins in that pattern as
well.)

### Commit

Experimental: `Commit` is used to register claims that will stick
around until you do another `Commit`. You can use this to create the
equivalent of 'variables', stateful statements.

```
Commit { Claim $this has a ball at x 100 y 100 }

When $this has a ball at x /x/ y /y/ {
    puts "ball at $x $y"
    After 10 milliseconds {
        Commit { Claim $this has a ball at x $x y [expr {$y+1}] }
        if {$y > 115} { set ::done true }
    }
}
```

`Commit` will overwrite all statements made by the previous `Commit`
(scoped to the current `$this`).

**Notice that you should scope your claim: it's `$this has a ball`, not `there
is a ball`, so different programs with different values of `$this`
will not stomp over each other.** Not scoping your claims will bite
you once you print your program and have both virtual & printed
instances of your program running.

If you want multiple state atoms, you can also provide a key -- you
can be like

```
Commit ball position {
  Claim $this has a ball at blahblah
}
```

and then future commits with that key, `ball position`, will
overwrite this statement but not override different commits with
different keys

(there's currently no way to overwrite state from other pages, but we
could probably add a way to provide an absolute key that would allow
that if it was useful.)

### Every time

Experimental: `Every time` works almost like `When`, but it's used to
commit when an 'event' happens without causing a reaction cascade.

**You can't make Claims, Whens, or Wishes inside an `Every time`
block. You can only Commit.**

Example:

```
Commit { Claim $this has seen 0 boops }

Every time there is a boop & $this has seen /n/ boops {
  Commit { Claim $this has seen [expr {$n + 1}] boops }
}
```

If you had used `When` here, it wouldn't terminate, since the new
`$this has seen n+1 boops` commit would cause the `When` to retrigger,
resulting in a `$this has seen n+2 boops` commit, then another
retrigger, and so on.

`Every time`, in contrast, will 'only react once' to the boop; nothing
in its body will run again unless the boop goes away and an entirely
new boop appears.

### Animation

#### Getting time

Get the global clock time with:

```
When the clock time is /t/ {
  Wish $this is labelled $t
}
```

Use it in an animation:

```
When the clock time is /t/ {
  Wish $this draws a circle with offset [list [expr {sin($t) * 50}] 0]
}
```

### You usually won't need these

#### When when

Lets you create statements only on demand, when someone is looking for
that statement.

```
When /thing/ is cool {
    Wish $this is labelled "$thing is cool"
}
When when /personVar/ is cool /lambda/ with environment /e/ {
    Claim Folk is cool
}
```

#### On and Start

FIXME: General note: the `On` and `Start` blocks are used for weird
non-reactive behavior. Need to fill this out more.

##### Start process

```
Start process A {
  while true {
    puts "Hello! Another second has passed"
    exec sleep 1
  }
}
```

##### On unmatch

You should _not_ use `When`, `Claim`, or `Wish` directly inside an
`On unmatch` block; those only make sense inside a normal reactive
context.

```
set pid [exec python3]
On unmatch {
    kill $pid
}
```


#### Non-capturing

You can disable capturing of lexical context around a When with the
`(non-capturing)` flag.

This is mostly to help runtime performance if a When is declared
somewhere that has a lot of stuff in scope at declaration time.

```
set foo 3
When (non-capturing) /p/ is cool {
   Claim $p is awesome
   # can't access $foo from in here
}
```

#### Assert and Retract

General note: `Assert` and `Retract` are used for weird non-reactive
behavior.

You should generally _not_ use `Assert` and `Retract` inside a `When`
block. Use `Claim`, `Wish`, and `When` instead.

## Tcl for JavaScripters

JS:
```
let names = ["64", "GameCube", "Wii", "Switch"];
names = names.map(name => `Nintendo ${name}`);
console.log(names);

function add(a, b) { return a + b; }
const numbers = [1, 2];
console.log(add(...numbers));
```

Tcl:
```
set names [list 64 GameCube Wii Switch]
set names [lmap name $names {expr {"Nintendo $name"}}]
puts $names

proc add {a b} { expr {$a + $b} }
set numbers [list 1 2]
puts [add {*}$numbers]
```

## Style guide

### Tcl code vs. virtual programs vs. printed programs

In general, avoid adding new .tcl files to the Git repo. Pure Tcl
libraries are an antipattern; we should only need them for the hard
core of the system.

Most new code (both libraries and applications) should be virtual
programs (which ilve as .folk files in the virtual-programs/
subfolder) or printed programs.

### Folk 

- Use complete sentences when you word your claims and wishes.

  Bad: `Claim $this firstName Omar`

  Good: `Claim $this has first name Omar`

- Scope using `$this` where appropriate to prevent weird global
  interactions

  Bad: `Claim the value is 3`

  Good: `Claim $this has value 3`

- Style for joins across multiple lines -- use `&\` and align on the
  first token after `When`:

  ```
  When the fox is out &\
       the label is "Hello" &\
       everything seems good {
    ...
  }
  ```

### Tcl

#### fn

Use `fn` instead of `proc` to get a lexically captured command.

#### Error handling

Use `try` (and `on error`) in new code. Avoid using `catch`; it's
older and easier to get wrong.

#### apply

Use `apply` instead of `subst` to construct lambdas/code blocks,
except for one-liners (where you can use `list`)

#### Tcl datatypes

Create a namespace for your datatype that is an ensemble command with
operations on that datatype.

(Examples: `statement`, `c`, `region`, `point`, `image`)

Call the constructor `create`, as in `dict create` and `statement
create`.

#### Singletons

Capitalized namespace, like `Statements`.