Advertisement
Guest User

Untitled

a guest
Apr 22nd, 2017
132
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Go 14.99 KB | None | 0 0
  1. package main
  2.  
  3. import (
  4.     "encoding/json"
  5.     "log"
  6.     "regexp"
  7.     "strconv"
  8.     "strings"
  9.     "sync"
  10.     "time"
  11. )
  12.  
  13. type BufferflowGrbl struct {
  14.     Name      string
  15.     Port      string
  16.     Paused    bool
  17.     BufferMax int
  18.     q         *Queue
  19.  
  20.     // use thread locking for b.Paused
  21.     lock *sync.Mutex
  22.  
  23.     sem            chan int
  24.     LatestData     string
  25.     LastStatus     string
  26.     version        string
  27.     quit           chan int
  28.     parent_serport *serport
  29.  
  30.     reNewLine *regexp.Regexp
  31.     ok        *regexp.Regexp
  32.     err       *regexp.Regexp
  33.     initline  *regexp.Regexp
  34.     qry       *regexp.Regexp
  35.     rpt       *regexp.Regexp
  36. }
  37.  
  38. func (b *BufferflowGrbl) Init() {
  39.     b.lock = &sync.Mutex{}
  40.     b.SetPaused(false, 1)
  41.  
  42.     log.Println("Initting GRBL buffer flow")
  43.     // b.BufferMax = 127 //max buffer size 127 bytes available
  44.     b.BufferMax = 125 // changed to be safe with extra chars
  45.  
  46.     b.q = NewQueue()
  47.  
  48.     //create channels
  49.     b.sem = make(chan int)
  50.  
  51.     //define regex
  52.     b.reNewLine, _ = regexp.Compile("\\r{0,1}\\n{1,2}") //\\r{0,1}
  53.     b.ok, _ = regexp.Compile("^ok")
  54.     b.err, _ = regexp.Compile("^error")
  55.     b.initline, _ = regexp.Compile("^Grbl")
  56.     b.qry, _ = regexp.Compile("\\?")
  57.     b.rpt, _ = regexp.Compile("^<")
  58.  
  59.     //initialize query loop
  60.     b.rptQueryLoop(b.parent_serport)
  61. }
  62.  
  63. func (b *BufferflowGrbl) RewriteSerialData(cmd string, id string) string {
  64.     return ""
  65. }
  66.  
  67. func (b *BufferflowGrbl) BlockUntilReady(cmd string, id string) (bool, bool, string) {
  68.     log.Printf("BlockUntilReady() start\n")
  69.  
  70.     b.q.Push(cmd, id)
  71.  
  72.     log.Printf("New line length: %v, buffer size increased to:%v\n", len(cmd), b.q.LenOfCmds())
  73.     log.Println(b.q)
  74.  
  75.     if b.q.LenOfCmds() >= b.BufferMax {
  76.         b.SetPaused(true, 0)
  77.         log.Printf("Buffer Full - Will send this command when space is available")
  78.     }
  79.  
  80.     if b.GetPaused() {
  81.         log.Println("It appears we are being asked to pause, so we will wait on b.sem")
  82.         // We are being asked to pause our sending of commands
  83.  
  84.         // clear all b.sem signals so when we block below, we truly block
  85.         b.ClearOutSemaphore()
  86.  
  87.         log.Println("Blocking on b.sem until told from OnIncomingData to go")
  88.         unblockType, ok := <-b.sem // will block until told from OnIncomingData to go
  89.  
  90.         log.Printf("Done blocking cuz got b.sem semaphore release. ok:%v, unblockType:%v\n", ok, unblockType)
  91.  
  92.         // we get an unblockType of 1 for normal unblocks
  93.         // we get an unblockType of 2 when we're being asked to wipe the buffer, i.e. from a % cmd
  94.         if unblockType == 2 {
  95.             log.Println("This was an unblock of type 2, which means we're being asked to wipe internal buffer. so return false.")
  96.             // returning false asks the calling method to wipe the serial send once
  97.             // this function returns
  98.             return false, false, ""
  99.         }
  100.  
  101.         log.Printf("BlockUntilReady(cmd:%v, id:%v) end\n", cmd, id)
  102.     }
  103.     return true, true, ""
  104. }
  105.  
  106. func (b *BufferflowGrbl) OnIncomingData(data string) {
  107.     log.Printf("OnIncomingData() start. data:%q\n", data)
  108.  
  109.     b.LatestData += data
  110.  
  111.     //it was found ok was only received with status responses until the grbl buffer is full.
  112.     //b.LatestData = regexp.MustCompile(">\\r\\nok").ReplaceAllString(b.LatestData, ">") //remove oks from status responses
  113.  
  114.     arrLines := b.reNewLine.Split(b.LatestData, -1)
  115.     log.Printf("arrLines:%v\n", arrLines)
  116.  
  117.     if len(arrLines) > 1 {
  118.         // that means we found a newline and have 2 or greater array values
  119.         // so we need to analyze our arrLines[] lines but keep last line
  120.         // for next trip into OnIncomingData
  121.         log.Printf("We have data lines to analyze. numLines:%v\n", len(arrLines))
  122.  
  123.     } else {
  124.         // we don't have a newline yet, so just exit and move on
  125.         // we don't have to reset b.LatestData because we ended up
  126.         // without any newlines so maybe we will next time into this method
  127.         log.Printf("Did not find newline yet, so nothing to analyze\n")
  128.         return
  129.     }
  130.  
  131.     // if we made it here we have lines to analyze
  132.     // so analyze all of them except the last line
  133.     for index, element := range arrLines[:len(arrLines)-1] {
  134.         log.Printf("Working on element:%v, index:%v", element, index)
  135.  
  136.         //check for 'ok' or 'error' response indicating a gcode line has been processed
  137.         if b.ok.MatchString(element) || b.err.MatchString(element) {
  138.             if b.q.Len() > 0 {
  139.                 doneCmd, id := b.q.Poll()
  140.  
  141.                 if b.ok.MatchString(element) {
  142.                     // Send cmd:"Complete" back
  143.                     m := DataCmdComplete{"Complete", id, b.Port, b.q.LenOfCmds(), doneCmd}
  144.                     bm, err := json.Marshal(m)
  145.                     if err == nil {
  146.                         h.broadcastSys <- bm
  147.                     }
  148.                 } else if b.err.MatchString(element) {
  149.                     // Send cmd:"Error" back
  150.                     log.Printf("Error Response Received:%v, id:%v", doneCmd, id)
  151.                     m := DataCmdComplete{"Error", id, b.Port, b.q.LenOfCmds(), doneCmd}
  152.                     bm, err := json.Marshal(m)
  153.                     if err == nil {
  154.                         h.broadcastSys <- bm
  155.                     }
  156.                 }
  157.  
  158.                 log.Printf("Buffer decreased to itemCnt:%v, lenOfBuf:%v\n", b.q.Len(), b.q.LenOfCmds())
  159.             } else {
  160.                 log.Printf("We should NEVER get here cuz we should have a command in the queue to dequeue when we get the r:{} response. If you see this debug stmt this is BAD!!!!")
  161.             }
  162.  
  163.             if b.q.LenOfCmds() < b.BufferMax {
  164.  
  165.                 log.Printf("Grbl just completed a line of gcode\n")
  166.  
  167.                 // if we are paused, tell us to unpause cuz we have clean buffer room now
  168.                 if b.GetPaused() {
  169.                     time.Sleep(2 * time.Millisecond)
  170.                     b.SetPaused(false, 1)
  171.                 }
  172.             }
  173.  
  174.             //check for the grbl init line indicating the arduino is ready to accept commands
  175.             //could also pull version from this string, if we find a need for that later
  176.         } else if b.initline.MatchString(element) {
  177.             //grbl init line received, clear anything from current buffer and unpause
  178.             b.LocalBufferWipe(b.parent_serport)
  179.  
  180.             //unpause buffer but wipe the command in the queue as grbl has restarted.
  181.             if b.GetPaused() {
  182.                 time.Sleep(2 * time.Millisecond)
  183.                 b.SetPaused(false, 2)
  184.             }
  185.  
  186.             b.version = element //save element in version
  187.  
  188.             //Check for report output, compare to last report output, if different return to client to update status; otherwise ignore status.
  189.         } else if b.rpt.MatchString(element) {
  190.             if element == b.LastStatus {
  191.                 log.Println("Grbl status has not changed, not reporting to client")
  192.                 continue //skip this element as the cnc position has not changed, and move on to the next element.
  193.             }
  194.  
  195.             b.LastStatus = element //if we make it here something has changed with the status string and laststatus needs updating
  196.         }
  197.  
  198.         // handle communication back to client
  199.         m := DataPerLine{b.Port, element + "\n"}
  200.         bm, err := json.Marshal(m)
  201.         if err == nil {
  202.             h.broadcastSys <- bm
  203.         }
  204.  
  205.     } // for loop
  206.  
  207.     // now wipe the LatestData to only have the last line that we did not analyze
  208.     // because we didn't know/think that was a full command yet
  209.     b.LatestData = arrLines[len(arrLines)-1]
  210.  
  211.     //time.Sleep(3000 * time.Millisecond)
  212.     log.Printf("OnIncomingData() end.\n")
  213. }
  214.  
  215. // Clean out b.sem so it can truly block
  216. func (b *BufferflowGrbl) ClearOutSemaphore() {
  217.     ctr := 0
  218.  
  219.     keepLooping := true
  220.     for keepLooping {
  221.         select {
  222.         case d, ok := <-b.sem:
  223.             log.Printf("Consuming b.sem queue to clear it before we block. ok:%v, d:%v\n", ok, string(d))
  224.             ctr++
  225.             if ok == false {
  226.                 keepLooping = false
  227.             }
  228.         default:
  229.             keepLooping = false
  230.             log.Println("Hit default in select clause")
  231.         }
  232.     }
  233.     log.Printf("Done consuming b.sem queue so we're good to block on it now. ctr:%v\n", ctr)
  234.     // ok, all b.sem signals are now consumed into la-la land
  235. }
  236.  
  237. func (b *BufferflowGrbl) BreakApartCommands(cmd string) []string {
  238.  
  239.     // add newline after !~%
  240.     log.Printf("Command Before Break-Apart: %q\n", cmd)
  241.  
  242.     cmds := strings.Split(cmd, "\n")
  243.     finalCmds := []string{}
  244.     for _, item := range cmds {
  245.         //remove comments and whitespace from item
  246.         item = regexp.MustCompile("\\(.*?\\)").ReplaceAllString(item, "")
  247.         item = regexp.MustCompile(";.*").ReplaceAllString(item, "")
  248.         item = strings.Replace(item, " ", "", -1)
  249.  
  250.         if item == "*init*" { //return init string to update grbl widget when already connected to grbl
  251.             m := DataPerLine{b.Port, b.version + "\n"}
  252.             bm, err := json.Marshal(m)
  253.             if err == nil {
  254.                 h.broadcastSys <- bm
  255.             }
  256.         } else if item == "*status*" { //return status when client first connects to existing open port
  257.             m := DataPerLine{b.Port, b.LastStatus + "\n"}
  258.             bm, err := json.Marshal(m)
  259.             if err == nil {
  260.                 h.broadcastSys <- bm
  261.             }
  262.         } else if item == "?" {
  263.             log.Printf("Query added without newline: %q\n", item)
  264.             finalCmds = append(finalCmds, item) //append query request without newline character
  265.         } else if item == "%" {
  266.             log.Printf("Wiping Grbl BufferFlow")
  267.             b.LocalBufferWipe(b.parent_serport)
  268.             //dont add this command to the list of finalCmds
  269.         } else if item != "" {
  270.             log.Printf("Re-adding newline to item:%v\n", item)
  271.             s := item + "\n"
  272.             finalCmds = append(finalCmds, s)
  273.             log.Printf("New cmd item:%v\n", s)
  274.         }
  275.  
  276.     }
  277.     log.Printf("Final array of cmds after BreakApartCommands(). finalCmds:%v\n", finalCmds)
  278.  
  279.     return finalCmds
  280.     //return []string{cmd} //do not process string
  281. }
  282.  
  283. func (b *BufferflowGrbl) Pause() {
  284.     b.SetPaused(true, 0)
  285.     //b.BypassMode = false // turn off bypassmode in case it's on
  286.     log.Println("Paused buffer on next BlockUntilReady() call")
  287. }
  288.  
  289. func (b *BufferflowGrbl) Unpause() {
  290.     //unpause buffer by setting paused to false and passing a 1 to b.sem
  291.     time.Sleep(2 * time.Millisecond)
  292.     b.SetPaused(false, 1)
  293.     log.Println("Unpaused buffer inside BlockUntilReady() call")
  294. }
  295.  
  296. func (b *BufferflowGrbl) SeeIfSpecificCommandsShouldSkipBuffer(cmd string) bool {
  297.     // remove comments
  298.     //cmd = regexp.MustCompile("\\(.*?\\)").ReplaceAllString(cmd, "")
  299.     //cmd = regexp.MustCompile(";.*").ReplaceAllString(cmd, "")
  300.     // adding some new regexp to match real-time commands for grbl 1 version
  301.     if match, _ := regexp.MatchString("[!~\\?]|(\u0018)|[\u0080-\u00FF]", cmd); match {
  302.         log.Printf("Found cmd that should skip buffer. cmd:%v\n", cmd)
  303.         return true
  304.     }
  305.     return false
  306. }
  307.  
  308. func (b *BufferflowGrbl) SeeIfSpecificCommandsShouldPauseBuffer(cmd string) bool {
  309.     // remove comments
  310.     //cmd = regexp.MustCompile("\\(.*?\\)").ReplaceAllString(cmd, "")
  311.     //cmd = regexp.MustCompile(";.*").ReplaceAllString(cmd, "")
  312.     if match, _ := regexp.MatchString("[!]", cmd); match {
  313.         log.Printf("Found cmd that should pause buffer. cmd:%v\n", cmd)
  314.         return true
  315.     }
  316.     return false
  317. }
  318.  
  319. func (b *BufferflowGrbl) SeeIfSpecificCommandsShouldUnpauseBuffer(cmd string) bool {
  320.  
  321.     //cmd = regexp.MustCompile("\\(.*?\\)").ReplaceAllString(cmd, "")
  322.     //cmd = regexp.MustCompile(";.*").ReplaceAllString(cmd, "")
  323.     if match, _ := regexp.MatchString("[~]", cmd); match {
  324.         log.Printf("Found cmd that should unpause buffer. cmd:%v\n", cmd)
  325.         return true
  326.     }
  327.     return false
  328. }
  329.  
  330. func (b *BufferflowGrbl) SeeIfSpecificCommandsShouldWipeBuffer(cmd string) bool {
  331.  
  332.     //cmd = regexp.MustCompile("\\(.*?\\)").ReplaceAllString(cmd, "")
  333.     //cmd = regexp.MustCompile(";.*").ReplaceAllString(cmd, "")
  334.     if match, _ := regexp.MatchString("(\u0018)", cmd); match {
  335.         log.Printf("Found cmd that should wipe out and reset buffer. cmd:%v\n", cmd)
  336.  
  337.         //b.q.Delete() //delete tracking queue, all buffered commands will be wiped.
  338.  
  339.         //log.Println("Buffer variables cleared for new input.")
  340.         return true
  341.     }
  342.     return false
  343. }
  344.  
  345. func (b *BufferflowGrbl) SeeIfSpecificCommandsReturnNoResponse(cmd string) bool {
  346.     /*
  347.         // remove comments
  348.         cmd = b.reComment.ReplaceAllString(cmd, "")
  349.         cmd = b.reComment2.ReplaceAllString(cmd, "")
  350.         if match := b.reNoResponse.MatchString(cmd); match {
  351.             log.Printf("Found cmd that does not get a response from TinyG. cmd:%v\n", cmd)
  352.             return true
  353.         }
  354.     */
  355.     return false
  356. }
  357.  
  358. func (b *BufferflowGrbl) ReleaseLock() {
  359.     log.Println("Lock being released in GRBL buffer")
  360.  
  361.     b.q.Delete()
  362.  
  363.     log.Println("ReleaseLock(), so we will send signal of 2 to b.sem to unpause the BlockUntilReady() thread")
  364.  
  365.     //release lock, send signal 2 to b.sem
  366.     time.Sleep(2 * time.Millisecond)
  367.     b.SetPaused(false, 2)
  368. }
  369.  
  370. func (b *BufferflowGrbl) IsBufferGloballySendingBackIncomingData() bool {
  371.     //telling json server that we are handling client responses
  372.     return true
  373. }
  374.  
  375. //Use this function to open a connection, write directly to serial port and close connection.
  376. //This is used for sending query requests outside of the normal buffered operations that will pause to wait for room in the grbl buffer
  377. //'?' is asynchronous to the normal buffer load and does not need to be paused when buffer full
  378. func (b *BufferflowGrbl) rptQueryLoop(p *serport) {
  379.     b.parent_serport = p //make note of this port for use in clearing the buffer later, on error.
  380.     ticker := time.NewTicker(250 * time.Millisecond)
  381.     b.quit = make(chan int)
  382.     go func() {
  383.         for {
  384.             select {
  385.             case <-ticker.C:
  386.  
  387.                 n2, err := p.portIo.Write([]byte("?"))
  388.  
  389.                 log.Print("Just wrote ", n2, " bytes to serial: ?")
  390.  
  391.                 if err != nil {
  392.                     errstr := "Error writing to " + p.portConf.Name + " " + err.Error() + " Closing port."
  393.                     log.Print(errstr)
  394.                     h.broadcastSys <- []byte(errstr)
  395.                     ticker.Stop() //stop query loop if we can't write to the port
  396.                     break
  397.                 }
  398.             case <-b.quit:
  399.                 ticker.Stop()
  400.                 return
  401.             }
  402.         }
  403.     }()
  404. }
  405.  
  406. func (b *BufferflowGrbl) Close() {
  407.     //stop the status query loop when the serial port is closed off.
  408.     log.Println("Stopping the status query loop")
  409.     b.quit <- 1
  410. }
  411.  
  412. //  Gets the paused state of this buffer
  413. //  go-routine safe.
  414. func (b *BufferflowGrbl) GetPaused() bool {
  415.     b.lock.Lock()
  416.     defer b.lock.Unlock()
  417.     return b.Paused
  418. }
  419.  
  420. //  Sets the paused state of this buffer
  421. //  go-routine safe.
  422. func (b *BufferflowGrbl) SetPaused(isPaused bool, semRelease int) {
  423.     b.lock.Lock()
  424.     defer b.lock.Unlock()
  425.     b.Paused = isPaused
  426.  
  427.     //if we are unpausing the buffer, we need to send a signal to release the channel
  428.     if isPaused == false {
  429.         go func() {
  430.             // sending a 2 asks BlockUntilReady() to cancel the send
  431.             b.sem <- semRelease
  432.             defer func() {
  433.                 log.Printf("Unpause Semaphore just got consumed by the BlockUntilReady()\n")
  434.             }()
  435.         }()
  436.     }
  437. }
  438.  
  439. //local version of buffer wipe loop needed to handle pseudo clear buffer (%) without passing that value on to
  440. func (b *BufferflowGrbl) LocalBufferWipe(p *serport) {
  441.     log.Printf("Pseudo command received to wipe grbl buffer but *not* send on to grbl controller.")
  442.  
  443.     // consume all stuff queued
  444.     func() {
  445.         ctr := 0
  446.  
  447.         keepLooping := true
  448.         for keepLooping {
  449.             select {
  450.             case d, ok := <-p.sendBuffered:
  451.                 log.Printf("Consuming sendBuffered queue. ok:%v, d:%v, id:%v\n", ok, string(d.data), string(d.id))
  452.                 ctr++
  453.  
  454.                 p.itemsInBuffer--
  455.                 if ok == false {
  456.                     keepLooping = false
  457.                 }
  458.             default:
  459.                 keepLooping = false
  460.                 log.Println("Hit default in select clause")
  461.             }
  462.         }
  463.         log.Printf("Done consuming sendBuffered cmds. ctr:%v\n", ctr)
  464.     }()
  465.  
  466.     b.ReleaseLock()
  467.  
  468.     // let user know we wiped queue
  469.     log.Printf("itemsInBuffer:%v\n", p.itemsInBuffer)
  470.     h.broadcastSys <- []byte("{\"Cmd\":\"WipedQueue\",\"QCnt\":" + strconv.Itoa(p.itemsInBuffer) + ",\"Port\":\"" + p.portConf.Name + "\"}")
  471. }
  472.  
  473. func (b *BufferflowGrbl) GetManualPaused() bool {
  474.     return false
  475. }
  476.  
  477. func (b *BufferflowGrbl) SetManualPaused(isPaused bool) {
  478. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement