Commit 7c71061b authored by Philip Levis's avatar Philip Levis
Browse files

Merge branch 'master' of code.stanford.edu:plevis/ee185

parents 18cf9813 4dc59eae
......@@ -3,18 +3,9 @@
This directory stores all of FLIGHT's software.
The directory structure:
- firmware: code that runs on a FractalFlyer, providing a Python interface over a flash drive and USB tty
- FlightGui: [Processing](https://processing.org) sketches for the FLIGHT GUI.
- scripts: python scripts that run on the firmware
- simulator: a graphical simulator of FLIGHT, which you can program just like the real installation (using Python)
- ui: The user interface to controlling FLIGHT both in real-time and using predefined playlists
- [firmware](firmware): code that runs on a FractalFlyer, providing a Python interface over a flash drive and USB tty
- [FlightGui](FlightGui): [Processing](https://processing.org) sketches for the FLIGHT GUI.
- [scripts](scripts): python scripts that run on the firmware
- [simulator](simulator): a graphical simulator of FLIGHT, which you can program just like the real installation (using Python)
- [ui](ui): The user interface to controlling FLIGHT both in real-time and using predefined playlists
Simulator (High Level)
The simulator consists of a four step pipeline; first, it reads generated
python commands from the Engine. Then, it spawns the desired number of
processes (corresponding to the number of fliers being invoked in the model)
and starts a python interpreter. It opens a socket to the main simulator
process through a C binding (which will eventually be responsible for generating and
updating the simulated graphical interface) and transmits the commands, updating the
state of the simulator, on a per-flier basis.
# FLIGHT: Packard Interactive Light Sculpture Project
# FLIGHT Simulator
This directory stores source code for a graphical FLIGHT simulator. The simulator
appears exactly as the art installation does, like 76 CircuitPython instances. You
can program and visualize how they look using a high-quality 3D visualization.
The FLIGHT simulator is a Linux program that appears to be the FLIGHT
installation and responds to scripting commands. It is structured as
follows:
The high level flow of the simulator is summarized in this diagram:
(https://code.stanford.edu/plevis/ee185/-/blob/master/software/simulator/SimulatorFlow.png)
- [fsimulator](fsimulator): the main executable for running the simulator.
- [graphics](graphics): the graphics library for displaying the simulated
state of FLIGHT.
- [client](client): a simple test client for interacting with the simulator.
The intended use of the simulator is to test and visualize what the
[FlightGui](../FlightGui) UI is doing. It allows you to test its
output without requiring a full FLIGHT installation. This lets you
test whether the UI is producing correct scripts and debug more easily.
#PYTHON API FORMAT
## Operation
Normally, each Fractal Flyer in FLIGHT is plugged into a computer with
USB. Each Flyer appears to the computer in two ways, as a serial port
presenting a Python read-eval-print-loop and as a removeable USB drive
that you can store Python code on.
The FLIGHT simulator recreates this behavior. When it boots, it
creates 76 entries in the local file system (named FIFOs) that act
like serial ports. These simulated serial ports are connected to
Python interpreters that provide the same API as a Fractal Flyer (see
below). The simulator also creates 76 directories that you can copy
Python code into, code which is accessible to the Python
interpreters. Just like the Fractal Flyers, if you copy new code to
the directory, the Python interpreter reboots: the simulator registers
for changes to the directory with `inotify(7)` and restarts the
corresponding interpreter process when it receives a notification.
These 76 interpreter processes send commands to a server process over
a network socket, which simulates the lights and motion of the Fractal
Flyers and displays them graphically.
![The high level flow of the simulator is summarized in this diagram.](SimulatorFlow.png)
Currently, the simulation component of the simulator is complete but
the graphics are unfinished.
## Python API
For a Python API, I’d suggest these three functions:
Fractal Flyers provide 3 Python functions to control their lights and wings:
``` Python
wing_leds(LEFT|RIGHT, (r, g, b), (r, g, b,), (r, g, b,))
body_leds((r, g, b)
wing_angle(LEFT|RIGHT, angle)
```
Where r g and b are 0-255 and angle is -90.0 - 90.0.
The assumption is the LED changes are near-instantaneous. The angle change,
however, may take time. The FF is smart enough to know its current wing position
and velocity and just try to move to the most recent angle command. So, suppose
the left wing is at 0 and you call wing_angle(LEFT, -20),the left wing will
start to lower to -20. Suppose, when it is at -10, you call wing_angle(LEFT, -30).
It will keep on lowering until it reaches -30. If, when it is at -25, you call
wing_angle(LEFT, 10), it will slow and stop, then start moving to 10.
This will all occur on the firmware.
Splits API for simulator into two parts; the interpreter/data_processing side
(in interpreter/) and the client side/main simulator process (server.c)
This API assumes that the LED changes are near-instantaneous. The wing
angle change, however, may take time. Each Fractal Flyer has a local
control loop that moves the wings to the last specified position in a
stable way.
For example, suppose the left wing is at 0 and a script calls
`wing_angle(LEFT, -20)`. The left wing will start to lower to
-20. Suppose, when it is at -10, the script calls `wing_angle(LEFT,
-30)`. The wing will keep on lowering until it reaches -30. If, when
it is at -25, the script calls `wing_angle(LEFT, 10)`, it will slow
and stop, then start moving to 10. The firmware takes care of all of
this logic.
PROGRAMS = data_processor
all:: $(PROGRAMS)
CC = gcc
CPP = g++
CPPFLAGS = -I/usr/include/python3.6
LIBS = -lpython3.6m -lpthread
$(PROGRAMS): fsimulator
fsimulator: fsimulator.o flight_server.o
$(CPP) -o $@ $^ $(LIBS) $(LIBS)
%.o: %.cc
$(CPP) $(CPPFLAGS) -c $< -o $@
%.o: %.c
$(CC) -c $< -o $@
clean::
rm -f $(PROGRAMS) *.o *~
.PHONY: clean all
## Interpreter ##
Contains
- Data_processor.cc: Main process for reading from engine output, spawning
76 processes, and transferring data into the main simulator process.
# Flight Simulator
- simLib.c: The symbolic library containing C bindings for the engine's
python API.
This directory contains the main source code for the Flight simulator. To run
it, follow these steps:
1. Install the Python library for simulating a Fractal Flyer in a Python interpreter: `python3 setup.py install`. You may need to run this with `sudo`.
1. Compile the simulator: `make`.
1. Run the simulator: `./fsimulator`.
The simulator exposes a simple console interface.
- api.py: A local version of the python API for use in binding incoming
## Files
-fsimulator.cc: Main process for reading from engine output, spawning
76 processes, and transferring data into the main simulator process.
- simLib.c: The symbolic library containing C bindings for the engine's
python API.
- api.py: A local version of the python API for use in binding incoming
python commands to their simLib versions.
- server.cc: networking code that receives commands from the 76 Python interpreters.
To compile data_processor: g++ data_processor.cc -I/usr/include/python3.6 -lpython3.6m -o data_processor
To install simLib on your local machine: python3 setup.py install
To restart interpreter for fractal flyer type the idx of the FF and hit enter while running ./data_processor
To shutdown entire interpreter type in -1 and hit enter
### fsimulator.cc
Data_processor.cc:
This file contains the main code of the simulator which creates all 76 flyer emulations, runs the interpreters, and runs the server side implementation. From here the process is able to send signals to shutdown or restart any interpreter and manage the entire simulation. It creates the server followed by the flyer emulations. Each emulation creates a FIFO. All the FIFO are stored at /tmp/fractal_flyers/flyeri where i is the index of the fractal flyer. If directory /tmp/fractal_flyers does not exist, it is created. Both of these file structures are destroyed when the process is closed or crashes. After creating the FIFOS the process then runs on an interpreter on a new process which reads from the FIFO and runs the commands. Before this is done, a set of initalization python commands are run which connect to the server and communicate information about the fractal flyer. The commands later sent on that flyer therefore have context that they are sent by a specific fractal flyer. If the fractal flyer is in a while loop, the interpreter is closed and reopened. The entire python environment and objects are destroyed and the interpreter is reopened, reconnects, and is then able again to read commands from the FIFO. While the interpreters are running, the main process is waiting on user input. If the user types in -1 or ctrl-C a signal is sent to kill all of the processes, close the file structures, and cleanly close the program. If the user types in any positive number, that flyer index is restarted. This is intended to resolve any issues with inifinite while loops which can stall an interpreter indefinetly.
......
......@@ -8,7 +8,10 @@
#include <poll.h>
#include <stdbool.h>
#include <mutex>
#define PORT 4040
#include "flight_server.h"
#define PORT 0xf100
#define NUM_FLYERS 76
#define BODYLED 1
#define WINGLED 2
......@@ -16,31 +19,22 @@
#define LEFT 1
#define RIGHT 0
struct rgb{
int red;
int green;
int blue;
};
struct wing{
struct rgb node1;
struct rgb node2;
struct rgb node3;
double angle;
};
struct flyerInfo{
int number;
struct rgb body;
struct wing leftWing;
struct wing rightWing;
};
struct flyerInfo flyerStates[NUM_FLYERS] = {0};
struct flight_flyer flyerStates[NUM_FLYERS];
std::mutex flyerLock;
/* Runs main serverCode which listens to all
* the fractal flyers and creates a singular
* model of the state of the fractal flyers
*/
int serverCode(){
int flight_server_num_flyers() {
return NUM_FLYERS;
}
struct flight_flyer* flight_server_get_flyer(int which) {
if (which < 0 || which >= NUM_FLYERS) {
return NULL;
} else {
return &(flyerStates[which]);
}
}
int flight_server_start(){
int server_fd, valread;
struct sockaddr_in address;
......@@ -161,7 +155,7 @@ int serverCode(){
break;
}
char message_recieved[1024] = {0};
char *message_send = "Hello from server!!";
const char *message_send = "Hello from server!!";
//close directory when you cant read anything
valread = read( fds[i].fd , message_recieved, 1024);
if(valread <= 0){
......@@ -176,14 +170,14 @@ int serverCode(){
flyerStates[temploc].number = temploc;
change = true;
if(tempCommand == BODYLED){
struct rgb rgbTemp;
struct flight_rgb rgbTemp;
sscanf(tempRest,"%d,%d,%d",&rgbTemp.red,&rgbTemp.green,&rgbTemp.blue);
flyerStates[temploc].body = rgbTemp;
}
else if(tempCommand == WINGLED){
struct rgb rgbNode1Temp;
struct rgb rgbNode2Temp;
struct rgb rgbNode3Temp;
struct flight_rgb rgbNode1Temp;
struct flight_rgb rgbNode2Temp;
struct flight_rgb rgbNode3Temp;
bool direction;
sscanf(tempRest,"%d:%d,%d,%d:%d,%d,%d:%d,%d,%d",
(int*)&direction,&rgbNode1Temp.red,&rgbNode1Temp.green,
......@@ -238,4 +232,4 @@ int serverCode(){
}
return 0;
}
\ No newline at end of file
}
#ifndef FLIGHT_SERVER_H
#define FLIGHT_SERVER_H
struct flight_rgb {
int red;
int green;
int blue;
};
struct flight_wing {
struct flight_rgb node1;
struct flight_rgb node2;
struct flight_rgb node3;
double angle;
};
struct flight_flyer {
int number;
struct flight_rgb body;
struct flight_wing leftWing;
struct flight_wing rightWing;
};
/*
* Starts the network server, which listens to commands from all
* of the Flyer Python interpreter processes and incorporates them
* into the current state of FLIGHT.
*/
int flight_server_start();
/* Returns the number of Fractal Flyers in the simulation. */
int flight_server_num_flyers();
/* Returns the state of a particular Fractal Flyer. Returns NULL if
* which is not a valid Fractal Flyer. */
struct flight_flyer* flight_server_get_flyer(int which);
#endif
......@@ -25,35 +25,33 @@
#include <sys/wait.h>
#include <mutex>
#include <sys/prctl.h>
#include "./../networking/server.c"
#include <thread>
#include "flight_server.h"
using namespace std;
#define NUM_FF NUM_FLYERS //Change to 76.
#define BUF_LENGTH 1000
/* https://stackoverflow.com/questions/23498654/read-from-a-named-pipe TODO(SD): For testing named pipe.
* and https://docs.python.org/2/extending/embedding.html for embedding python interpreter. */
struct flier {
struct flyer {
pid_t pid; //Pid associated with the flier's process.
};
char* directory = "/tmp/fractal_flyers";
static const char* directory = "/tmp/fractal_flyers";
flier fliers[NUM_FF];
pid_t server;
static int num_flyers;
static struct flyer* flyers;
static pid_t server;
/* Signal Handler run by server to shutdown any
* open FIFOS if not shutdown by parent on death
* of server
*/
static void end_process(int signo){
for (int i =0 ; i < NUM_FF; i++) {
static void end_process(int signo) {
for (int i =0 ; i < num_flyers; i++) {
char temparray[100];
sprintf(temparray,"%s/flyer%d",directory,i);
snprintf(temparray, 99, "%s/flyer%d",directory,i);
remove(temparray);
}
remove(directory);
......@@ -63,8 +61,7 @@ static void end_process(int signo){
/* Spawns each interpreter and runs code to
* define library functions in a local frame
*/
void spawn_process (int flier_num)
{
void spawn_process (int flier_num) {
prctl(PR_SET_PDEATHSIG, SIGKILL);
char initcode[256];
sprintf(initcode, "import simLib\n"
......@@ -96,9 +93,10 @@ void spawn_process (int flier_num)
}
/* Sets up a specific flier struct with its own named pipe. */
void create_fifo (int argc, char *argv[], int flier_num, struct flier &new_flier)
{
if(stat(directory, NULL)){
void create_fifo (int argc, char *argv[], int flier_num, struct flyer &new_flier)
{
struct stat statbuf;
if (stat(directory, &statbuf)) {
int temp = mkdir(directory,0777);
}
char temparray[100];
......@@ -106,14 +104,14 @@ void create_fifo (int argc, char *argv[], int flier_num, struct flier &new_flier
int fifo_ret = mkfifo(temparray, 0666);
}
struct flier spawn_new_flier(int argc, char *argv[], int flier_num)
struct flyer spawn_new_flier(int argc, char *argv[], int flyer_num)
{
struct flier new_flier;
create_fifo (argc, argv, flier_num, new_flier);
new_flier.pid = fork();
if(new_flier.pid == 0)
spawn_process (flier_num);
return new_flier;
struct flyer new_flyer;
create_fifo (argc, argv, flyer_num, new_flyer);
new_flyer.pid = fork();
if(new_flyer.pid == 0)
spawn_process (flyer_num);
return new_flyer;
}
/* Runs server to handle all the flyer requests
......@@ -121,9 +119,9 @@ struct flier spawn_new_flier(int argc, char *argv[], int flier_num)
pid_t spawn_server(){
pid_t pid = fork();
if(pid == 0){
signal(SIGTERM,end_process);
signal(SIGTERM, end_process);
prctl(PR_SET_PDEATHSIG, SIGTERM); //runs signal on death of parent
std::thread server (serverCode);
std::thread server (flight_server_start);
//supposed to call the simulator which has shared access to
//flyerStates and flyerLock
//std::thread sim (simCode);
......@@ -137,23 +135,23 @@ pid_t spawn_server(){
void create_data_pipeline(int argc, char *argv[])
{
server = spawn_server();
for (int i =0 ; i < NUM_FF; i++) {
fliers[i] = spawn_new_flier(argc, argv, i);
for (int i =0 ; i < num_flyers; i++) {
flyers[i] = spawn_new_flier(argc, argv, i);
}
}
/* Signal handler for main thread to kill all
* children and close the open FIFOs
*/
static void kill_all_processes(int signo){
for (int i =0 ; i < NUM_FF; i++) {
kill(fliers[i].pid,SIGKILL);
waitpid(fliers[i].pid,NULL,0);
for (int i =0 ; i < num_flyers; i++) {
kill(flyers[i].pid, SIGKILL);
waitpid(flyers[i].pid, NULL, 0);
char temparray[100];
sprintf(temparray,"%s/flyer%d",directory,i);
sprintf(temparray,"%s/flyer%d", directory, i);
remove(temparray);
}
kill(server,SIGKILL);
waitpid(server,NULL,0);
kill(server, SIGKILL);
waitpid(server, NULL, 0);
remove(directory);
exit(0);
}
......@@ -166,6 +164,9 @@ static void kill_all_processes(int signo){
//TODO(SD): Eventually define an init directory to pass into the process.
int main(int argc, char *argv[])
{
num_flyers = flight_server_num_flyers();
flyers = (struct flyer*)malloc(sizeof(struct flyer) * num_flyers);
create_data_pipeline(argc, argv);
signal(SIGINT,kill_all_processes);
signal(SIGTERM,kill_all_processes);
......@@ -179,22 +180,27 @@ int main(int argc, char *argv[])
cout << "k \tkills all processes" << endl
<< "r id \trestart fractal flyer with id" << endl
<< "h \thelp" << endl;
while(1){
while (1) {
scanf("%c",&command);
if(command == 'k'){
if (command == 'k') {
kill_all_processes(-1);
}else if(command == 'r'){
scanf(" %d",&flyer);
if(flyer >= NUM_FF){
printf("Invalid FF id\n");
} else if(command == 'r') {
int count = scanf(" %d",&flyer);
if (count != 1) {
printf("usage: r ID\n");
continue;
} else if (flyer >= num_flyers || flyer < 0) {
printf("Invalid flyer id: %d\n", flyer);
continue;
}
kill(fliers[flyer].pid,SIGKILL);
fliers[flyer].pid = fork();
if(fliers[flyer].pid == 0){
} else {
printf("Restarting flyer %d\n", flyer);
kill(flyers[flyer].pid,SIGKILL);
flyers[flyer].pid = fork();
if (flyers[flyer].pid == 0) {
spawn_process(flyer);
}
}else if(command == 'h'){
}
}
} else if(command == 'h') {
cout << "k \tkills all processes" << endl;
cout << "r id \trestart fractal flyer with id" << endl;
cout << "h \thelp" << endl;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment