/*
-----------------------------------------------------------------------------
This source file is part of OpenSpace3D
For the latest info, see http://www.openspace3d.com
Copyright (c) 2012 I-maginer
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt
-----------------------------------------------------------------------------
*/
/*
Version : 1.0
First version : 03/14/2023
Author : Bourineau Bastien
*/
var iMaxPort = 12;;
var iDeviceBootTime = 150;; //some devices like arduino can take some time to initialize
var iAutoPortTimeOut = 500;;
var lMusics = ["Dadadadum" 0]::["Entertainer" 1]::["Prelude" 2]::["Ode" 3]::["Nyan" 4]::["Ringtone" 5]::["Funk" 6]::["Blues" 7]::["Birthday" 8]::["Wedding" 9]::["Funeral" 10]::["Punchline" 11]::["Baddy" 12]::["Chase" 13]::["BaDing" 14]::["Wawawawaa" 15]::["JumpUp" 16]::["JumpDown" 17]::["PowerUp" 18]::["PowerDown" 19]::nil;;
struct PlugMicrobit = [
MBIT_inst : PInstance,
MBIT_pCom : SerialIO,
MBIT_sPort : S,
MBIT_iSpeed : I,
MBIT_bParity : I,
MBIT_iByteSize : I,
MBIT_iStopBit : I,
MBIT_sLastChar : S,
MBIT_trmConnect : Timer,
MBIT_bAuto : I,
MBIT_iAutoPort : I,
MBIT_iAutoState : I,
MBIT_tAccel : [F F F F],
MBIT_tOrient : [F F F],
MBIT_tSensor : [F F F I],
MBIT_tPins : [I I I],
MBIT_bBtnA : I,
MBIT_bBtnB : I,
MBIT_bBtnL : I,
MBIT_bShake : I,
MBIT_iDir : [I I],
//matrix
MBIT_lData : [[I r1] r1],
MBIT_iNbFrames : I,
MBIT_iTick : I,
MBIT_iCurFrame : I,
MBIT_bReadEvent : I,
MBIT_bAccelEvent : I,
MBIT_bForceEvent : I,
MBIT_bOrientEvent : I,
MBIT_bDirEvent : I,
MBIT_bLightEvent : I,
MBIT_bSndLevelEvent : I
]mkPlugMicrobit;;
proto cbAutoConnectTrm = fun [Timer PlugMicrobit] I;;
/*! \brief Callback on instance destruction
*
* Prototype: fun [PInstance SerialIO] I
*
* \param PInstance : destroyed plugIT instance
* \param SerialIO : serial object
*
* \return I : 0
**/
fun deleteOb(inst, obstr)=
if (obstr.MBIT_trmConnect == nil) then nil else
_deltimer obstr.MBIT_trmConnect;
_closeSIO obstr.MBIT_pCom;
0;;
/*! \brief Split buffer to separator separate messages
*
* Prototype: fun [S S] [S r1]
*
* \param S : buffer
* \param S : separator
*
* \return [S r1] : splitted buffer
**/
fun getbuffer(s, sep)=
let strlen s -> size in
let strlen sep -> sizesep in
let nil -> nbuf in
let 0 -> lpos in
let 0 -> pos in
if (sizesep == 0) then
["" s::nbuf]
else
(
while ((set pos = (strfind sep s lpos)) != nil) do
(
//addLogMessage strcatn (itoa pos)::" "::(substr s lpos (pos - lpos))::nil;
if nbuf == nil then
set nbuf = (substr s lpos (pos - lpos))::nil
else
set nbuf = lcat nbuf (substr s lpos (pos - lpos))::nil;
0;
set lpos = pos + sizesep;
);
let "" -> rbuf in
(
if (lpos >= size) then nil else
set rbuf = substr s lpos (size - lpos);
[rbuf nbuf];
);
);;
fun readData(obstr, mess)=
let strToListSep mess " " -> lw in
let hd lw -> key in
if (!strcmp key "a") then
(
let obstr.MBIT_tAccel -> [ox oy oz of] in
let [(atof (hd tl lw)) (atof (hd tl tl lw)) (atof (hd tl tl tl lw)) (atof (hd tl tl tl tl lw))] -> [nx ny nz nf] in
(
set obstr.MBIT_tAccel = [nx ny nz nf];
if (!obstr.MBIT_bAccelEvent) || (ox == nx && oy == ny && oz == nz) then nil else
SendPluginEvent obstr.MBIT_inst "Acceleration" strcatn (ftoa nx)::" "::(ftoa ny)::" "::(ftoa nz)::nil obstr.MBIT_inst.INST_sName;
if (!obstr.MBIT_bForceEvent) || (of == nf) then nil else
SendPluginEvent obstr.MBIT_inst "Force" (ftoa nf) obstr.MBIT_inst.INST_sName;
);
0;
)
else if (!strcmp key "r") then
(
if (!obstr.MBIT_bOrientEvent) then nil else
let obstr.MBIT_tOrient -> [ox oy oz] in
let [(atof (hd tl lw)) (atof (hd tl tl lw)) (atof (hd tl tl tl lw))] -> [nx ny nz] in
(
set obstr.MBIT_tOrient = [nx ny nz];
if (ox == nx && oy == ny && oz == nz) then nil else
SendPluginEvent obstr.MBIT_inst "Orientation" strcatn (ftoa nx)::" "::(ftoa ny)::" "::(ftoa (-.nz))::nil obstr.MBIT_inst.INST_sName;
);
0;
)
else if (!strcmp key "s") then
(
let obstr.MBIT_tSensor -> [ot ol os c] in
let [(atof (hd tl lw)) (atof (hd tl tl lw)) (atof (hd tl tl tl lw))] -> [nt nl ns] in
(
set obstr.MBIT_tSensor = [nt nl ns c];
if (ot == nt) then nil else
SendPluginEvent obstr.MBIT_inst "Temperature" (ftoa nt) obstr.MBIT_inst.INST_sName;
if (!obstr.MBIT_bLightEvent) || (ol == nl) then nil else
SendPluginEvent obstr.MBIT_inst "Light" (ftoa nl) obstr.MBIT_inst.INST_sName;
if (!obstr.MBIT_bSndLevelEvent) || (os == ns) then nil else
SendPluginEvent obstr.MBIT_inst "Sound level" (ftoa ns) obstr.MBIT_inst.INST_sName;
);
0;
)
else if (!strcmp key "c") then
(
let obstr.MBIT_tSensor -> [ot ol os c] in
let atof (hd tl lw) -> ns in
// ignore too fast trigger
if ((_tickcount - c) < 200) then nil else
(
set obstr.MBIT_tSensor = [ot ol os _tickcount];
SendPluginEvent obstr.MBIT_inst "Sound clap" (ftoa ns) obstr.MBIT_inst.INST_sName;
);
0;
)
else if (!strcmp key "p") then
(
let obstr.MBIT_tPins -> [op0 op1 op2] in
let [(atoi (hd tl lw)) (atoi (hd tl tl lw)) (atoi (hd tl tl tl lw))] -> [np0 np1 np2] in
(
set obstr.MBIT_tPins = [np0 np1 np2];
if (op0 == np0) then nil else
if (np0 != 0) then
SendPluginEvent obstr.MBIT_inst "Pin 0 high" nil obstr.MBIT_inst.INST_sName
else
SendPluginEvent obstr.MBIT_inst "Pin 0 low" nil obstr.MBIT_inst.INST_sName;
if (op1 == np1) then nil else
if (np1 != 0) then
SendPluginEvent obstr.MBIT_inst "Pin 1 high" nil obstr.MBIT_inst.INST_sName
else
SendPluginEvent obstr.MBIT_inst "Pin 1 low" nil obstr.MBIT_inst.INST_sName;
if (op2 == np2) then nil else
if (np2 != 0) then
SendPluginEvent obstr.MBIT_inst "Pin 2 high" nil obstr.MBIT_inst.INST_sName
else
SendPluginEvent obstr.MBIT_inst "Pin 2 low" nil obstr.MBIT_inst.INST_sName;
);
0;
)
else
let obstr.MBIT_iDir -> [odx ody] in
(
let strToListSep mess ":" -> lm in
let hd lm -> key in
if (!strcmp key "b.A") then
(
let obstr.MBIT_bBtnA -> ob in
let atoi (hd tl lm) -> nb in
(
set obstr.MBIT_bBtnA = nb;
if (ob == nb) then nil else
if (nb) then
SendPluginEvent obstr.MBIT_inst "Button A down" nil obstr.MBIT_inst.INST_sName
else
SendPluginEvent obstr.MBIT_inst "Button A up" nil obstr.MBIT_inst.INST_sName;
);
0;
)
else if (!strcmp key "b.B") then
(
let obstr.MBIT_bBtnB -> ob in
let atoi (hd tl lm) -> nb in
(
set obstr.MBIT_bBtnB = nb;
if (ob == nb) then nil else
if (nb) then
SendPluginEvent obstr.MBIT_inst "Button B down" nil obstr.MBIT_inst.INST_sName
else
SendPluginEvent obstr.MBIT_inst "Button B up" nil obstr.MBIT_inst.INST_sName;
);
0;
)
else if (!strcmp key "b.L") then
(
let obstr.MBIT_bBtnL -> ob in
let atoi (hd tl lm) -> nb in
(
set obstr.MBIT_bBtnL = nb;
if (ob == nb) then nil else
if (nb) then
SendPluginEvent obstr.MBIT_inst "Button Logo down" nil obstr.MBIT_inst.INST_sName
else
SendPluginEvent obstr.MBIT_inst "Button Logo up" nil obstr.MBIT_inst.INST_sName;
);
0;
)
else if (!strcmp key "d.x") then
(
mutate obstr.MBIT_iDir <- [atoi (hd tl lm) _];
0;
)
else if (!strcmp key "d.y") then
(
mutate obstr.MBIT_iDir <- [_ atoi (hd tl lm)];
0;
)
else if (!strcmp key "shake") then
(
let obstr.MBIT_bShake -> ob in
let atoi (hd tl lm) -> nb in
(
set obstr.MBIT_bShake = nb;
if (ob == nb) then nil else
if (nb) then
SendPluginEvent obstr.MBIT_inst "Start shaking" nil obstr.MBIT_inst.INST_sName
else
SendPluginEvent obstr.MBIT_inst "Stop shaking" nil obstr.MBIT_inst.INST_sName;
);
0;
)
else nil;
let obstr.MBIT_iDir -> [ndx ndy] in
if (!obstr.MBIT_bDirEvent) || (odx == ndx && ody == ndy) then nil else
SendPluginEvent obstr.MBIT_inst "Direction" strcatn (itoa ndx)::" "::(itoa ndy)::nil obstr.MBIT_inst.INST_sName;
);
0;;
// Matrix led
fun sendMatrix(obstr, data)=
let data -> [a [b [c [d [e _]]]]] in
let strcatn (itoa a)::" "::(itoa b)::" "::(itoa c)::" "::(itoa d)::" "::(itoa e)::nil -> sdata in
_writeSIO obstr.MBIT_pCom strcatn "l;"::sdata::obstr.MBIT_sLastChar::nil;
0;;
fun cbPreRender(inst, sessionstr, etime, obstr)=
let if etime < 1000 then 1 else etime / 1000 -> etime in
let (set obstr.MBIT_iTick = obstr.MBIT_iTick + etime) -> rtick in
if (rtick < (1000 / 4)) then nil else
(
if (obstr.MBIT_iCurFrame < obstr.MBIT_iNbFrames) then nil else
set obstr.MBIT_iCurFrame = 0;
sendMatrix obstr nth_list obstr.MBIT_lData obstr.MBIT_iCurFrame;
set obstr.MBIT_iCurFrame = obstr.MBIT_iCurFrame + 1;
set obstr.MBIT_iTick = 0;
);
0;;
fun getMatrixData(l)=
let 1::2::4::8::16::nil -> lflags in
let mktab 5 0 -> mtab in
(
let 0 -> y in
while ((l != nil) && (y < 5)) do
(
let hd l -> lp in
let 0 -> x in
while ((lp != nil) && (x < 5)) do
(
let (atoi (hd lp)) -> p in
if (p != 1) then nil else
set mtab.(y) = mtab.(y) | (nth_list lflags x);
set lp = tl lp;
set x = x + 1;
);
set y = y + 1;
set l = tl l;
);
[l tabtolist mtab];
);;
fun cbSetMatrix(inst, from, action, param, reply, obstr)=
let strextr param -> l in
let nil -> ldata in
(
while (l != nil) do
(
let getMatrixData l -> [nl data] in
(
set ldata = data::ldata;
if ((hd nl) != nil) then nil else
set nl = tl nl;
set l = nl;
);
);
set obstr.MBIT_lData = revertlist ldata;
sendMatrix obstr hd obstr.MBIT_lData;
set obstr.MBIT_iNbFrames = sizelist obstr.MBIT_lData;
if (obstr.MBIT_iNbFrames > 0) then
setPluginInstanceCbScenePreRender obstr.MBIT_inst mkfun4 @cbPreRender obstr
else
setPluginInstanceCbScenePreRender obstr.MBIT_inst nil;
);
0;;
fun cbWriteText(inst, from, action, param, reply, obstr)=
_writeSIO obstr.MBIT_pCom strcatn "w;"::param::obstr.MBIT_sLastChar::nil;
0;;
fun cbSetBuzzer(inst, from, action, param, reply, obstr)=
let hd hd strextr param -> tone in
_writeSIO obstr.MBIT_pCom strcatn "b;"::tone::obstr.MBIT_sLastChar::nil;
0;;
fun cbPlayMusic(inst, from, action, param, reply, obstr)=
let switchstri lMusics param -> id in
let if (id == nil) then atoi param else id -> id in
(
//addLogMessage itoa id;
_writeSIO obstr.MBIT_pCom strcatn "m; "::(itoa id)::obstr.MBIT_sLastChar::nil;
);
0;;
fun cbWritePin(inst, from, action, param, reply, p)=
let p -> [obstr cmd] in
_writeSIO obstr.MBIT_pCom strcatn cmd::param::obstr.MBIT_sLastChar::nil;
0;;
/*! \brief Callback on serial receive data
*
* Prototype: fun [SerialIO [PInstance S S] S I] I
*
* \param SerialIO : serial object
* \param [PInstance S] : plugIT instance and last buffer
* \param S : data
* \param I : data size
*
* \return I : 0
**/
fun cbRead(com, p, data, size)=
let p -> [obstr buffer] in
let getbuffer (strcat buffer data) obstr.MBIT_sLastChar -> [rest lbuf] in
(
mutate p <- [_ rest];
while lbuf != nil do
(
let hd lbuf -> mess in
(
if (obstr.MBIT_bAuto && obstr.MBIT_iAutoState == 1) then
(
if (strcmpi strtrim mess "microbit") then nil else
(
if (obstr.MBIT_trmConnect == nil) then nil else
_deltimer obstr.MBIT_trmConnect;
set obstr.MBIT_iAutoState = 0;
set obstr.MBIT_sPort = itoa obstr.MBIT_iAutoPort;
addLogMessage strcat "Microbit: connected on port " obstr.MBIT_sPort;
SendPluginEvent obstr.MBIT_inst "Opened" nil nil;
);
0;
)
else
(
readData obstr mess;
// Send "Read" event messages
if (!obstr.MBIT_bReadEvent) then nil else
SendPluginEvent obstr.MBIT_inst "Read" mess nil;
0;
);
);
set lbuf = tl lbuf;
);
);
0;;
/*! \brief Callback on "Send" action
*
* Write param link data to serial object
*
* Prototype: fun [PInstance DMI S S I [SerialIO I I I I S]] I
*
* \param PInstance : plugIT instance
* \param DMI : DMS module who call the action (not used)
* \param S : name of the launched action
* \param S : data posted in DMS action link
* \param I : reply flag (not used)
* \param [SerialIO S] : Serial object and char to add at the end of message
*
* \return I : 0
**/
fun cbSend(inst, from, action, param, reply, obstr)=
_writeSIO obstr.MBIT_pCom strcat param obstr.MBIT_sLastChar;
0;;
fun cbConnectTrm(trm, obstr)=
_deltimer trm;
set obstr.MBIT_trmConnect = nil;
if (obstr.MBIT_bAuto && obstr.MBIT_iAutoState == 1) then
(
_closeSIO obstr.MBIT_pCom;
set obstr.MBIT_iAutoPort = obstr.MBIT_iAutoPort + 1;
if (obstr.MBIT_iAutoPort <= iMaxPort) then nil else
set obstr.MBIT_iAutoPort = 0;
set obstr.MBIT_pCom = _openSIO _channel (itoa obstr.MBIT_iAutoPort) [obstr.MBIT_iSpeed obstr.MBIT_bParity obstr.MBIT_iByteSize obstr.MBIT_iStopBit] @cbRead [obstr ""];
set obstr.MBIT_trmConnect = _rfltimer _starttimer _channel iDeviceBootTime @cbAutoConnectTrm obstr;
addLogMessage strcat "Microbit: try to connect on port " itoa obstr.MBIT_iAutoPort;
0;
)
else
(
SendPluginEvent obstr.MBIT_inst "Opened" nil nil;
0;
);
0;;
fun cbAutoConnectTrm(trm, obstr)=
_deltimer trm;
set obstr.MBIT_trmConnect = nil;
_writeSIO obstr.MBIT_pCom strcat "h;" obstr.MBIT_sLastChar;
set obstr.MBIT_trmConnect = _rfltimer _starttimer _channel iAutoPortTimeOut @cbConnectTrm obstr;
0;;
/*! \brief Callback on "Set com port" dms action
*
* Reconnect the serial com on a new port
*
* Prototype: fun [PInstance DMI S S I [SerialIO I I I I S]] I
*
* \param PInstance : plugIT instance
* \param DMI : DMS module who call the action (not used)
* \param S : name of the launched action
* \param S : data posted in DMS action link
* \param I : reply flag (not used)
* \param [SerialIO I I I I S] : Serial object and char to add at the end of message
*
* \return I : 0
**/
fun cbSetComPort(inst, from, action, param, reply, obstr)=
if (obstr.MBIT_trmConnect == nil) then nil else
_deltimer obstr.MBIT_trmConnect;
_closeSIO obstr.MBIT_pCom;
set obstr.MBIT_sPort = param;
set obstr.MBIT_pCom = _openSIO _channel obstr.MBIT_sPort [obstr.MBIT_iSpeed obstr.MBIT_bParity obstr.MBIT_iByteSize obstr.MBIT_iStopBit] @cbRead [obstr ""];
set obstr.MBIT_trmConnect = _rfltimer _starttimer _channel iDeviceBootTime @cbConnectTrm obstr;
set obstr.MBIT_bAuto = 0;
set obstr.MBIT_iAutoState = 0;
0;;
fun cbConnect(inst, from, action, param, reply, obstr)=
if (obstr.MBIT_trmConnect == nil) then nil else
_deltimer obstr.MBIT_trmConnect;
_closeSIO obstr.MBIT_pCom;
if (obstr.MBIT_bAuto) then
(
set obstr.MBIT_iAutoState = 1;
set obstr.MBIT_pCom = _openSIO _channel (itoa obstr.MBIT_iAutoPort) [obstr.MBIT_iSpeed obstr.MBIT_bParity obstr.MBIT_iByteSize obstr.MBIT_iStopBit] @cbRead [obstr ""];
set obstr.MBIT_trmConnect = _rfltimer _starttimer _channel iDeviceBootTime @cbAutoConnectTrm obstr;
addLogMessage strcat "Microbit: try to connect on port " itoa obstr.MBIT_iAutoPort;
0;
)
else
(
set obstr.MBIT_pCom = _openSIO _channel obstr.MBIT_sPort [obstr.MBIT_iSpeed obstr.MBIT_bParity obstr.MBIT_iByteSize obstr.MBIT_iStopBit] @cbRead [obstr ""];
set obstr.MBIT_trmConnect = _rfltimer _starttimer _channel iDeviceBootTime @cbConnectTrm obstr;
0;
);
0;;
fun cbDisconnect(inst, from, action, param, reply, obstr)=
if (obstr.MBIT_trmConnect == nil) then nil else
_deltimer obstr.MBIT_trmConnect;
set obstr.MBIT_pCom = nil;
set obstr.MBIT_trmConnect = nil;
0;;
/*! \brief Callback on new plugIT instance
*
* Read the parameters from editor values and connect the serial port
*
* Prototype: fun [PInstance] I
*
* \param PInstance : plugIT instance
*
* \return I : 0
**/
fun newOb(inst)=
let atoi (getPluginInstanceParam inst "auto") -> auto in
let if auto == nil then 1 else auto -> auto in
let (getPluginInstanceParam inst "port") -> sport in
let if sport == nil then "1" else sport -> sport in
let atoi (getPluginInstanceParam inst "speed") -> speed in
let if speed == nil then 115200 else speed -> speed in
let atoi (getPluginInstanceParam inst "lastchar") -> lastchar in
let if lastchar == nil then 0 else lastchar -> lastchar in
let atoi (getPluginInstanceParam inst "init") -> init in
let if init == nil then 1 else init -> init in
let (IsInEditor inst) || IsEventLinked inst "Read" -> breadevent in
let (IsInEditor inst) || IsEventLinked inst "Acceleration" -> baccelevent in
let (IsInEditor inst) || IsEventLinked inst "Force" -> bforceevent in
let (IsInEditor inst) || IsEventLinked inst "Orientation" -> borientevent in
let (IsInEditor inst) || IsEventLinked inst "Direction" -> bdirevent in
let (IsInEditor inst) || IsEventLinked inst "Light" -> blightevent in
let (IsInEditor inst) || IsEventLinked inst "Sound level" -> bsnblevelevent in
let if lastchar == 1 then ctoa 13 else if lastchar == 2 then (strcat (ctoa 13) (ctoa 10)) else "" -> slastchar in
let mkPlugMicrobit [inst nil sport speed 0 8 0 slastchar nil auto 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]
nil 0 0 0
breadevent baccelevent bforceevent borientevent bdirevent blightevent bsnblevelevent] -> obstr in
(
if (!init) then nil else
cbConnect inst nil nil nil nil obstr;
PluginRegisterAction inst "Connect" mkfun6 @cbConnect obstr;
PluginRegisterAction inst "Disconnect" mkfun6 @cbDisconnect obstr;
PluginRegisterAction inst "Send" mkfun6 @cbSend obstr;
PluginRegisterAction inst "Set com port" mkfun6 @cbSetComPort obstr;
PluginRegisterAction inst "Set matrix" mkfun6 @cbSetMatrix obstr;
PluginRegisterAction inst "Write text" mkfun6 @cbWriteText obstr;
PluginRegisterAction inst "Play tone" mkfun6 @cbSetBuzzer obstr;
PluginRegisterAction inst "Play music" mkfun6 @cbPlayMusic obstr;
PluginRegisterAction inst "Write pin 0" mkfun6 @cbWritePin [obstr "p0;"];
PluginRegisterAction inst "Write pin 1" mkfun6 @cbWritePin [obstr "p1;"];
PluginRegisterAction inst "Write pin 2" mkfun6 @cbWritePin [obstr "p2;"];
setPluginInstanceCbDel inst mkfun2 @deleteOb obstr;
);
0;;
/*! \brief Global plugIT function to initialize the plugIT callbacks
*
* Prototype: fun [S] I
*
* \param S : plugIT file path
*
* \return I : 0
**/
fun IniPlug(file)=
PlugRegister @newOb nil;
setPluginEditor @dynamicedit;
0;;