2022年8月6日 星期六

測試dart 的 c interop 並繪圖

//main.dart
import 'package:flutter/material.dart';
import 'draw2d.dart';
void main( ) {
  runApp(new MyApp( ));
}
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark( ),
      home: HomePage( ),
      title:"draw2d",// id, it's not title name!
    );
  }
}
class HomePage extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Draw2D(MediaQuery.of(context).size)  
    );
  }
}

// draw2d.dart
import 'dart:ffi'  hide Size;// to use Size in dart:ui
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart'  hide TextStyle;// to use TextStyle in dart:ui
import "package:ffi/ffi.dart";
import "dart:async";
import "dart:isolate";
class Draw2D extends StatefulWidget {
  final Size bodySize;
  Draw2D(Size? size): bodySize = size ?? Size(400, 400);
  @override _Draw2DState createState( ) => _Draw2DState( );
}
class _Draw2DState extends State<Draw2D> {
  List<double> figure = [ ];
  final iso = ReceivePort( );
  final dataOffset = 16;
  static Future<void> bgIsolate(SendPort port) async { // isolate function must be static
    final tag = Isolate.current.debugName.toString( );
    final tagLog= (_)=> print("${DateTime.now( )}(${tag}) $_");
    final libso = DynamicLibrary.open("lib/libnative.so");// load share library
    final sttyReady = libso.lookupFunction<Bool Function( ),
        bool Function ( )
      >("ready");        
    final sopen = libso.lookupFunction<Int32 Function(Pointer<Utf8>, Int32),
        int Function(Pointer<Utf8>, int)
      >("sopen");
    final sttyOpen = ([String dev = "/dev/ttyACM0", baud = 250000]) {
      final _str = dev.toNativeUtf8( );
      sopen(_str, baud);
      malloc.free(_str);
    };
    final swrite = libso.lookupFunction<Int32 Function(Pointer<Utf8>),
      int Function(Pointer<Utf8>)
    >("swrite");    
    final sttyWrite = (String str) {
      final _str = str.toNativeUtf8( );
      swrite(_str);
      malloc.free(_str);
    };
    final sttyPoll = libso.lookupFunction<Int32 Function( ),
        int Function ( )
      >("spoll");    
    final sttyBuffer = libso.lookupFunction<Pointer<Uint8> Function( ),
        Pointer<Uint8> Function ( )
      >("adcBuffer");
    final portSendADC = ( ) {
      final n = sttyPoll( );
      if (n > 0) {// make sure null safety
        port.send(sttyBuffer( ).asTypedList(n));
      }
    };
    tagLog("debugName is $tag\n");  
    if (!sttyReady( )) sttyOpen("/dev/ttyACM0");

    if (sttyReady( )) {
      sttyWrite("fson\n");// command to send ADC data
      while (true) {// event loop
        await Future.delayed(Duration(milliseconds: 0));
        portSendADC( );
      }
      sttyWrite("fsoff\n");// command to turn off ADC
    }
    Isolate.exit(port, null);// isolate bgChild exit and return a null back.
  }
  @override void dispose( ) {
    iso.close( );
    super.dispose( );
  }
  @override void initState( ) {
    super.initState( );
    final mainLog = (_) => print("${DateTime.now( )}(${Isolate.current.debugName}) $_");
    mainLog("Future<Isolate> attach sendPort");
    figure = List<double>.filled(widget.bodySize.width.toInt( ), 0);
    final flen = figure.length;
    for (int i = 0; i < flen; i ++) {// normalize f(θ) =  (1 + sin(θ)) / 2;
      figure[i] = (1 - sin(2 * 3.14159 * i / flen)) / 2;// normallize, mirror to speed up CavasDraw.paint
    }
    Isolate.spawn(bgIsolate, iso.sendPort, debugName: "bgChild");
    iso.listen((adcData) {
      if (adcData == null) {
        iso.close( );
      } else {
        int i = 0;
        for (final adc in adcData) {
          if (i < dataOffset) figure[i] = 0.5;// header, fill base point 128 / 256 = 0.5
          else if (i < flen)  figure[i] = adc / 256.0;// normallize adc data, mirror already.
          i ++;// next location
        }
        setState(( ){ });
      }
    });
  }
  @override Widget build(BuildContext context) => Listener (
    onPointerSignal: (evt) => setState(( ) {
      // print(evt);
    }),
    onPointerHover: (evt)  => setState(( ) {
      // print(evt);
    }),
    child: CustomPaint(
      painter: CavasDraw(widget.bodySize, figure),
      size: widget.bodySize,
    )
  );
}
class CavasDraw extends CustomPainter {
  final Size drawSize;
  final List<double> figure;// normalize/mirror figure [0 ~ 1]
  final pen     = Paint( );
  final outline = Path( );
  final dx, cx, cy, w, h;
  late Offset east, west, south, north, center;    
  Paragraph sentence(String str, [double? sz, Color? c]) {
    final font = ParagraphBuilder(ParagraphStyle(fontSize: sz ?? 24))..
                  pushStyle(TextStyle(color: c ?? Colors.white))..
                  addText(str);
    return font.build( ).. layout(ParagraphConstraints(width: drawSize.width));
  }
  CavasDraw(this.drawSize, this.figure):
    dx    = drawSize.width  / figure.length,
    cx    = drawSize.width  / 2,
    cy    = drawSize.height / 2,
    w     = drawSize.width,
    h     = drawSize.height {
    pen.style = PaintingStyle.stroke;
    center = Offset(cx, cy);
    east   = Offset(w - 1, cy);
    west   = Offset(0, cy);
    south  = Offset(cx, h - 1);
    north  = Offset(cx, 0);
    outline.moveTo(0, 0);
    outline.lineTo(0, h - 1);
    outline.lineTo(w - 1, h - 1);
    outline.lineTo(w - 1, 0);
    outline.lineTo(0, 0);
    outline.close( );
  }
  @override bool shouldRepaint(CavasDraw old) => true;
  @override void paint(Canvas canvas, Size size) {
    pen.strokeWidth = 1;
    pen.color = Colors.green;
    double x = 0.0;
    Offset lastone = Offset(x, cy);// base point
    for (final y in figure) {// draw the figure
      final fxy = Offset(x, h * y);
      canvas.drawLine(lastone, fxy, pen);
      lastone = fxy;
      x += dx;
    }
    pen.color = Colors.red;
    canvas.drawLine(north, south, pen);
    canvas.drawLine(west, east, pen);
    canvas.drawParagraph(sentence("觸發點0"), west);// draw text
    pen.strokeWidth = 8;
    pen.color = Colors.grey;
    canvas.drawPath(outline, pen);
  }
}

#CMakeLists.txt
cmake_minimum_required(VERSION 3.7)
project(native)
add_library(native SHARED
    native.cpp
 )

//native.h
#ifdef __cplusplus
   extern "C" {
#endif
#ifndef Native_H
#define Native_H
bool    ready( );// device ready
int     sopen(char *buffer, int baud);
int     spoll( );// number of data available in serial port
int     swrite(char *buffer);// serial port write c_str
unsigned char *adcBuffer( );
#endif
#ifdef __cplusplus
   }
#endif
   
//native.cpp
#include "UBitSerial2.h"
#include "native.h"
static UBitSerial   serialPort = UBitSerial( );
extern "C" { // export as C function
        bool ready( ) {
                return serialPort.ready( );
        }              
        int spoll( ) {
                return serialPort.adcPoll( );
        }
        int swrite(char *buffer){
                return serialPort.write(buffer);
        }
        int sopen(char *buffer, int baud) {
                return serialPort.sopen(buffer, baud);
        }        
        unsigned char *adcBuffer( ) {
                return serialPort.adcBuffer( );
        }        
}

//UbitSerial2.h
#ifndef UBitSerial2_H
#define UBitSerial2_H
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <asm/termbits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#define defaultBaudrate 250000
#define maxCountSPF 2048
#define dataOffset  16
class UBitSerial {
  private:
    int fd = 0;
  public:
    ~UBitSerial( ) {
      if (fd) close(fd);
      fd = 0;
    }
    int sopen(char *dev, int baud) {
      if (fd > 0) close(fd);
      int ttyFD = open(dev, O_RDWR | O_NONBLOCK);
      if (ttyFD > 0) {
        fd = ttyFD;
        struct termios2 uart2;
        ioctl(fd, TCGETS2, &uart2);
        uart2.c_iflag     = 0;
        uart2.c_oflag     = 0;
        uart2.c_lflag     = 0;
        uart2.c_cc[VMIN]  = 1;
        uart2.c_cc[VTIME] = 0;
        uart2.c_cflag     = CS8 | CREAD | CLOCAL;
        uart2.c_cflag    &= ~CBAUD;
        uart2.c_cflag    |= CBAUDEX;
        uart2.c_ispeed = baud;
        uart2.c_ospeed = baud;
        ioctl(fd, TCSETS2, &uart2);
        ::printf("%s baud=%d ready. serial port fd=%d\n", dev, baud, fd); // use global printf
      }
      return ttyFD;
    }
    UBitSerial(const char *dev = "/dev/ttyUSB0", int baud = defaultBaudrate) {   
      sopen((char *)dev, baud);
    }
    bool ready( ) { return fd > 0;  }
    unsigned char rawBuffer[2][maxCountSPF];// byte buffer
    int qSize = sizeof(rawBuffer)/sizeof(rawBuffer[0]);
    int head = 0;
    int tail = 0;
    int frameSync = 0;
    int nIndex  = 0;
    char chunkBuffer[1024];
    const int lastone = sizeof(chunkBuffer) - 1;
    int adcPoll( ) {
        if (fd > 0) {
        int fig_1  = 0;
        int chunk = 0;    
        unsigned char *fillBuffer = rawBuffer[head];// raw figure buffer
        while (true) {
          ioctl(fd, FIONREAD, &chunk);
          if (chunk <= 0) break;// poll until empty
          if (chunk > lastone) chunk = lastone;
          ::read(fd, chunkBuffer, chunk);
          chunkBuffer[chunk] = 0;// append EOS
          if (strstr(chunkBuffer, "stop!!!")) continue;
          
          for (int i = 0; i < chunk; i ++) {// check one by one          
              unsigned char figY = (unsigned char) chunkBuffer[i];
              switch (frameSync) {// state macine to detect frameSync 0xff 0x0 ...
               case 0: if (figY == 0xff) {
                              frameSync = 1;
                              fig_1 = 0xff;
                          }
                          break;
                  case 1: if ((figY & 0xf0) != 0 || fig_1 != 0xff) frameSync = 0;
                          else {
                              frameSync = 2;
                              nIndex = 0;
                              fillBuffer[nIndex ++] = 0xff;//insert 0xff at the begining
                          }
                          break;
                  default:break;
              }
              if (frameSync != 2) continue;// until frameSync == 2
              if (nIndex < dataOffset) fillBuffer[nIndex ++] = figY;
              else fillBuffer[nIndex ++]  = ~figY;
              if (nIndex == maxCountSPF) {
                frameSync = 0;
                int next = head + 1;
                if (next == qSize) next = 0;
                if (next != tail) head = next;// head to next rawBuffer
            }
          }
        }
      }
      return (head == tail) ? 0 : maxCountSPF;
    }
    unsigned char *adcBuffer( ) {// no copy
      if (adcPoll( ) > 0) {
        unsigned char *ptr = rawBuffer[tail];
        if (++ tail == qSize) tail = 0;
        return ptr;
      }
      return nullptr;
    }
    int write(char *buffer) {
      if (fd && buffer) return ::write(fd, buffer, strlen(buffer));// use global write      
      return 0;
    }
    int printf(const char *fmt, ...) {// todo: prevent size of strBuffer overflow.
      static char strBuffer[1024];// upto 1024 characters
      va_list parameters;
      va_start(parameters, fmt);
      vsprintf(strBuffer, fmt, parameters);
      va_end(parameters);
      return write(strBuffer);// this->write
    }
};
#endif
 

沒有留言:

張貼留言

使用 pcie 轉接器連接 nvme SSD

之前 AM4 主機板使用 pcie ssd, 但主機板故障了沒辦法上網, 只好翻出以前買的 FM2 舊主機板, 想辦法讓老主機復活, 但舊主機板沒有 nvme 的界面, 因此上網買了 pcie 轉接器用來連接 nvme ssd, 遺憾的是 grub2 bootloader 無法識...