• Hey! Just wanted to post an update, I had a go at implementing the suggestions yesterday evening & got something working - it catches even fast button presses but occasionally drops them which is something I'm looking into. I've attached some code, its still ugly POC code & I'm sure there are things I can do to make it run more efficiently :)

    //=======================================================================
    // MATRIX
    //=======================================================================
    const matrix = (rows, cols) => {
      let _scanning;
      let _onStopScanning;
      let _numberOfIdleScansBeforeStop;
      let _numberOfActiveKeys;
      let _numberOfIdleScans;
      let _keyStates;
      let _rowStates;
      let _cols = cols;
      let _rows = rows;
      
      const defaultKeyStates = new Array(_rows.length).fill(new Array(_cols.length).fill(0));
      // const resetKeyStates = () => _keyStates = defaultKeyStates;
    
      // Make this flexible for different rows/cols
      const resetKeyStates = () => _keyStates = [
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
      ];
      // Make this flexible for different rows/cols
      const resetRowStates = () => _rowStates = [ 0, 0, 0, 0 ];
      const resetNumberOfIdleScans = () => _numberOfIdleScans = 0;
      const resetNumberOfActiveKeys = () => _numberOfActiveKeys = 0;
      
      const getMatrixState = () => _keyStates;
      const getKeyState = (row, col) => _keyStates[row][col];
      
      const setPinModes = () => {
        "compiled";
        // @TODO: Can we set an array of pinmodes instead of iterating?
        _rows.forEach(row => pinMode(row, 'output'));
        _cols.forEach(col => pinMode(col, 'input_pulldown'));
      };
      
      const unsetPinModes = () => {
        "compiled";
        _rows.forEach(row => pinMode(row, 'input_pulldown'));
        _cols.forEach(col => pinMode(col, 'output'));
      };
      
      const setup = () => {
        "compiled";
        resetNumberOfIdleScans();
        resetNumberOfActiveKeys();
        resetKeyStates();
        resetRowStates(); 
      };
    
      const startScanning = (onChange, onStop, numberOfIdleScansBeforeStop, interval) => {
        "compiled";
        console.log("======================");
        setPinModes();
        _numberOfIdleScansBeforeStop = numberOfIdleScansBeforeStop;
        _onStopScanning = onStop;
        if (!_scanning) _scanning = setInterval(() => scanMatrix(onChange), interval || 1);
      };
      const stopScanning = () => {
        "compiled";
        resetNumberOfIdleScans();
        resetNumberOfActiveKeys();
        resetKeyStates();
        resetRowStates();
        unsetPinModes();
        if (_scanning) { clearInterval(_scanning); _scanning = undefined; }
        if (_onStopScanning) _onStopScanning();
      };
      const isScanning = () => !!_scanning;
      
      const updateNumberOfIdleScans = (activeKeys, callback) => {
        "compiled";
        if (activeKeys === 0) _numberOfIdleScans = _numberOfIdleScans + 1;
        callback();
      };
      const stopOnIdle = () => {
        "compiled";
        if (_numberOfIdleScansBeforeStop && (_numberOfIdleScans >= _numberOfIdleScansBeforeStop)) stopScanning();
      };
      
      const updateNumberOfActiveKeys = (isActive) => {
        "compiled";
        if (isActive) {_numberOfActiveKeys = _numberOfActiveKeys + 1;}
        else if (_numberOfActiveKeys > 0) {_numberOfActiveKeys = _numberOfActiveKeys - 1;}
      };
    
      const parseRowToString = (row) => {
        "compiled";
        return row.toString(2);
      };
    
      const detectChange = (rowState, currentRowIndex, onChange) => {
        "compiled";
        if (rowState !== _rowStates[currentRowIndex]) {
          _rowStates[currentRowIndex] = rowState;
          const parsedRow = parseRowToString(rowState);
    
          // @TODO: This feels inefficient
          for (let colIndex = 0; colIndex < parsedRow.length; colIndex++) {
            const col = Number(parsedRow[colIndex]);
            if (col !== _keyStates[currentRowIndex][colIndex]) {
              _keyStates[currentRowIndex][colIndex] = col;
              onChange(currentRowIndex, colIndex, col);
              updateNumberOfActiveKeys(col);
            }
          }
          
          
        }
      };
    
      const scanMatrix = (onChange) => {
        "compiled";
        _rows.forEach((currentRow, currentRowIndex) => {
          digitalWrite(currentRow, 1);
    
          detectChange(digitalRead(_cols), currentRowIndex, onChange);
    
          digitalWrite(currentRow, 0);
        });
        updateNumberOfIdleScans(_numberOfActiveKeys, stopOnIdle);
      };
      
      setup();
    
      return {
        getMatrixState: getMatrixState,
        getKeyState: getKeyState,
        startScanning: startScanning,
        stopScanning: stopScanning,
        isScanning: isScanning
      };
    };
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    //=======================================================================
    // USAGE
    //=======================================================================
    const ROWS = [D14, D15, D17, D30];
    const COLS = [D16, D18, D19, D20, D22, D25, D26, D27, D28, D29, D31];
    
    let _rowWatchers;
    
    const myMatrix = matrix(ROWS, COLS);
    
    const onMatrixChange = (currentRow, currentCol, isActive) => {
      // Keep track of key press/release duration
      // Classify as tap or hold
      
      // keyPress when a key is pressed, payload containing key row/column
      // keyRelease when a key is released, payload containing row/column, duration of press
      // keyTap when a key press/release cycle is < 200ms, payload containing row/column
      // keyHoldStart when a key has been pressed for > 200ms, payload containing row/column
      // keyHoldEnd fired when held key is released, payload containing row/column, duration of press
      console.log(currentRow, currentCol, isActive);
    };
    
    const onMatrixStop = () => {
      "compiled";
      enable();
    };
    
    const handleWatchTrigger = (e) => { 
      "compiled";
      unsetWatches(_rowWatchers);
      setColsLow(COLS);
      myMatrix.startScanning(onMatrixChange, onMatrixStop, 2, 1);
    };
    
    const setColsHigh = (cols) => {
      "compiled";
      cols.forEach(col => digitalWrite(col, 1));
    };
    const setColsLow = (cols) => {
      "compiled";
      cols.forEach(col => digitalWrite(col, 0));
    };
    const setWatches = (rows) => {
      "compiled";
      _rowWatchers = rows.map(row => setWatch((e) => handleWatchTrigger(e), row, { repeat:false, edge:'rising', debounce: 10 }));
    };
    
    // @TODO: Make this more resilient, this errors when it can't clear a watch
    const unsetWatches = (rowWatchers) => {
      "compiled";
      rowWatchers.forEach(rowWatcher => clearWatch(rowWatcher));
    };
    
    const enable = () => {
      "compiled";
      ROWS.forEach(row => pinMode(row, 'input_pulldown'));
      COLS.forEach(col => pinMode(col, 'output'));
      setColsHigh(COLS);
      setWatches(ROWS);
    };
    
    enable();
    
    
About

Avatar for consolenaut @consolenaut started