diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c277ec4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.settings +/.classpath +/.project +/target diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4239f4d --- /dev/null +++ b/pom.xml @@ -0,0 +1,33 @@ + + 4.0.0 + com.buildbrighton.ccTalk + ccTalk + ccTalk + 0.0.1-SNAPSHOT + ccTalk protocol implementation in Java + + + + junit + junit + 4.8.2 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + + + + + \ No newline at end of file diff --git a/src/main/java/com/buildbrighton/cctalk/CCTalkMessage.java b/src/main/java/com/buildbrighton/cctalk/CCTalkMessage.java new file mode 100644 index 0000000..fb113d5 --- /dev/null +++ b/src/main/java/com/buildbrighton/cctalk/CCTalkMessage.java @@ -0,0 +1,50 @@ +package com.buildbrighton.cctalk; + +import java.util.Arrays; + + +public class CCTalkMessage { + protected byte[] data; + protected byte dest; + protected Header header; + protected byte source; + + public byte getDest() { + return dest; + } + + public byte[] getData() { + return data; + } + + public Header getHeader() { + return header; + } + + public byte getSource() { + return source; + } + + public void setDest(byte dest) { + this.dest = dest; + } + + public void setData(byte[] data) { + this.data = data; + } + + public void setHeader(Header header) { + this.header = header; + } + + public void setSource(byte source) { + this.source = source; + } + + @Override + public String toString() { + return "CCTalkMessage [data=" + Arrays.toString(data) + ", dest=" + + dest + ", header=" + header + ", source=" + source + "]"; + } + +} diff --git a/src/main/java/com/buildbrighton/cctalk/ChecksumMessageParser.java b/src/main/java/com/buildbrighton/cctalk/ChecksumMessageParser.java new file mode 100644 index 0000000..8bf47a8 --- /dev/null +++ b/src/main/java/com/buildbrighton/cctalk/ChecksumMessageParser.java @@ -0,0 +1,41 @@ +package com.buildbrighton.cctalk; + +import java.util.Arrays; + +import com.buildbrighton.cctalk.interfaces.CCTalkMessageParser; + +public class ChecksumMessageParser implements CCTalkMessageParser { + + @Override + public CCTalkMessage parseMessage(byte[] message) + throws InvalidMessageException { + if (message == null || message.length < 2) { + throw new InvalidMessageException( + "Message was less than two bytes long, therefore did not contain a data length byte"); + } + //Convert unsigned byte to signed + int dataLength = (int) (message[1] & 0xFF); + // data + source, destination, checksum, numBytes, header + int expectedMessageLength = dataLength + 5; + + if(message.length < expectedMessageLength){ + throw new InvalidMessageException("Message was shorter than expected length of "+expectedMessageLength+" bytes"); + } + + int sum = 0; + for(int i = 4; i < expectedMessageLength; i++){ + sum += (message[i] & 0xff); + } + if(sum % 256 != 0){ + throw new InvalidMessageException("Invalid checksum result"); + } + + CCTalkMessage ccMessage = new CCTalkMessage(); + ccMessage.setDest(message[0]); + ccMessage.setSource(message[2]); + ccMessage.setHeader(Header.valueOf(message[3])); + ccMessage.setData(Arrays.copyOfRange(message, 4, expectedMessageLength - 1)); + + return ccMessage; + } +} diff --git a/src/main/java/com/buildbrighton/cctalk/Header.java b/src/main/java/com/buildbrighton/cctalk/Header.java new file mode 100644 index 0000000..e5e0472 --- /dev/null +++ b/src/main/java/com/buildbrighton/cctalk/Header.java @@ -0,0 +1,199 @@ +package com.buildbrighton.cctalk; + +public enum Header { + FactorySetUpAndTest(255), + SimplePoll(254), + AddressPoll(253), + AddressClash(252), + AddressChange(251), + AddressRandom(250), + RequestPollingPriority(249), + RequestStatus(248), + RequestVariableSet(247), + RequestManufacturerId(246), + RequestEquipmentCategoryId(245), + RequestProductCode(244), + RequestDatabaseVersion(243), + RequestSerialNumber(242), + RequestSoftwareRevision(241), + TestSolenoids(240), + OperateMotors(239), + TestOutputLines(238), + ReadInputLines(237), + ReadOptoStates(236), + ReadLastCreditOrErrorCode(235), + IssueGuardCode(234), + LatchOutputLines(233), + PerformSelfCheck(232), + ModifyInhibitStatus(231), + RequestInhibitStatus(230), + ReadBufferedCreditOrErrorCodes(229), + ModifyMasterInhibitStatus(228), + RequestMasterInhibitStatus(227), + RequestInsertionCounter(226), + RequestAcceptCounter(225), + DispenseCoins(224), + DispenseChange(223), + ModifySorterOverrideStatus(222), + RequestSorterOverrideStatus(221), + OneShotCredit(220), + EnterNewPinNumber(219), + EnterPinNumber(218), + RequestPayoutHighLowStatus(217), + RequestDataStorageAvailability(216), + ReadDataBlock(215), + WriteDataBlock(214), + RequestOptionFlags(213), + RequestCoinPosition(212), + PowerManagementControl(211), + ModifySorterPaths(210), + RequestSorterPaths(209), + ModifyPayoutAbsoluteCount(208), + RequestPayoutAbsoluteCount(207), + EmptyPayout(206), + RequestAuditInformationBlock(205), + MeterControl(204), + DisplayControl(203), + TeachModeControl(202), + RequestTeachStatus(201), + UploadCoinData(200), + ConfigurationToEeprom(199), + CountersToEeprom(198), + CalculateRomChecksum(197), + RequestCreationDate(196), + RequestLastModificationDate(195), + RequestRejectCounter(194), + RequestFraudCounter(193), + RequestBuildCode(192), + KeypadControl(191), + RequestPayoutStatus(190), + ModifyDefaultSorterPath(189), + RequestDefaultSorterPath(188), + ModifyPayoutCapacity(187), + RequestPayoutCapacity(186), + ModifyCoinId(185), + RequestCoinId(184), + UploadWindowData(183), + DownloadCalibrationInfo(182), + ModifySecuritySetting(181), + RequestSecuritySetting(180), + ModifyBankSelect(179), + RequestBankSelect(178), + HandheldFunction(177), + RequestAlarmCounter(176), + ModifyPayoutFloat(175), + RequestPayoutFloat(174), + RequestThermistorReading(173), + EmergencyStop(172), + RequestHopperCoin(171), + RequestBaseYear(170), + RequestAddressMode(169), + RequestHopperDispenseCount(168), + DispenseHopperCoins(167), + RequestHopperStatus(166), + ModifyVariableSet(165), + EnableHopper(164), + TestHopper(163), + ModifyInhibitAndOverrideregisters(162), + PumpRng(161), + RequestCipherKey(160), + ReadBufferedBillEvents(159), + ModifyBillId(158), + RequestBillId(157), + RequestCountryScalingFactor(156), + RequestBillPosition(155), + RouteBill(154), + ModifyBillOperatingMode(153), + RequestBillOperatingMode(152), + TestLamps(151), + RequestIndividualAcceptCounter(150), + RequestIndividualErrorCounter(149), + ReadOptoVoltages(148), + PerformStackerCycle(147), + OperateBiDirectionalMotors(146), + RequestCurrencyRevision(145), + UploadBillTables(144), + BeginBillTableUpgrade(143), + FinishBillTableUpgrade(142), + RequestFirmwareUpgradeCapability(141), + UploadFirmware(140), + BeginFirmwareUpgrade(139), + FinishFirmwareUpgrade(138), + SwitchEncryptionCode(137), + StoreEncryptionCode(136), + SetAcceptLimit(135), + DispenseHopperValue(134), + RequestHopperPollingValue(133), + EmergencyStopValue(132), + RequestHopperCoinValue(131), + RequestIndexedHopperDispenseCount(130), + ReadBarcodeData(129), + RequestMoneyIn(128), + RequestMoneyOut(127), + ClearMoneyCounters(126), + PayMoneyOut(125), + VerifyMoneyOut(124), + RequestActivityRegister(123), + RequestErrorStatus(122), + PurgeHopper(121), + ModifyHopperBalance(120), + RequestHopperBalance(119), + ModifyCashboxValue(118), + RequestCashboxValue(117), + ModifyRealTimeClock(116), + RequestRealTimeClock(115), + RequestUsbId(114), + SwitchBaudRate(113), + ReadEncryptedEvents(112), + RequestEncryptionSupport(111), + SwitchEncryptionKey(110), + RequestEncryptedHopperStatus(109), + RequestEncryptedMonetaryId(108), + ExpansionHeader4(103), + ExpansionHeader3(102), + ExpansionHeader2(101), + ExpansionHeader1(100), + BusyMessage(006), + NakMessage(005), + RequestCommsRevision(004), + ClearCommsStatusVariables(003), + RequestCommsStatusVariables(002), + ResetDevice(001), + ReturnMessage(000); + + private byte value; + + Header(int value){ + this.value = (byte) (0xff & value); + } + + public byte getValue(){ + return value; + } + + /** + * returns the current value byte as a binary string. + */ + public String toString(){ + StringBuilder sb = new StringBuilder(); + sb.append(this.name()); + sb.append('('); + byte valCopy = value; + for (int i = 0; i < 8; i++) + { + sb.append((valCopy & 128) == 0 ? 0 : 1); + valCopy <<= 1; + } + sb.append(')'); + return sb.toString(); + } + + public static Header valueOf(byte b){ + for(Header h : Header.values()){ + if(h.value == b){ + return h; + } + } + return null; + } +} diff --git a/src/main/java/com/buildbrighton/cctalk/InvalidMessageException.java b/src/main/java/com/buildbrighton/cctalk/InvalidMessageException.java new file mode 100644 index 0000000..61bc10c --- /dev/null +++ b/src/main/java/com/buildbrighton/cctalk/InvalidMessageException.java @@ -0,0 +1,30 @@ +package com.buildbrighton.cctalk; + +/** + * Thrown when an invalid CCTalk message is encountered + * @author tub + */ +public class InvalidMessageException extends Exception { + + private static final long serialVersionUID = -3258256268402518828L; + + private String message; + + public String getMessage() { + return message; + } + + public InvalidMessageException(){ + + } + + public InvalidMessageException(String message){ + this.message = message; + } + + @Override + public String toString() { + return "InvalidMessageException [message=" + message + "]"; + } + +} diff --git a/src/main/java/com/buildbrighton/cctalk/interfaces/CCTalkListener.java b/src/main/java/com/buildbrighton/cctalk/interfaces/CCTalkListener.java new file mode 100644 index 0000000..5c9e529 --- /dev/null +++ b/src/main/java/com/buildbrighton/cctalk/interfaces/CCTalkListener.java @@ -0,0 +1,7 @@ +package com.buildbrighton.cctalk.interfaces; + +import com.buildbrighton.cctalk.CCTalkMessage; + +public interface CCTalkListener { + public void messageReceived(CCTalkMessage message); +} diff --git a/src/main/java/com/buildbrighton/cctalk/interfaces/CCTalkMessageParser.java b/src/main/java/com/buildbrighton/cctalk/interfaces/CCTalkMessageParser.java new file mode 100644 index 0000000..26922b4 --- /dev/null +++ b/src/main/java/com/buildbrighton/cctalk/interfaces/CCTalkMessageParser.java @@ -0,0 +1,8 @@ +package com.buildbrighton.cctalk.interfaces; + +import com.buildbrighton.cctalk.CCTalkMessage; +import com.buildbrighton.cctalk.InvalidMessageException; + +public interface CCTalkMessageParser { + public CCTalkMessage parseMessage(byte[] message) throws InvalidMessageException; +} diff --git a/src/main/java/com/buildbrighton/cctalk/interfaces/CCTalkMessageSerializer.java b/src/main/java/com/buildbrighton/cctalk/interfaces/CCTalkMessageSerializer.java new file mode 100644 index 0000000..c77a2c7 --- /dev/null +++ b/src/main/java/com/buildbrighton/cctalk/interfaces/CCTalkMessageSerializer.java @@ -0,0 +1,7 @@ +package com.buildbrighton.cctalk.interfaces; + +import com.buildbrighton.cctalk.CCTalkMessage; + +public interface CCTalkMessageSerializer { + public byte[] serialize(CCTalkMessage message); +} diff --git a/src/test/java/com/buildbrighton/cctalk/ChecksumMessageParserTest.java b/src/test/java/com/buildbrighton/cctalk/ChecksumMessageParserTest.java new file mode 100644 index 0000000..884524b --- /dev/null +++ b/src/test/java/com/buildbrighton/cctalk/ChecksumMessageParserTest.java @@ -0,0 +1,13 @@ +package com.buildbrighton.cctalk; + +import org.junit.Test; + + +public class ChecksumMessageParserTest { + + @Test + public void testParseMessage() throws InvalidMessageException{ + ChecksumMessageParser mp = new ChecksumMessageParser(); + mp.parseMessage(new byte[]{0x00, 0x01, 0x02, 0x03}); + } +} diff --git a/src/test/java/com/buildbrighton/cctalk/HeaderTest.java b/src/test/java/com/buildbrighton/cctalk/HeaderTest.java new file mode 100644 index 0000000..1cfdb3f --- /dev/null +++ b/src/test/java/com/buildbrighton/cctalk/HeaderTest.java @@ -0,0 +1,22 @@ +package com.buildbrighton.cctalk; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class HeaderTest { + + @Test + public void testToString() { + assertEquals(Header.ReturnMessage.toString(), "ReturnMessage(00000000)"); + assertEquals(Header.ResetDevice.toString(), "ResetDevice(00000001)"); + assertEquals(Header.FactorySetUpAndTest.toString(), "FactorySetUpAndTest(11111111)"); + } + + @Test + public void testValueOf(){ + byte value = Header.ConfigurationToEeprom.getValue(); + assertEquals(Header.valueOf(value), Header.ConfigurationToEeprom); + } + +}