1
0
LH 4 сар өмнө
parent
commit
6cfede5814

+ 2 - 1
Megatron-Bridge/src/main/java/de/nplusc/izc/MegatronBridge/CommandQueueItem.java

@@ -4,5 +4,6 @@ public class CommandQueueItem
 {
     public CommandType type;
     public int argument;
-    public String patharg;
+    public String extendedArg;
+    public byte[] streamData;
 }

+ 12 - 0
Megatron-Bridge/src/main/java/de/nplusc/izc/MegatronBridge/MegatronBridgeApplication.java

@@ -2,7 +2,10 @@ package de.nplusc.izc.MegatronBridge;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
 
 @SpringBootApplication
@@ -22,5 +25,14 @@ public class MegatronBridgeApplication {
 
 		SpringApplication.run(MegatronBridgeApplication.class, args);
 	}
+}
+@Configuration
+class OpenApiConfiguration implements WebMvcConfigurer
+{
 
+	@Override
+	public void addViewControllers(final ViewControllerRegistry registry) {
+		registry.addRedirectViewController("/", "swagger-ui/index.html");
+	}
 }
+

+ 70 - 2
Megatron-Bridge/src/main/java/de/nplusc/izc/MegatronBridge/MegatronController.java

@@ -4,44 +4,112 @@ import de.nplusc.izc.MegatronBridge.Models.CustomAudio;
 import de.nplusc.izc.MegatronBridge.Models.MovementActions;
 import de.nplusc.izc.MegatronBridge.Models.OnboardAudio;
 import de.nplusc.izc.MegatronBridge.Models.RawAction;
-import org.springframework.stereotype.Service;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
 @RestController
+@Tag(name = "base", description = "API module for the Megatron sculpture at the CC86 assembly at 38C3")
 public class MegatronController
 {
     @Autowired
     private MegatronService svc;
 
+
+    @Operation(summary = "Sets the volume level, range is from 0 to 8", description = "Sets the volume for the custom audio to the given level")
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "200", description = "successful operation"),
+            @ApiResponse(responseCode = "400", description = "Out of range value",content = @Content)}
+    )
     @PostMapping("/volume")
+    @ResponseBody
     public void setVolume(int volume)
     {
         svc.setVolume(volume);
     }
-    @PostMapping("/raw")
+
+    @Operation(summary = "Raw lowlevel command, debugging only", description = "Raw command sending, don't waste your time if you don't have the secret key")
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "200", description = "successful operation"),
+            @ApiResponse(responseCode = "401", description = "Not a chance!", content = @Content)}
+    )
+    @PostMapping("/debug")
+    @ResponseBody
     public void executeRaw(RawAction action)
     {
         svc.executeRaw(action);
     }
+
+    @Operation(summary = "Triggers one of the onboard audio snippets", description = "Plays one of the original builtin audio snippets, WARNING: LOUD!")
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "200", description = "successful operation"),
+            @ApiResponse(responseCode = "400", description = "Out of range value", content = @Content)}
+    )
     @PostMapping("/onboardAudio")
+    @ResponseBody
     public void playOnbardAudio(OnboardAudio audio)
     {
         svc.playOnbardAudio(audio);
     }
+
+    @Operation(summary = "Triggers one of the custom fixed audio snippets", description = "Plays one of the fixed custom audio snippets. Volume depends on configured volume")
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "200", description = "successful operation"),
+            @ApiResponse(responseCode = "400", description = "Out of range value", content = @Content)}
+    )
     @PostMapping("/customAudio")
+    @ResponseBody
     public void playCustomAudio(CustomAudio audio)
     {
         svc.playCustomAudio(audio);
     }
+    @Operation(summary = "Lists the valid custom audio snippets", description = "Lists the valid custom audio snippets")
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "200", description = "successful operation")
+    })
+    @GetMapping("/customAudio")
+    public String[] listAudio()
+    {
+        return svc.listAudio();
+    }
+
+    @Operation(summary = "Triggers the fixed movement sequences", description = "Triggers one of the fixed movement actions")
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "200", description = "successful operation"),
+            @ApiResponse(responseCode = "400", description = "Out of range value", content = @Content)}
+    )
     @PostMapping("/wiggle")
+
     public void wiggle(MovementActions action)
     {
         svc.wiggle(action);
     }
+
+    @Operation(summary = "toggles mouth wiggling state", description = "Plays one of the original builtin audio snippets, WARNING: LOUD!")
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "200", description = "successful operation"),
+            @ApiResponse(responseCode = "400", description = "Out of range value", content = @Content)}
+    )
     @PostMapping("/wiggleMouth")
     public void wiggleMouth()
     {
+        svc.wiggleMouth();
+    }
 
+
+
+    @Operation(summary = "send raw audio to be played", description = "plays raw 11khz 8bit signed PCM audio (max 60seconds)")
+    @PostMapping(value = "dynamicAudio",
+            consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    public void streamAudio(@RequestParam("file") MultipartFile file)
+    {
+        svc.streamAudio(file);
     }
     //TODO sent audio
 }

+ 84 - 29
Megatron-Bridge/src/main/java/de/nplusc/izc/MegatronBridge/MegatronService.java

@@ -4,7 +4,16 @@ import de.nplusc.izc.MegatronBridge.Models.CustomAudio;
 import de.nplusc.izc.MegatronBridge.Models.MovementActions;
 import de.nplusc.izc.MegatronBridge.Models.OnboardAudio;
 import de.nplusc.izc.MegatronBridge.Models.RawAction;
+import org.springframework.http.HttpStatus;
 import org.springframework.stereotype.Service;
+import org.springframework.util.ResourceUtils;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.io.*;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
 
 @Service
 public class MegatronService
@@ -21,21 +30,33 @@ public class MegatronService
         }
         else
         {
-            //validator error
+            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Out of Range");
         }
     }
     public void executeRaw(RawAction action)
     {
-        if(action.getPSK().equals(MegatronBridgeApplication.PSK))
+        boolean sus=false;
+        if(action.getpsk()==null)
+        {
+            sus=true;
+            System.out.println("it smells fishy");
+        }
+        if(action.getCommand()==null)
+        {
+            sus=true;
+            System.out.println("it smells fishy again");
+        }
+
+        if(!sus&&action.getpsk().equals(MegatronBridgeApplication.PSK))
         {
             CommandQueueItem itm = new CommandQueueItem();
             itm.type=CommandType.RawCommand;
-            itm.patharg=action.getCommand();
+            itm.extendedArg =action.getCommand();
             MegatronBridgeApplication.spammer.getQueue().add(itm);
         }
         else
         {
-            //"from __future__ import braces
+            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "from __future__ import braces");
         }
     }
     public void playOnbardAudio(OnboardAudio audio)
@@ -51,36 +72,25 @@ public class MegatronService
             case SND_BOOTUP -> 6;
         };
         CommandQueueItem itm = new CommandQueueItem();
-        itm.type=CommandType.CustomAudio;
+        itm.type=CommandType.OnboardAudio;
         itm.argument=internalId;
         MegatronBridgeApplication.spammer.getQueue().add(itm);
     }
+
+    public String[] listAudio()
+    {
+        return audioMapTable.stream().map((o)->o.ID()).toList().toArray(new String[audioMapTable.size()]);
+    }
+
     public void playCustomAudio(CustomAudio audio)
     {
-        int internalId = switch(audio.getTargetSample())
+        final String ref = audio.getTargetSample();
+        InternalAudioMapping mapping = audioMapTable.stream().filter(o->o.ID().equals(ref)).findFirst().orElseGet(()->null);
+        if(mapping==null)
         {
-            case ROGERROGER -> 0;
-            case R2D2_BEEPERY -> 1;
-            case JFO_SFX_MACHINEPOWERUP -> 2;
-            case JFO_AUTHENTICATED -> 3;
-            case JFO_CHARGED_AND_READY -> 4;
-            case JFO_CORE_PROGRAMMING_MODIFIED -> 5;
-            case JFO_RECALIBRATING -> 6;
-            case JFO_SCANNING -> 7;
-            case JFO_SYSTEM_REBOOTED -> 8;
-            case JFO_SYSTEM_MALFUNCTION -> 9;
-            case R2D2_DEMOTIVATED -> 10;
-            case GB_STARTUP_SOUND -> 11;
-            case WORMS_BITTEKOMMENGROSSERVOGEL -> 12;
-            case GLADOS_OHDUBISTES -> 19;
-            case GLADOS_NICHT_REINFALLEN -> 20;
-            case GLADOS_PLANB -> 21;
-            case GLADOS_SEID_IHR_DEFEKT -> 22;
-            case HALLELUJAH -> 23;
-            case P2TURRET_FAIL -> 24;
-            case WORMS_OEDE -> 25;
-            case OPTIMUSPRIME -> 26;
-        };
+            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "from __future__ import braces");
+        }
+        int internalId=mapping.index();
         if(audio.isWiggleMouth())
         {
             internalId|=0x1_00_00;
@@ -90,6 +100,7 @@ public class MegatronService
         itm.argument=internalId;
         MegatronBridgeApplication.spammer.getQueue().add(itm);
     }
+
     public void wiggle(MovementActions action)
     {
         int internalId = switch(action)
@@ -109,9 +120,53 @@ public class MegatronService
     }
     public void wiggleMouth()
     {
+        System.out.println("Mundwerk wiggler");
         CommandQueueItem itm = new CommandQueueItem();
         itm.type=CommandType.MundwerkToggle;
         MegatronBridgeApplication.spammer.getQueue().add(itm);
     }
-    //TODO sent audio
+
+    public void streamAudio(MultipartFile file)
+    {
+        if(file.getSize()>60*11000)
+        {
+            throw new ResponseStatusException(HttpStatus.PAYLOAD_TOO_LARGE, "forget it....");
+        }
+        try
+        {
+            CommandQueueItem itm = new CommandQueueItem();
+            itm.type=CommandType.StreamAudio;
+            byte[] tmp = file.getBytes();
+            itm.streamData= Arrays.copyOfRange(tmp,0,tmp.length&(~0b00111111)); //trim down to nearest 64 byte chunk
+            MegatronBridgeApplication.spammer.getQueue().add(itm);
+        } catch (IOException e)
+        {
+            e.printStackTrace();
+            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "oupps");
+        }
+    }
+
+    private List<InternalAudioMapping> audioMapTable = loadMappings();
+
+    private static List<InternalAudioMapping> loadMappings(){
+        LinkedList list = new LinkedList<>();
+        try (BufferedReader in = new BufferedReader(new FileReader(ResourceUtils.getFile("classpath:audiomapping.lst")));){
+
+            in.lines().forEach(line->
+            {
+                String[] linesplit = line.split(":");
+                int index = Integer.decode(linesplit[1]);
+                list.add(new InternalAudioMapping(linesplit[0],index));
+            });
+
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return list;
+    }
 }
+
+
+
+record InternalAudioMapping(String ID, int index)
+{}

+ 3 - 3
Megatron-Bridge/src/main/java/de/nplusc/izc/MegatronBridge/Models/CustomAudio.java

@@ -3,7 +3,7 @@ package de.nplusc.izc.MegatronBridge.Models;
 public class CustomAudio
 {
     private boolean wiggleMouth;
-    private CustomAudioNames targetSample;
+    private String targetSample;
 
     public boolean isWiggleMouth()
     {
@@ -15,12 +15,12 @@ public class CustomAudio
         this.wiggleMouth = wiggleMouth;
     }
 
-    public CustomAudioNames getTargetSample()
+    public String getTargetSample()
     {
         return targetSample;
     }
 
-    public void setTargetSample(CustomAudioNames targetSample)
+    public void setTargetSample(String targetSample)
     {
         this.targetSample = targetSample;
     }

+ 0 - 26
Megatron-Bridge/src/main/java/de/nplusc/izc/MegatronBridge/Models/CustomAudioNames.java

@@ -1,26 +0,0 @@
-package de.nplusc.izc.MegatronBridge.Models;
-
-public enum CustomAudioNames
-{
-    ROGERROGER,
-    R2D2_BEEPERY,
-    JFO_SFX_MACHINEPOWERUP,
-    JFO_AUTHENTICATED,
-    JFO_CHARGED_AND_READY,
-    JFO_CORE_PROGRAMMING_MODIFIED,
-    JFO_RECALIBRATING,
-    JFO_SCANNING,
-    JFO_SYSTEM_REBOOTED,
-    JFO_SYSTEM_MALFUNCTION,
-    R2D2_DEMOTIVATED,
-    GB_STARTUP_SOUND,
-    WORMS_BITTEKOMMENGROSSERVOGEL,
-    GLADOS_OHDUBISTES,
-    GLADOS_NICHT_REINFALLEN,
-    GLADOS_PLANB,
-    GLADOS_SEID_IHR_DEFEKT,
-    HALLELUJAH,
-    P2TURRET_FAIL,
-    WORMS_OEDE,
-    OPTIMUSPRIME,
-}

+ 5 - 5
Megatron-Bridge/src/main/java/de/nplusc/izc/MegatronBridge/Models/RawAction.java

@@ -3,7 +3,7 @@ package de.nplusc.izc.MegatronBridge.Models;
 public class RawAction
 {
     private String command;
-    private String PSK;
+    private String psk;
 
     public String getCommand()
     {
@@ -15,13 +15,13 @@ public class RawAction
         this.command = command;
     }
 
-    public String getPSK()
+    public String getpsk()
     {
-        return PSK;
+        return psk;
     }
 
-    public void setPSK(String PSK)
+    public void setpsk(String PSK)
     {
-        this.PSK = PSK;
+        this.psk = PSK;
     }
 }

+ 32 - 32
Megatron-Bridge/src/main/java/de/nplusc/izc/MegatronBridge/SerialSpammer.java

@@ -3,8 +3,8 @@ package de.nplusc.izc.MegatronBridge;
 
 import com.fazecast.jSerialComm.SerialPort;
 
-import javax.sql.rowset.serial.SQLOutputImpl;
 import java.io.*;
+import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.Queue;
 
@@ -23,7 +23,7 @@ public class SerialSpammer
     private NextChunkAction nextChunkReady = NextChunkAction.WAIT;
     private int waitCtr = 0;
     private Object workaround = new Object();
-
+    private int streampointer = 0;
     private Queue<CommandQueueItem> queue= new LinkedList<>();
     private boolean startedStreamPlayback = false;
 
@@ -33,7 +33,7 @@ public class SerialSpammer
     }
 
     private final char[] hex = "0123456789ABCDEF".toCharArray();
-    private RandomAccessFile streamfile = null;
+    private byte[] streamfile = null;
     public SerialSpammer(String comPortName)
     {
 
@@ -100,7 +100,7 @@ public class SerialSpammer
                 switch (itm.type)
                 {
                     case ServoAction:
-                        if(itm.argument>15&&itm.argument<0)
+                        if(itm.argument>15||itm.argument<0)
                         {
                             break;
                         }
@@ -116,7 +116,7 @@ public class SerialSpammer
                         }
                         break;
                     case OnboardAudio:
-                        if(itm.argument>15&&itm.argument<0)
+                        if(itm.argument>15||itm.argument<0)
                         {
                             break;
                         }
@@ -136,9 +136,16 @@ public class SerialSpammer
                         {
                             break;
                         }
+                        boolean wiggleMouth = false;
+                        if(itm.argument>0xFFFF)
+                        {
+                            wiggleMouth=true;
+                            itm.argument&=0xFFFF;
+                        }
+                        System.out.println(itm.argument);
                         LinkedList<Character> reverser = new LinkedList<>();
                         int arg = itm.argument;
-                        while(arg>15)
+                        while(arg>0)
                         {
                             reverser.add(hex[arg%16]);
                             arg>>=4;
@@ -154,8 +161,9 @@ public class SerialSpammer
                                     break;
                                 }
                                 o.write(c);
+                                System.out.println("CSEND="+c);
                             }
-                            o.write('v');
+                            o.write(wiggleMouth?'V':'v');
                         } catch (IOException e)
                         {
                             e.printStackTrace();
@@ -164,22 +172,14 @@ public class SerialSpammer
                         break;
 
                     case StreamAudio:
-                        String path = itm.patharg;
-                        System.out.println("Streaming: "+path);
-                        if(new File(path).exists())
+                        if(streamfile==null)
                         {
-                            try
+                            streamfile = itm.streamData;
+                            nextChunkReady=NextChunkAction.CONTINUE;
+                            synchronized (workaround)
                             {
-                                streamfile = new RandomAccessFile(path,"r");
-                                nextChunkReady=NextChunkAction.CONTINUE;
-                                synchronized (workaround)
-                                {
-                                    System.out.println("POKE");
-                                    workaround.notify();
-                                }
-                            } catch (FileNotFoundException e)
-                            {
-                                e.printStackTrace();
+                                System.out.println("POKE");
+                                workaround.notify();
                             }
                         }
                         break;
@@ -202,7 +202,7 @@ public class SerialSpammer
                     case RawCommand:
                         try
                         {
-                            for(char c:itm.patharg.toCharArray())
+                            for(char c:itm.extendedArg.toCharArray())
                             {
                                 o.write(c);
                             }
@@ -232,17 +232,16 @@ public class SerialSpammer
             if(streamfile!=null)
             {
                 try{
-                if(streamfile.getFilePointer()>=streamfile.length())
+                if(streampointer>=streamfile.length)
                 {
                     System.out.println("FINI");
                     System.err.println("FINI");
-                    streamfile.close();
                     streamfile=null;
                     startedStreamPlayback=false;
                 }
                 else
                 {
-                    if((!startedStreamPlayback)&&(streamfile.getFilePointer()==8192||streamfile.getFilePointer()==streamfile.length()))
+                    if((!startedStreamPlayback)&&(streampointer==8192||streampointer==streamfile.length))
                     {
                         System.out.println("PLAY");
                         o.write('|');
@@ -251,17 +250,17 @@ public class SerialSpammer
                     switch(nextChunkReady)
                     {
                         case REPEAT:
-                            System.out.println("RTRY=("+streamfile.getFilePointer()+")");
+                            System.out.println("RTRY=("+streampointer+")");
                             waitCtr=0;
-                            if(streamfile.getFilePointer()>=64)
+                            if(streampointer>=64)
                             {
-                                streamfile.seek(streamfile.getFilePointer()-64);
+                                streampointer-=64;
                             }
                         case CONTINUE:
                             waitCtr=0;
-                            System.out.println("STR=("+streamfile.getFilePointer()+")");
-                            byte[] readbfr = new byte[64];
-                            streamfile.read(readbfr);
+                            System.out.println("STR=("+streampointer+")");
+                            byte[] readbfr = Arrays.copyOfRange(streamfile,streampointer,streampointer+64);
+                            streampointer+=64;
                             o.write('>');
                             nextChunkReady=NextChunkAction.WAIT;
                             o.write(readbfr);
@@ -286,7 +285,8 @@ public class SerialSpammer
                 }
                 catch (IOException e)
                 {
-
+                    System.out.println("Zöinks");
+                    e.printStackTrace();
                 }
             }
         }

+ 1 - 1
Megatron-Bridge/src/main/resources/application.properties

@@ -1 +1 @@
-spring.application.name=MegatronBridge
+spring.application.name=MegatronBridge

+ 21 - 0
Megatron-Bridge/src/main/resources/audiomapping.lst

@@ -0,0 +1,21 @@
+ROGERROGER:0
+R2D2_BEEPERY:1
+JFO_SFX_MACHINEPOWERUP:2
+JFO_AUTHENTICATED:3
+JFO_CHARGED_AND_READY:4
+JFO_CORE_PROGRAMMING_MODIFIED:5
+JFO_RECALIBRATING:6
+JFO_SCANNING:7
+JFO_SYSTEM_REBOOTED:8
+JFO_SYSTEM_MALFUNCTION:9
+R2D2_DEMOTIVATED:10
+GB_STARTUP_SOUND:11
+WORMS_BITTEKOMMENGROSSERVOGEL:12
+GLADOS_OHDUBISTES:19
+GLADOS_NICHT_REINFALLEN:20
+GLADOS_PLANB:21
+GLADOS_SEID_IHR_DEFEKT:22
+HALLELUJAH:23
+P2TURRET_FAIL:24
+WORMS_OEDE:25
+OPTIMUSPRIME:26