/* ----------------------------------------------------------------------------- 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;;