├── .gitignore ├── .npmignore ├── .travis.yml ├── spec ├── lib │ ├── driver.spec.js │ ├── cylon-beaglebone-stub.spec.js │ └── adaptor.spec.js └── helper.js ├── History.md ├── .jshintrc ├── Makefile ├── lib ├── cylon-beaglebone-stub.js ├── driver.js └── adaptor.js ├── package.json ├── LICENSE └── README.markdown /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | before_install: 3 | - npm update -g npm 4 | node_js: 5 | - "0.10" 6 | -------------------------------------------------------------------------------- /spec/lib/driver.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var BeagleboneStub = source("driver"); 4 | 5 | describe("Cylon.Drivers.BeagleboneStub", function() { 6 | var driver = new BeagleboneStub({ 7 | connection: {} 8 | }); 9 | 10 | it("has no drivers", function(){ 11 | expect(true).to.equal(true); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 0.1.4 / 2015-02-26 2 | ================== 3 | 4 | * Better tests 5 | 6 | 0.1.3 / 2015-02-25 7 | ================== 8 | 9 | * Random values on reads 10 | 11 | 0.1.2 / 2014-12-23 12 | ================== 13 | 14 | * Un-broke tests broken in 0.1.1 15 | 16 | 0.1.1 / 2014-12-23 17 | ================== 18 | 19 | * Added gpio and i2c dependencies 20 | 21 | 0.1.0 / 2014-12-23 22 | ================== 23 | 24 | * Initial checkin 25 | * Travis integration 26 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": true, 11 | "boss": true, 12 | "eqnull": true, 13 | "node": true, 14 | "predef": [ 15 | "Cylon", 16 | "Logger", 17 | 18 | "after", 19 | "bind", 20 | "constantly", 21 | "every", 22 | "hasProp", 23 | "proxyFunctionsToObject", 24 | "proxyTestStubs", 25 | "sleep", 26 | "slice", 27 | "subclass" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN := ./node_modules/.bin 2 | TEST_FILES := spec/helper.js $(shell find spec/lib -type f -name "*.js") 3 | 4 | VERSION := $(shell node -e "console.log(require('./package.json').version)") 5 | 6 | .PHONY: test bdd release 7 | 8 | test: 9 | @$(BIN)/mocha -r cylon --colors -R dot $(TEST_FILES) 10 | 11 | bdd: 12 | @$(BIN)/mocha -r cylon --colors -R spec $(TEST_FILES) 13 | 14 | release: 15 | @git push origin master 16 | @git checkout release ; git merge master ; git push ; git checkout master 17 | @git tag -m "$(VERSION)" v$(VERSION) 18 | @git push --tags 19 | @npm publish ./ 20 | -------------------------------------------------------------------------------- /lib/cylon-beaglebone-stub.js: -------------------------------------------------------------------------------- 1 | /* 2 | * cylon-beaglebone-stub 3 | * 4 | * Copyright (c) 2014-2015 Loren West 5 | * Licensed under the MIT license. 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var Adaptor = require('./adaptor'), 11 | Driver = require('./driver'); 12 | 13 | module.exports = { 14 | // Adaptors your module provides, e.g. ['spark'] 15 | adaptors: ['beaglebone-stub'], 16 | 17 | // Drivers your module provides, e.g. ['led', 'button'] 18 | drivers: [], 19 | 20 | // Modules intended to be used with yours, e.g. ['cylon-gpio'] 21 | dependencies: ['cylon-gpio', 'cylon-i2c'], 22 | 23 | adaptor: function(opts) { 24 | return new Adaptor(opts); 25 | }, 26 | 27 | driver: function(opts) { 28 | return new Driver(opts); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /spec/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env.NODE_ENV = 'test'; 4 | 5 | var path = require('path'); 6 | 7 | var chai = require('chai'), 8 | sinon = require('sinon'), 9 | sinonChai = require('sinon-chai'); 10 | 11 | chai.use(sinonChai); 12 | 13 | global.chai = chai; 14 | global.sinon = sinon; 15 | 16 | global.should = chai.should(); 17 | global.expect = chai.expect; 18 | global.assert = chai.assert; 19 | global.AssertionError = chai.AssertionError; 20 | 21 | global.spy = sinon.spy; 22 | global.stub = sinon.stub; 23 | 24 | // convenience function to require modules in lib directory 25 | global.source = function(module) { 26 | return require(path.normalize('./../lib/' + module)); 27 | }; 28 | 29 | var Cylon = require('cylon'); 30 | 31 | Cylon.config({ 32 | logging: { logger: false } 33 | }); 34 | 35 | Cylon.Logger.setup(false); 36 | -------------------------------------------------------------------------------- /lib/driver.js: -------------------------------------------------------------------------------- 1 | /* 2 | * cylon-beaglebone-stub driver 3 | * 4 | * Copyright (c) 2014-2015 Loren West 5 | * Licensed under the MIT license. 6 | */ 7 | 8 | "use strict"; 9 | 10 | var Cylon = require('cylon'); 11 | 12 | var Driver = module.exports = function Driver(opts) { 13 | Driver.__super__.constructor.apply(this, arguments); 14 | 15 | opts = opts || {}; 16 | 17 | // Include a list of commands that will be made available to the API. 18 | this.commands = { 19 | // This is how you register a command function for the API; 20 | // the command should be added to the prototype, see below. 21 | hello: this.hello 22 | }; 23 | }; 24 | 25 | Cylon.Utils.subclass(Driver, Cylon.Driver); 26 | 27 | Driver.prototype.start = function(callback) { 28 | callback(); 29 | }; 30 | 31 | Driver.prototype.halt = function(callback) { 32 | callback(); 33 | }; 34 | 35 | Driver.prototype.hello = function() { 36 | Cylon.Logger.info('Hello World!'); 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cylon-beaglebone-stub", 3 | "version": "0.1.4", 4 | "main": "lib/cylon-beaglebone-stub.js", 5 | "description": "Beaglebone stub for testing", 6 | "homepage": "https://github.com/lorenwest/cylon-beaglebone-stub", 7 | "keywords": ["cylon", "beaglebone", "robot"], 8 | "author": "Loren West ", 9 | 10 | "repository": { 11 | "type" : "git", 12 | "url" : "https://github.com/lorenwest/cylon-beaglebone-stub.git" 13 | }, 14 | 15 | "licenses": [ 16 | "MIT" 17 | ], 18 | 19 | "devDependencies": { 20 | "sinon-chai": "2.6.0", 21 | "chai": "1.9.2", 22 | "mocha": "1.21.5", 23 | "sinon": "1.10.3" 24 | }, 25 | 26 | "peerDependencies": { 27 | "bluebird": ">=2.3.11 <3.0.0", 28 | "cylon": ">= 0.21.0 < 1", 29 | "cylon-gpio": ">= 0.21.0 < 1", 30 | "cylon-i2c": ">= 0.18.0 < 1" 31 | }, 32 | 33 | "scripts": { 34 | "test": "make bdd" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014-2015, Loren West and other contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to 5 | deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /spec/lib/cylon-beaglebone-stub.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var module = source("cylon-beaglebone-stub"); 4 | 5 | var Adaptor = source('adaptor'), 6 | Driver = source('driver'); 7 | 8 | describe("Cylon.BeagleboneStub", function() { 9 | describe("#adaptors", function() { 10 | it('is an array of supplied adaptors', function() { 11 | expect(module.adaptors).to.be.eql(['beaglebone-stub']); 12 | }); 13 | }); 14 | 15 | describe("#drivers", function() { 16 | it('is an array of supplied drivers', function() { 17 | expect(module.drivers).to.be.eql([]); 18 | }); 19 | }); 20 | 21 | describe("#dependencies", function() { 22 | it('is an array of supplied dependencies', function() { 23 | expect(module.dependencies).to.be.eql(['cylon-gpio','cylon-i2c']); 24 | }); 25 | }); 26 | 27 | describe("#driver", function() { 28 | it("returns an instance of the Driver", function() { 29 | expect(module.driver()).to.be.instanceOf(Driver); 30 | }); 31 | }); 32 | 33 | describe("#adaptor", function() { 34 | it("returns an instance of the Adaptor", function() { 35 | expect(module.adaptor()).to.be.instanceOf(Adaptor); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Cylon.js Beaglebone stub for testing 2 | 3 | [![NPM](https://nodei.co/npm/cylon-beaglebone-stub.svg?downloads=true&downloadRank=true)](https://nodei.co/npm/cylon-beaglebone-stub/)   4 | [![Build Status](https://secure.travis-ci.org/lorenwest/cylon-beaglebone-stub.svg?branch=master)](https://travis-ci.org/lorenwest/cylon-beaglebone-stub)   5 | [release notes](https://github.com/lorenwest/cylon-beaglebone-stub/blob/master/History.md) 6 | 7 | This repository conains what looks like a real beaglebone, but 8 | is stubbed out so robots can be built and tested without being 9 | connected to a beaglebone. 10 | 11 | For more information about Cylon, check out the repo at 12 | https://github.com/hybridgroup/cylon 13 | 14 | ## Getting Started 15 | 16 | Install the module with: `npm install cylon-beaglebone-stub` 17 | 18 | ## Examples 19 | 20 | See https://github.com/hybridgroup/cylon-beaglebone for the real 21 | beaglebone connector. 22 | 23 | ## Connecting 24 | 25 | ```javascript 26 | var Cylon = require('cylon'); 27 | 28 | Cylon.robot({ 29 | connections: { 30 | beaglebone: {adaptor: 'beaglebone-stub' } 31 | }, 32 | devices: { 33 | pump_motor_on: { 34 | driver: 'direct-pin', 35 | pin: 'P9_11', 36 | connection: 'beaglebone' 37 | } 38 | } 39 | 40 | work: function(my) { 41 | my.pump_motor_on.digitalWrite(1); 42 | } 43 | }).start(); 44 | ``` 45 | 46 | ## License 47 | 48 | Copyright (c) 2014-2015 Loren West and other contributors. 49 | See `LICENSE` for more details 50 | -------------------------------------------------------------------------------- /spec/lib/adaptor.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Cylon = require('cylon'); 4 | var StubAdaptor = source("adaptor"); 5 | 6 | describe("Cylon.Adaptors.BeagleboneStub", function() { 7 | var adaptor = new StubAdaptor(); 8 | 9 | it("Is a sub-class of Cylon.Adaptor", function() { 10 | expect(adaptor).to.be.instanceOf(Cylon.Adaptor); 11 | }); 12 | 13 | it("Has the right name", function() { 14 | expect(adaptor.firmwareName()).to.equal('BeagleboneStub'); 15 | }); 16 | 17 | it("Calls the connect callback", function(done) { 18 | adaptor.connect(done); 19 | }); 20 | 21 | it("Calls the disconnect callback", function(done) { 22 | adaptor.disconnect(done); 23 | }); 24 | 25 | it("Emits the disconnect event", function(done) { 26 | adaptor.once('disconnect', done); 27 | adaptor.disconnect(); 28 | }); 29 | 30 | it("Validates digital pins", function() { 31 | try { 32 | adaptor.digitalWrite('bad_pin', 34); 33 | expect(false).to.equal(true); 34 | } 35 | catch (e) { 36 | expect(true).to.equal(true); 37 | } 38 | 39 | // Expect to not throw an exception 40 | adaptor.digitalWrite('P9_18', 1); 41 | }); 42 | 43 | 44 | it("Returns pins and values", function() { 45 | var pins = adaptor.pins; 46 | expect(typeof pins).to.equal('object'); 47 | expect(pins['P9_18']).to.equal(1); 48 | }); 49 | 50 | it("Reads written digital values", function(done) { 51 | adaptor.digitalRead('P9_18', function(err, value) { 52 | expect(err).to.equal(null); 53 | expect(value === 0 || value === 1).to.equal(true); 54 | done(); 55 | }); 56 | }); 57 | 58 | it("Emits written digital values", function(done) { 59 | adaptor.digitalRead('P9_18'); 60 | adaptor.once('digitalRead', function(pin, value) { 61 | expect(pin).to.equal('P9_18'); 62 | expect(value === 0 || value === 1).to.equal(true); 63 | done(); 64 | }); 65 | }); 66 | 67 | it("Validates analog pins", function() { 68 | try { 69 | adaptor.analogRead('P9_11'); 70 | expect(false).to.equal(true); 71 | } 72 | catch (e) { 73 | expect(true).to.equal(true); 74 | } 75 | 76 | // Expect to not throw an exception 77 | adaptor.analogWrite('P9_40', .8); 78 | }); 79 | 80 | it("Reads written analog values", function(done) { 81 | adaptor.analogRead('P9_40', function(err, value) { 82 | expect(err).to.equal(null); 83 | expect(value >= 0 && value < 1).to.equal(true); 84 | done(); 85 | }); 86 | }); 87 | 88 | it("Validates PWM pins", function() { 89 | try { 90 | adaptor.pwmWrite('P9_13'); 91 | expect(false).to.equal(true); 92 | } 93 | catch (e) { 94 | expect(true).to.equal(true); 95 | } 96 | 97 | // Expect to not throw an exception 98 | adaptor.pwmWrite('P9_14', .8); 99 | }); 100 | 101 | it("Emits written PWM values", function(done) { 102 | adaptor.pwmWrite('P9_14', .23); 103 | adaptor.once('pwmWrite', function(pin, value) { 104 | expect(value >= 0 && value < 1).to.equal(true); 105 | done(); 106 | }); 107 | }); 108 | 109 | it("Writes servo values as PWM values", function(done) { 110 | adaptor.servoWrite('P9_42', .85, 2000, 2); 111 | adaptor.once('servoWrite', function(pin, value) { 112 | expect(value >= 0 && value < 1).to.equal(true); 113 | done(); 114 | }); 115 | }); 116 | 117 | it("Emits written i2c values", function(done) { 118 | adaptor.i2cWrite('someAddress', 'someCmd', ['b','u','f'], function(err) { 119 | expect(err).to.equal(null); 120 | done(); 121 | }); 122 | }); 123 | 124 | it("Reads written i2c values", function(done) { 125 | adaptor.i2cRead('someAddress', 'someCmd', 30, function(err, buf) { 126 | expect(err).to.equal(null); 127 | expect(Array.isArray(buf)).to.equal(true); 128 | expect(buf.length).to.equal(3); 129 | done(); 130 | }); 131 | }); 132 | 133 | }); 134 | -------------------------------------------------------------------------------- /lib/adaptor.js: -------------------------------------------------------------------------------- 1 | /* 2 | * cylon-beaglebone-stub adaptor 3 | * 4 | * Copyright (c) 2014-2015 Loren West 5 | * Licensed under the MIT license. 6 | */ 7 | 8 | "use strict"; 9 | 10 | var Cylon = require('cylon'); 11 | var logger = Cylon.Logger; 12 | var NOP = function() {}; 13 | var Promise = require('bluebird'); 14 | 15 | var BeagleboneStub = module.exports = function BeagleboneStub(opts) { 16 | var t = this; 17 | BeagleboneStub.__super__.constructor.apply(t, arguments); 18 | 19 | t.pins = {}; 20 | t.i2cAddresses = {}; 21 | t.opts = opts || {}; 22 | }; 23 | 24 | Cylon.Utils.subclass(BeagleboneStub, Cylon.Adaptor); 25 | var proto = BeagleboneStub.prototype; 26 | 27 | var PINS = [ 28 | "P8_3", "P8_4", "P8_5", "P8_6", "P8_7", "P8_8", "P8_9", "P8_10", "P8_11", "P8_12", 29 | "P8_13", "P8_14", "P8_15", "P8_16", "P8_17", "P8_18", "P8_19", "P8_20", "P8_21", 30 | "P8_22", "P8_23", "P8_24", "P8_25", "P8_26", "P8_27", "P8_28", "P8_29", "P8_30", 31 | "P8_31", "P8_32", "P8_33", "P8_34", "P8_37", "P8_38", "P8_39", "P8_40", "P8_41", 32 | "P8_42", "P8_43", "P8_44", "P8_45", "P8_46", "P9_11", "P9_12", "P9_13", "P9_14", 33 | "P9_15", "P9_16", "P9_17", "P9_18", "P9_19", "P9_20", "P9_21", "P9_22", "P9_23", 34 | "P9_24", "P9_25", "P9_26", "P9_27", "P9_28", "P9_29", "P9_30", "P9_31" 35 | ]; 36 | 37 | var PWM_PINS = [ 38 | "P9_14", "P9_21", "P9_22", "P9_29", "P9_42", "P8_13", "P8_34", "P8_45", "P8_46" 39 | ]; 40 | 41 | var ANALOG_PINS = [ 42 | "P9_39", "P9_40", "P9_37", "P9_38", "P9_33", "P8_36", "P8_35" 43 | ]; 44 | 45 | proto.commands = [ 46 | 'pins', 'analogRead', 'analogWrite', 'digitalRead', 'digitalWrite', 'pwmWrite', 47 | 'servoWrite', 'firmwareName', 'i2cWrite', 'i2cRead' 48 | ]; 49 | 50 | proto.firmwareName = function() { 51 | return 'BeagleboneStub'; 52 | }; 53 | 54 | proto.connect = function(callback) { 55 | var my = this; 56 | callback = callback || NOP; 57 | logger.debug("Connecting to '" + this.name + "'..."); 58 | process.nextTick(function(){ 59 | my.emit('connect'); 60 | callback(); 61 | }); 62 | }; 63 | 64 | proto.disconnect = function(callback) { 65 | var my = this; 66 | callback = callback || NOP; 67 | logger.debug("Disconnecting from '" + this.name + "'..."); 68 | process.nextTick(function(){ 69 | my.emit('disconnect'); 70 | callback(); 71 | }); 72 | }; 73 | 74 | proto.analogRead = function(pinNum, callback) { 75 | var my = this; 76 | var deferred = Promise.defer(); 77 | var promise = deferred.promise.nodeify(callback); 78 | callback = callback || NOP; 79 | my._validateAnalogPin(pinNum); 80 | process.nextTick(function() { 81 | my.pins[pinNum] = Math.random(); 82 | my.emit('analogRead', pinNum, my.pins[pinNum]); 83 | return deferred.resolve(my.pins[pinNum]); 84 | }); 85 | return promise; 86 | }; 87 | 88 | // This is for testing instrumentation. There is no real analogWrite. 89 | proto.analogWrite = function(pinNum, value) { 90 | var my = this; 91 | my._validateAnalogPin(pinNum); 92 | my.pins[pinNum] = value; 93 | process.nextTick(function() { 94 | my.emit('analogWrite', pinNum, value); 95 | }); 96 | return value; 97 | }; 98 | 99 | proto.digitalRead = function(pinNum, callback) { 100 | var my = this; 101 | var deferred = Promise.defer(); 102 | var promise = deferred.promise.nodeify(callback); 103 | my._validateDigitalPin(pinNum); 104 | process.nextTick(function() { 105 | my.pins[pinNum] = Math.floor(Math.random() * 2); 106 | my.emit('digitalRead', pinNum, my.pins[pinNum]); 107 | return deferred.resolve(my.pins[pinNum]); 108 | }); 109 | return promise; 110 | }; 111 | 112 | proto.digitalWrite = function(pinNum, value, callback) { 113 | var my = this; 114 | var deferred = Promise.defer(); 115 | var promise = deferred.promise.nodeify(callback); 116 | my._validateDigitalPin(pinNum); 117 | my.pins[pinNum] = value; 118 | process.nextTick(function() { 119 | my.emit('digitalWrite', pinNum, value); 120 | return deferred.resolve(value); 121 | }); 122 | return promise; 123 | }; 124 | 125 | proto.pwmWrite = function(pinNum, scaledDuty, freq, pulseWidth, eventName) { 126 | var my = this; 127 | my._validatePWMPin(pinNum); 128 | my.pins[pinNum] = scaledDuty; 129 | process.nextTick(function() { 130 | eventName = eventName || 'pwm'; 131 | my.emit(eventName + 'Write', pinNum, my.pins[pinNum]); 132 | }); 133 | }; 134 | 135 | proto.servoWrite = function(pinNum, scaledDuty, freq, pulseWidth) { 136 | var my = this; 137 | var args = Array.prototype.slice.call(arguments); 138 | args.push('servo'); 139 | my.pwmWrite.apply(this, args); 140 | }; 141 | 142 | proto.i2cWrite = function(address, cmd, buff, callback) { 143 | var my = this; 144 | var deferred = Promise.defer(); 145 | var promise = deferred.promise.nodeify(callback); 146 | buff = buff != null ? buff : []; 147 | my.i2cAddresses[address] = buff; 148 | process.nextTick(function(){ 149 | return deferred.resolve(); 150 | }); 151 | return promise; 152 | }; 153 | 154 | proto.i2cRead = function(address, cmd, length, callback) { 155 | var my = this; 156 | var deferred = Promise.defer(); 157 | var promise = deferred.promise.nodeify(callback); 158 | process.nextTick(function(){ 159 | my.i2cAddresses[address] = [Math.random(), Math.random(), Math.random()]; 160 | return deferred.resolve(my.i2cAddresses[address]); 161 | }); 162 | return promise; 163 | }; 164 | 165 | proto._validateDigitalPin = function(pinNum) { 166 | if (PINS.indexOf(pinNum) < 0) { 167 | throw new Error("Pin '" + pinNum + "' not a digital pin."); 168 | } 169 | } 170 | proto._validateAnalogPin = function(pinNum) { 171 | if (ANALOG_PINS.indexOf(pinNum) < 0) { 172 | throw new Error("Pin '" + pinNum + "' not an analog pin."); 173 | } 174 | } 175 | proto._validatePWMPin = function(pinNum) { 176 | if (PWM_PINS.indexOf(pinNum) < 0) { 177 | throw new Error("Pin '" + pinNum + "' not a PWM pin."); 178 | } 179 | } 180 | --------------------------------------------------------------------------------