diff --git a/AVRsack.xcodeproj/project.pbxproj b/AVRsack.xcodeproj/project.pbxproj index 3bc172e..78c601a 100644 --- a/AVRsack.xcodeproj/project.pbxproj +++ b/AVRsack.xcodeproj/project.pbxproj @@ -19,6 +19,8 @@ 95468DDF1A228BE600668EE2 /* ASHardware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95468DDE1A228BE600668EE2 /* ASHardware.swift */; }; 95468DE31A228E1300668EE2 /* Defaults.plist in Resources */ = {isa = PBXBuildFile; fileRef = 95468DE21A228E1300668EE2 /* Defaults.plist */; }; 95539B681A3E7EAF00D8595C /* ASSerial.mm in Sources */ = {isa = PBXBuildFile; fileRef = 95539B671A3E7EAF00D8595C /* ASSerial.mm */; }; + 956005F41A4EF79D00327007 /* ASSerialWin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 956005F21A4EF79D00327007 /* ASSerialWin.swift */; }; + 956005F51A4EF79D00327007 /* ASSerialWin.xib in Resources */ = {isa = PBXBuildFile; fileRef = 956005F31A4EF79D00327007 /* ASSerialWin.xib */; }; 95A7C6401A38914300EF1963 /* ASPreferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 95A7C63E1A38914300EF1963 /* ASPreferences.xib */; }; 95A7C6461A3894C900EF1963 /* ASPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A7C6451A3894C900EF1963 /* ASPreferences.swift */; }; 95BF80EB1A185C9E0004A693 /* ASFileTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BF80EA1A185C9E0004A693 /* ASFileTree.swift */; }; @@ -83,6 +85,8 @@ 95468DE21A228E1300668EE2 /* Defaults.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Defaults.plist; sourceTree = ""; }; 95539B661A3E7EAF00D8595C /* ASSerial.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASSerial.h; sourceTree = ""; }; 95539B671A3E7EAF00D8595C /* ASSerial.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASSerial.mm; sourceTree = ""; }; + 956005F21A4EF79D00327007 /* ASSerialWin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASSerialWin.swift; sourceTree = ""; }; + 956005F31A4EF79D00327007 /* ASSerialWin.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ASSerialWin.xib; sourceTree = ""; }; 958D76811A17DA1C00917D96 /* AVRsack-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AVRsack-Bridging-Header.h"; sourceTree = ""; }; 95A7C63F1A38914300EF1963 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ASPreferences.xib; sourceTree = ""; }; 95A7C6451A3894C900EF1963 /* ASPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASPreferences.swift; sourceTree = ""; }; @@ -182,6 +186,7 @@ 95539B661A3E7EAF00D8595C /* ASSerial.h */, 95539B671A3E7EAF00D8595C /* ASSerial.mm */, 95DF20661A45A6090013D1B5 /* ASSketchBook.swift */, + 956005F21A4EF79D00327007 /* ASSerialWin.swift */, ); name = Source; sourceTree = ""; @@ -193,6 +198,7 @@ 950AB9261A296A160033A9DA /* ProjIcon.icns */, 9501D80A1A17025C0034C530 /* MainMenu.xib */, 9501D8051A17025C0034C530 /* ASProjDoc.xib */, + 956005F31A4EF79D00327007 /* ASSerialWin.xib */, 95A7C63E1A38914300EF1963 /* ASPreferences.xib */, 95468DE21A228E1300668EE2 /* Defaults.plist */, ); @@ -331,6 +337,7 @@ files = ( 95A7C6401A38914300EF1963 /* ASPreferences.xib in Resources */, 95468DE31A228E1300668EE2 /* Defaults.plist in Resources */, + 956005F51A4EF79D00327007 /* ASSerialWin.xib in Resources */, 9501D8071A17025C0034C530 /* ASProjDoc.xib in Resources */, 950AB9271A296A160033A9DA /* ProjIcon.icns in Resources */, 9501D8091A17025C0034C530 /* Images.xcassets in Resources */, @@ -353,6 +360,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 956005F41A4EF79D00327007 /* ASSerialWin.swift in Sources */, 9501D8041A17025C0034C530 /* ASProjDoc.swift in Sources */, 95539B681A3E7EAF00D8595C /* ASSerial.mm in Sources */, 95468DDF1A228BE600668EE2 /* ASHardware.swift in Sources */, diff --git a/AVRsack/ASBuilder.swift b/AVRsack/ASBuilder.swift index 3eaede6..7443d41 100644 --- a/AVRsack/ASBuilder.swift +++ b/AVRsack/ASBuilder.swift @@ -91,6 +91,8 @@ class ASBuilder { if variantPath != nil { args.append("variant_path="+variantPath!) } + args.append("usb_vid="+(boardProp["build.vid"] ?? "null")); + args.append("usb_pid="+(boardProp["build.pid"] ?? "null")); args.append("--") args += files.paths task!.arguments = args; @@ -110,14 +112,16 @@ class ASBuilder { task!.standardOutput = logOut task!.standardError = logOut - let libPath = (ASLibraries.instance().directories as NSArray).componentsJoinedByString(":") - let boardProp = ASHardware.instance().boards[board]! - let progProp = ASHardware.instance().programmers[programmer] - let proto = boardProp["upload.protocol"] ?? progProp?["protocol"] - let speed = boardProp["upload.speed"] ?? progProp?["speed"] - let verbosity = NSUserDefaults.standardUserDefaults().integerForKey("UploadVerbosity") - var args = Array(count: verbosity, repeatedValue: "-v") - args += [ + let libPath = (ASLibraries.instance().directories as NSArray).componentsJoinedByString(":") + let boardProp = ASHardware.instance().boards[board]! + let progProp = ASHardware.instance().programmers[programmer] + let hasBootloader = boardProp["upload.protocol"] != nil + let leonardish = hasBootloader && (boardProp["bootloader.path"] ?? "").hasPrefix("caterina") + let proto = hasBootloader ? boardProp["upload.protocol"] : progProp?["protocol"] + let speed = hasBootloader ? boardProp["upload.speed"] : progProp?["speed"] + let verbosity = NSUserDefaults.standardUserDefaults().integerForKey("UploadVerbosity") + var args = Array(count: verbosity, repeatedValue: "-v") + args += [ "-C", toolChain+"/etc/avrdude.conf", "-p", boardProp["build.mcu"]!, "-c", proto!, "-P", port, "-U", "flash:w:build/"+board+"/"+dir.lastPathComponent+".hex:i"] @@ -125,6 +129,28 @@ class ASBuilder { args.append("-b") args.append(speed!) } + + // + // For Leonardo & the like, reset by opening port at 1200 baud + // + if leonardish { + if verbosity > 0 { + logOut.writeData("Opening \(port) at 1200 baud\n".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!) + } + if let dummyConnection = ASSerial.openPort(port, withSpeed: 1200) { + ASSerial.restorePort(dummyConnection.fileDescriptor) + dummyConnection.closeFile() + sleep(5) + for (var retry=0; retry < 10; ++retry) { + if (NSFileManager.defaultManager().fileExistsAtPath(port)) { + if verbosity > 0 { + logOut.writeData("Found port \(port) after \(retry) attempts.\n".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!) + } + break; + } + } + } + } let cmdLine = task!.launchPath+" "+(args as NSArray).componentsJoinedByString(" ")+"\n" logOut.writeData(cmdLine.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!) task!.arguments = args; diff --git a/AVRsack/ASProjDoc.swift b/AVRsack/ASProjDoc.swift index fe1a4df..42360c7 100644 --- a/AVRsack/ASProjDoc.swift +++ b/AVRsack/ASProjDoc.swift @@ -38,8 +38,8 @@ class ASProjDoc: NSDocument, NSOutlineViewDelegate, NSMenuDelegate { var mainEditor : ASFileNode? var currentTheme : UInt = 0 var fontSize : UInt = 12 - var themeObserver : AnyObject? - var serialObserver : AnyObject? + var themeObserver : AnyObject! + var serialObserver : AnyObject! dynamic var board = "uno" dynamic var programmer = "arduino" dynamic var port : String = "" @@ -102,8 +102,8 @@ class ASProjDoc: NSDocument, NSOutlineViewDelegate, NSMenuDelegate { } override func finalize() { saveCurEditor() - NSNotificationCenter.defaultCenter().removeObserver(themeObserver!) - NSNotificationCenter.defaultCenter().removeObserver(serialObserver!) + NSNotificationCenter.defaultCenter().removeObserver(themeObserver) + NSNotificationCenter.defaultCenter().removeObserver(serialObserver) } override func windowControllerDidLoadNib(aController: NSWindowController) { @@ -485,5 +485,9 @@ class ASProjDoc: NSDocument, NSOutlineViewDelegate, NSMenuDelegate { } buildProject(sender) } + + @IBAction func serialConnect(sender: AnyObject) { + ASSerialWin.showWindowWithPort(port) + } } diff --git a/AVRsack/ASSerial.h b/AVRsack/ASSerial.h index 686a6a8..5c489c9 100644 --- a/AVRsack/ASSerial.h +++ b/AVRsack/ASSerial.h @@ -14,5 +14,6 @@ extern NSString * kASSerialPortsChanged; + (NSArray *) ports; + (NSFileHandle *)openPort:(NSString *) port withSpeed:(int)speed; ++ (void)restorePort:(int)fileDescriptor; @end diff --git a/AVRsack/ASSerial.mm b/AVRsack/ASSerial.mm index 05fb65f..a1cd0ef 100644 --- a/AVRsack/ASSerial.mm +++ b/AVRsack/ASSerial.mm @@ -9,8 +9,11 @@ #import "ASSerial.h" #include +#include +#include -static dispatch_source_t watchSlashDev; +static dispatch_source_t watchSlashDev; +static NSMutableDictionary * savedAttrs; NSString * kASSerialPortsChanged = @"PortsChanged"; @@ -35,4 +38,39 @@ NSString * kASSerialPortsChanged = @"PortsChanged"; return cuPorts; } ++ (NSFileHandle *)openPort:(NSString *)port withSpeed:(int)speed { + int fd = open([port UTF8String], O_RDWR | O_NOCTTY | O_NONBLOCK); + if (fd < 0) + return nil; + if (ioctl(fd, TIOCEXCL) < 0) + goto failed; + termios origAttr, newAttr; + if (tcgetattr(fd, &origAttr) < 0) + goto failed; + newAttr = origAttr; + cfmakeraw(&newAttr); + cfsetspeed(&newAttr, speed); + newAttr.c_cflag |= CS8 | CCTS_OFLOW | CRTS_IFLOW; + newAttr.c_cflag &= ~(PARENB); + tcsetattr(fd, TCSANOW, &newAttr); + if (!savedAttrs) { + savedAttrs = [NSMutableDictionary dictionary]; + } + [savedAttrs setObject:[NSData dataWithBytes:&origAttr length:sizeof(origAttr)] forKey:[NSNumber numberWithInt:fd]]; + + return [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:NO]; +failed: + close(fd); + + return nil; +} + ++ (void)restorePort:(int)fileDescriptor { + NSNumber * fd = [NSNumber numberWithInt:fileDescriptor]; + if (NSData * attr = [savedAttrs objectForKey:fd]) { + tcsetattr(fileDescriptor, TCSADRAIN, (termios *)[attr bytes]); + [savedAttrs removeObjectForKey:fd]; + } +} + @end diff --git a/AVRsack/ASSerialWin.swift b/AVRsack/ASSerialWin.swift new file mode 100644 index 0000000..b13a654 --- /dev/null +++ b/AVRsack/ASSerialWin.swift @@ -0,0 +1,110 @@ +// +// ASSerialWin.swift +// AVRsack +// +// Created by Matthias Neeracher on 27/12/14. +// Copyright (c) 2014 Aere Perennius. All rights reserved. +// + +import Cocoa + +private var serialInstances = [String : ASSerialWin]() + +class ASSerialWin: NSWindowController { + @IBOutlet weak var portPopUp : NSPopUpButton! + @IBOutlet weak var inputLine : NSTextField! + @IBOutlet weak var logView : ACEView! + + var baudRate : Int32 = 9600 { + didSet(oldRate) { + if portHandle != nil { + connect(self) // Disconnect existing + connect(self) // Reconnect + } + } + } + var sendCR = false + var sendLF = true + var port = "" + var serialData = "" + var serialObserver : AnyObject! + dynamic var portHandle : NSFileHandle? + + class func showWindowWithPort(port: String) { + if let existing = serialInstances[port] { + existing.showWindow(self) + } else { + let newInstance = ASSerialWin(port:port) + serialInstances[port] = newInstance + newInstance.showWindow(self) + } + } + + convenience init(port: String) { + self.init(windowNibName:"ASSerialWin") + self.port = port + var nc = NSNotificationCenter.defaultCenter() + serialObserver = nc.addObserverForName(kASSerialPortsChanged, object: nil, queue: nil, usingBlock: { (NSNotification) in + self.rebuildPortMenu() + }) + } + + override func finalize() { + NSNotificationCenter.defaultCenter().removeObserver(serialObserver) + serialInstances.removeValueForKey(port) + } + + override func windowDidLoad() { + logView.setReadOnly(true) + logView.setShowPrintMargin(false) + rebuildPortMenu() + window?.title = port + connect(self) + super.windowDidLoad() + } + + func rebuildPortMenu() { + portPopUp.removeAllItems() + portPopUp.addItemsWithTitles(ASSerial.ports()) + portPopUp.selectItemWithTitle(port) + } + + @IBAction func selectPort(item: AnyObject) { + port = (item as NSPopUpButton).titleOfSelectedItem! + window?.title = port + } + + @IBAction func sendInput(AnyObject) { + } + + @IBAction func connect(AnyObject) { + if portHandle != nil { + ASSerial.restorePort(portHandle!.fileDescriptor) + portHandle!.closeFile() + portHandle = nil + } else { + portHandle = ASSerial.openPort(port, withSpeed: baudRate) + if portHandle != nil { + serialData = "" + logView.setString(serialData) + portHandle!.readabilityHandler = {(handle) in + let newData = handle.availableData + let newString = NSString(data: newData, encoding: NSASCIIStringEncoding)! + self.serialData += newString + dispatch_async(dispatch_get_main_queue(), { () -> Void in + self.logView.setString(self.serialData) + }) + } + } + } + } + + var connectButtonTitle : String { + get { + return (portHandle != nil) ? "Disconnect" : "Connect" + } + } + class func keyPathsForValuesAffectingConnectButtonTitle() -> NSSet { + return NSSet(object: "portHandle") + } +} diff --git a/AVRsack/ASSerialWin.xib b/AVRsack/ASSerialWin.xib new file mode 100644 index 0000000..591a06c --- /dev/null +++ b/AVRsack/ASSerialWin.xib @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSIsNotNil + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AVRsack/Base.lproj/ASProjDoc.xib b/AVRsack/Base.lproj/ASProjDoc.xib index 0de03fa..52cc028 100644 --- a/AVRsack/Base.lproj/ASProjDoc.xib +++ b/AVRsack/Base.lproj/ASProjDoc.xib @@ -1,7 +1,7 @@ - + - + @@ -114,6 +114,12 @@ + + + + + + @@ -210,6 +216,7 @@ + @@ -224,6 +231,7 @@ + diff --git a/AVRsack/Images.xcassets/ConnIcon.imageset/ConnectIcon 1x.png b/AVRsack/Images.xcassets/ConnIcon.imageset/ConnectIcon 1x.png new file mode 100644 index 0000000..795700f Binary files /dev/null and b/AVRsack/Images.xcassets/ConnIcon.imageset/ConnectIcon 1x.png differ diff --git a/AVRsack/Images.xcassets/ConnIcon.imageset/ConnectIcon 2x.png b/AVRsack/Images.xcassets/ConnIcon.imageset/ConnectIcon 2x.png new file mode 100644 index 0000000..45ca577 Binary files /dev/null and b/AVRsack/Images.xcassets/ConnIcon.imageset/ConnectIcon 2x.png differ diff --git a/AVRsack/Images.xcassets/ConnIcon.imageset/Contents.json b/AVRsack/Images.xcassets/ConnIcon.imageset/Contents.json new file mode 100644 index 0000000..06a9542 --- /dev/null +++ b/AVRsack/Images.xcassets/ConnIcon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "ConnectIcon 1x.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "ConnectIcon 2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Artwork/ConnectIcon.artx/Preview/preview.png b/Artwork/ConnectIcon.artx/Preview/preview.png new file mode 100644 index 0000000..87b9bb8 Binary files /dev/null and b/Artwork/ConnectIcon.artx/Preview/preview.png differ diff --git a/Artwork/ConnectIcon.artx/QuickLook/Preview.pdf b/Artwork/ConnectIcon.artx/QuickLook/Preview.pdf new file mode 100644 index 0000000..8eae2c9 Binary files /dev/null and b/Artwork/ConnectIcon.artx/QuickLook/Preview.pdf differ diff --git a/Artwork/ConnectIcon.artx/doc.thread b/Artwork/ConnectIcon.artx/doc.thread new file mode 100644 index 0000000..d477210 Binary files /dev/null and b/Artwork/ConnectIcon.artx/doc.thread differ