From 7193ee5972fec2cc4a24314f0f0e4961ec345448 Mon Sep 17 00:00:00 2001 From: Stefan `Sec` Zehl Date: Sat, 19 Nov 2011 20:36:16 +0100 Subject: [PATCH 01/37] Add old rudimentary rem0te code --- firmware/applications/remote/config.c | 38 ++++ firmware/applications/remote/remote.c | 272 ++++++++++++++++++++++++++ firmware/applications/remote/serial.c | 139 +++++++++++++ firmware/applications/remote/util.c | 128 ++++++++++++ firmware/applications/remote/uuid.c | 26 +++ 5 files changed, 603 insertions(+) create mode 100644 firmware/applications/remote/config.c create mode 100644 firmware/applications/remote/remote.c create mode 100644 firmware/applications/remote/serial.c create mode 100644 firmware/applications/remote/util.c create mode 100644 firmware/applications/remote/uuid.c diff --git a/firmware/applications/remote/config.c b/firmware/applications/remote/config.c new file mode 100644 index 0000000..0d8df7c --- /dev/null +++ b/firmware/applications/remote/config.c @@ -0,0 +1,38 @@ +#include + +#include "basic/basic.h" + +#include "lcd/print.h" +#include "lcd/display.h" + +#include "filesystem/ff.h" + +#include + +/**************************************************************************/ + +void readcfg(void) { + readConfig(); +}; + +void savecfg(void){ + saveConfig(); +}; + +void applycfg(void){ + applyConfig(); +}; + +void show(void){ + lcdClear(); + lcdPrint("time:"); lcdPrintInt(globalconfig.time); lcdNl(); + lcdPrint("btrig:"); lcdPrintInt(globalconfig.backlighttrigger); lcdNl(); + lcdPrint("bval:"); lcdPrintInt(globalconfig.backlightvalue); lcdNl(); + lcdPrint("lcd:"); lcdPrintInt(globalconfig.lcdstate); lcdNl(); + lcdPrint("priv:"); lcdPrintInt(globalconfig.privacy); lcdNl(); + lcdRefresh(); +}; + +void lcdmirror(void){ + lcdToggleFlag(LCD_MIRRORX); +}; diff --git a/firmware/applications/remote/remote.c b/firmware/applications/remote/remote.c new file mode 100644 index 0000000..0c2dbb5 --- /dev/null +++ b/firmware/applications/remote/remote.c @@ -0,0 +1,272 @@ +#include + +#include "basic/basic.h" +#include "basic/byteorder.h" + +#include "lcd/lcd.h" +#include "lcd/print.h" + +#include "funk/nrf24l01p.h" +#include "usbcdc/usb.h" +#include "usbcdc/usbcore.h" +#include "usbcdc/usbhw.h" +#include "usbcdc/cdcuser.h" +#include "usbcdc/cdc_buf.h" +#include "usbcdc/util.h" + +#include + +#define REMOTE_CHANNEL 91 +#define REMOTE_MAC "REM0T" + +#if CFG_USBMSC +#error "MSC is defined" +#endif + +#if !CFG_USBCDC +#error "CDC is not defined" +#endif + +/**************************************************************************/ + +#include "SECRETS" + +void r_init(void){ + nrf_init(); + + struct NRF_CFG config = { + .channel= REMOTE_CHANNEL, + .txmac= REMOTE_MAC, + .nrmacs=1, + .mac0= REMOTE_MAC, + .maclen ="\x10", + }; + + nrf_config_set(&config); +}; + +void s_init(void){ + usbCDCInit(); + nrf_init(); + + struct NRF_CFG config = { + .channel= REMOTE_CHANNEL, + .txmac= REMOTE_MAC, + .nrmacs=1, + .mac0= REMOTE_MAC, + .maclen ="\x10", + }; + + nrf_config_set(&config); +}; + + void process(uint8_t * input){ + __attribute__ ((aligned (4))) uint8_t buf[32]; + puts("process: "); + puts(input); + puts("\r\n"); + if(input[0]=='M'){ + buf[0]=0x10; // Length: 16 bytes + buf[1]='M'; // Proto + buf[2]=0x01; + buf[3]=0x01; // Unused + + uint32touint8p(0,buf+4); + + uint32touint8p(0x41424344,buf+8); + + buf[12]=0xff; // salt (0xffff always?) + buf[13]=0xff; + nrf_snd_pkt_crc_encr(16,buf,remotekey); + nrf_rcv_pkt_start(); + }; + +}; + +#define INPUTLEN 99 +void r_recv(void){ + __attribute__ ((aligned (4))) uint8_t buf[32]; + int len; + + uint8_t input[INPUTLEN+1]; + int inputptr=0; + + nrf_rcv_pkt_start(); + puts("D start"); + + getInputWaitRelease(); + + while(!getInputRaw()){ + delayms(100); + + // Input + int l=INPUTLEN-inputptr; + CDC_OutBufAvailChar (&l); + + if(l>0){ + CDC_RdOutBuf (input+inputptr, &l); + input[inputptr+l+1]=0; + for(int i=0;i0){ + lcdPrint("Got!"); + lcdDisplay(); + break; + }; + delayms(10); + }; +}; + diff --git a/firmware/applications/remote/serial.c b/firmware/applications/remote/serial.c new file mode 100644 index 0000000..94d41b3 --- /dev/null +++ b/firmware/applications/remote/serial.c @@ -0,0 +1,139 @@ +#include + +#include "basic/basic.h" + +#include "lcd/lcd.h" +#include "lcd/print.h" + +#include "funk/nrf24l01p.h" + +#include "usbcdc/usb.h" +#include "usbcdc/usbcore.h" +#include "usbcdc/usbhw.h" +#include "usbcdc/cdcuser.h" +#include "usbcdc/cdc_buf.h" +#include "usbcdc/util.h" + +#include + +#define BEACON_CHANNEL 81 +#define BEACON_MAC "\x1\x2\x3\x2\1" + +uint32_t const testkey[4] = { + 0xB4595344,0xD3E119B6,0xA814D0EC,0xEFF5A24E +}; + +#if CFG_USBMSC +#error "MSC is defined" +#endif + +#if !CFG_USBCDC +#error "CDC is not defined" +#endif + +/**************************************************************************/ + + +void ser_enable(void) { + usbCDCInit(); +}; + +void ser_disable(void) { + usbCDCOff(); +}; + +#define myLEN 10 +void ser_read(){ + uint8_t buf[myLEN+1]; + int l=myLEN; + + lcdPrint("Bytes:"); + CDC_OutBufAvailChar (&l); + lcdPrintInt(l); + lcdNl(); + + lcdPrint("read:"); + CDC_RdOutBuf (buf, &l); + lcdPrintInt(l); + lcdNl(); + + buf[l]=0; + lcdPrintln(buf); +}; + +void ser_say(){ + puts("hello world\r\n"); +}; + +void f_init(){ + nrf_init(); + struct NRF_CFG config = { + .channel= BEACON_CHANNEL, + .txmac= BEACON_MAC, + .nrmacs=1, + .mac0= BEACON_MAC, + .maclen ="\x10", + }; + + nrf_config_set(&config); +}; + +void f_beacon(void){ + struct NRF_CFG config = { + .channel= BEACON_CHANNEL, + .txmac= BEACON_MAC, + .nrmacs=1, + .mac0= BEACON_MAC, + .maclen ="\x10", + }; + + nrf_config_set(&config); +}; + +int enctoggle=0; + +void f_enctog(){ + enctoggle=1-enctoggle; + lcdClear(); + lcdPrint("Encryption:"); + if(enctoggle) + lcdPrintln("ON"); + else + lcdPrintln("Off"); +}; + +void f_recser(void){ + __attribute__ ((aligned (4))) uint8_t buf[32]; + int len; + + getInputWaitRelease(); + + do{ + len=nrf_rcv_pkt_time_encr(1000,sizeof(buf),buf,enctoggle?testkey:NULL); + + if(len==0){ + puts("(Timeout)\r\n"); + }; + puts("pkt: "); + puts("[");puts(IntToStrX(len,2));puts("] "); + puts(IntToStrX( *(int*)(buf+ 0),2 )); + puts(" "); + puts(IntToStrX( *(int*)(buf+ 1),2 )); + puts(" "); + puts(IntToStrX( *(int*)(buf+ 2),2 )); + puts(" "); + puts(IntToStrX( *(int*)(buf+ 3),2 )); + puts("."); + puts(IntToStrX( *(int*)(buf+ 4),8 )); + puts("."); + puts(IntToStrX( *(int*)(buf+ 8),8 )); + puts("."); + puts(IntToStrX( *(int*)(buf+ 12),4 )); + puts(" ["); + + len=crc16(buf,14); + puts(IntToStrX(len,4)); puts("]\r\n"); + delayms(10); + }while ((getInputRaw())==BTN_NONE); + +}; diff --git a/firmware/applications/remote/util.c b/firmware/applications/remote/util.c new file mode 100644 index 0000000..8359628 --- /dev/null +++ b/firmware/applications/remote/util.c @@ -0,0 +1,128 @@ +#include + +#include "basic/basic.h" + +#include "lcd/lcd.h" +#include "lcd/print.h" +#include "lcd/allfonts.h" + +#include "filesystem/ff.h" +#include "filesystem/select.h" +#include "funk/nrf24l01p.h" +#include "usb/usbmsc.h" + +#include + +/**************************************************************************/ + +void show_ticks(void) { + int dx=0; + int dy=8; + lcdClear(); + dx=DoString(0,dy,"Ticks:"); + while ((getInputRaw())==BTN_NONE){ + DoInt(0,dy+8,_timectr); + lcdDisplay(); + }; + dy+=16; + dx=DoString(0,dy,"Done."); +}; + + +void chrg_stat(void) { + int stat; + while ((getInputRaw())==BTN_NONE){ + lcdClear(); + lcdPrintln("Chrg_stat:"); + stat=gpioGetValue(RB_PWR_CHRG); + lcdPrint(IntToStr(stat,3,0)); + lcdNl(); + lcdRefresh(); + }; + lcdPrintln("Done."); +}; +void adc_light(void) { + int dx=0; + int dy=8; + dx=DoString(0,dy,"Light:"); + DoString(0,dy+16,"Night:"); + while ((getInputRaw())==BTN_NONE){ + DoInt(dx,dy,GetLight()); + DoInt(dx,dy+16,isNight()); + DoInt(dx,dy+8,GLOBAL(daytrig)); + lcdDisplay(); + }; + dy+=8; + dx=DoString(0,dy,"Done."); +}; + +void uptime(void) { + int t; + int h; + char flag; + while ((getInputRaw())==BTN_NONE){ + lcdClear(); + lcdPrintln("Uptime:"); + t=getTimer()/(1000/SYSTICKSPEED); + h=t/60/60; + flag=F_ZEROS; + if(h>0){ + lcdPrint(IntToStr(h,2,flag)); + lcdPrint("h"); + flag|=F_LONG; + }; + h=t/60%60; + if(h>0){ + lcdPrint(IntToStr(h,2,flag)); + lcdPrint("m"); + flag|=F_LONG; + }; + h=t%60; + if(h>0){ + lcdPrint(IntToStr(h,2,flag)); + lcdPrint("s"); + }; + lcdNl(); + lcdRefresh(); + delayms_queue(200); + }; + lcdPrintln("done."); +}; + +void gotoISP(void) { + DoString(0,0,"Enter ISP!"); + lcdDisplay(); + ISPandReset(); +} + +void lcd_mirror(void) { + lcdToggleFlag(LCD_MIRRORX); +}; + +void lcd_invert(void) { + lcdToggleFlag(LCD_INVERTED); +}; + +void adc_check(void) { + int dx=0; + int dy=8; + // Print Voltage + dx=DoString(0,dy,"Voltage:"); + while ((getInputRaw())==BTN_NONE){ + DoInt(dx,dy,GetVoltage()); + lcdDisplay(); + }; + dy+=8; + dx=DoString(0,dy,"Done."); +}; + +void msc_menu(void){ + DoString(0,8,"MSC Enabled."); + lcdDisplay(); + usbMSCInit(); + getInputWaitRelease(); + getInputWait(); + DoString(0,16,"MSC Disabled."); + usbMSCOff(); +}; + diff --git a/firmware/applications/remote/uuid.c b/firmware/applications/remote/uuid.c new file mode 100644 index 0000000..601a5f7 --- /dev/null +++ b/firmware/applications/remote/uuid.c @@ -0,0 +1,26 @@ +#include + +#include "basic/basic.h" + +#include "lcd/lcd.h" +#include "lcd/print.h" + +#include "funk/nrf24l01p.h" + +#include + +#include "funk/rftransfer.h" +#include "funk/openbeacon.h" + +#include "core/iap/iap.h" + +/**************************************************************************/ + +void f_uuid(void) { + IAP_return_t iap_return; + iap_return = iapReadSerialNumber(); + lcdPrintIntHex(iap_return.Result[0]); lcdNl(); + lcdPrintIntHex(iap_return.Result[1]); lcdNl(); + lcdPrintIntHex(iap_return.Result[2]); lcdNl(); + lcdPrintIntHex(iap_return.Result[3]); lcdNl(); +} From 95cde1077dac1f6bc609a696b04e0a91f9105440 Mon Sep 17 00:00:00 2001 From: lilafisch Date: Thu, 8 Dec 2011 01:36:49 +0100 Subject: [PATCH 02/37] first structure for game interface l0dable --- firmware/l0dable/r_player.c | 289 ++++++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 firmware/l0dable/r_player.c diff --git a/firmware/l0dable/r_player.c b/firmware/l0dable/r_player.c new file mode 100644 index 0000000..9db06f9 --- /dev/null +++ b/firmware/l0dable/r_player.c @@ -0,0 +1,289 @@ +#include + +#include "basic/basic.h" +#include "basic/byteorder.h" + +#include "lcd/lcd.h" +#include "lcd/print.h" + +#include "funk/nrf24l01p.h" + +//includes useful for r_game: +//#include "usbcdc/usb.h" +//#include "usbcdc/usbcore.h" +//#include "usbcdc/usbhw.h" +//#include "usbcdc/cdcuser.h" +//#include "usbcdc/cdc_buf.h" +//#include "usbcdc/util.h" + +#include +#include "basic/random.h" + +#include "usetable.h" + +#define REMOTE_CHANNEL 81 + +//mac that the player receives +#define PLAYER_MAC "\x1\x2\x3\x2\x1" + +//mac that the game receives +#define GAME_MAC "\x1\x2\x3\x2\x1" + +//#if CFG_USBMSC +//#error "MSC is defined" +//#endif + +//#if !CFG_USBCDC +//#error "CDC is not defined" +//#endif + +struct NRF_CFG config = { //for some reason this has to be global + .channel= REMOTE_CHANNEL, + .txmac= GAME_MAC, + .nrmacs=1, + .mac0= PLAYER_MAC, + .maclen ="\x10", + }; + +struct packet{ + uint8_t len; + uint8_t protocol; + uint8_t command; + + //union with 11 bytes data + union content{ + struct button{ + uint8_t button; + uint32_t id; + uint32_t cnt; + uint8_t reserved[2]; + }button; + + struct text{ + uint8_t x; + uint8_t y; + uint8_t flags; + uint8_t text[8]; + }text; + struct nick{ + uint8_t flags; + uint8_t text[10]; + }nick; + struct nickrequest{ + uint8_t reserved[11]; + }nickrequest; + }c; + uint16_t crc; +}; + + +/**************************************************************************/ +/* l0dable for playing games which are announced by other r0kets with the l0dabel r_game */ +/* Values of buf[3]: + * B: packet sent by player, contain information which button is pressed + * T: packet sent by game, contain text for display + * N: packet sent by game, requesting nick + * n: packet sent player, containing nick + */ + +uint32_t ctr; +uint32_t id; + +void sendButton(uint8_t button); +void sendJoin(uint8_t game); +void processPacket(struct packet *p); + +void ram(void) +{ + nrf_config_set(&config); + id = getRandom(); + ctr = 1; + int len; + struct packet p; + + while(1){ + uint8_t button = getInputRaw(); + sendButton(button); + + while(1){ + len = nrf_rcv_pkt_time(30,sizeof(p),(uint8_t*)&p); + if(len==sizeof(p)){ + processPacket(&p); + }else{ + break; + } + } + delayms(20); + }; +}; +//getInputRaw(); + +void processPacket(struct packet *p) +{ + if ((p->len==16) && (p->protocol=='G')){ //check sanity, protocol + if (p->command=='T'){ + //processText(&(p->c.text)); + } + else if (p->command=='N'){ + //processNick(&(p->c.nickrequest)); + } + } +} + + +//increment ctr and send button state, id and ctr +void sendButton(uint8_t button) +{ + uint8_t buf[16]; + buf[0]=0x10; // Length: 16 bytes + buf[1]='G'; // Proto + buf[2]='B'; + buf[3]=button; + + ctr++; + *(int*)(buf+4)=ctr; + ctr+=4; + *(int*)(buf+4)=id; + + //lcdClear(); + //lcdPrint("Key:"); lcdPrintInt(buf[2]); lcdNl(); + + nrf_snd_pkt_crc(16,buf); +} + +//send join request for game +void sendJoin(uint8_t game) +{ + uint8_t buf[16]; + buf[0]=0x10; // Length: 16 bytes + buf[1]='G'; // Proto + buf[2]='J'; + buf[3]=game; + + ctr++; + *(int*)(buf+4)=ctr; + ctr+=4; + *(int*)(buf+4)=id; + + nrf_snd_pkt_crc(16,buf); +} + + +/* +void s_init(void){ + usbCDCInit(); + nrf_init(); + + struct NRF_CFG config = { + .channel= REMOTE_CHANNEL, + .txmac= REMOTE_MAC, + .nrmacs=1, + .mac0= REMOTE_MAC, + .maclen ="\x10", + }; + + nrf_config_set(&config); +}; +*/ + +/* void process(uint8_t * input){ + __attribute__ ((aligned (4))) uint8_t buf[32]; + puts("process: "); + puts(input); + puts("\r\n"); + if(input[0]=='M'){ + buf[0]=0x10; // Length: 16 bytes + buf[1]='M'; // Proto + buf[2]=0x01; + buf[3]=0x01; // Unused + + uint32touint8p(0,buf+4); + + uint32touint8p(0x41424344,buf+8); + + buf[12]=0xff; // salt (0xffff always?) + buf[13]=0xff; + nrf_snd_pkt_crc_encr(16,buf,remotekey); + nrf_rcv_pkt_start(); + }; + +}; +*/ + +/* +#define INPUTLEN 99 +void r_recv(void){ + __attribute__ ((aligned (4))) uint8_t buf[32]; + int len; + + uint8_t input[INPUTLEN+1]; + int inputptr=0; + + nrf_rcv_pkt_start(); + puts("D start"); + + getInputWaitRelease(); + + while(!getInputRaw()){ + delayms(100); + + // Input + int l=INPUTLEN-inputptr; + CDC_OutBufAvailChar (&l); + + if(l>0){ + CDC_RdOutBuf (input+inputptr, &l); + input[inputptr+l+1]=0; + for(int i=0;i Date: Sat, 10 Dec 2011 21:39:04 +0100 Subject: [PATCH 03/37] disabled usb interrupts in WriteEP --- firmware/usbcdc/usbhw.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/firmware/usbcdc/usbhw.c b/firmware/usbcdc/usbhw.c index 711190c..6fbf7ee 100644 --- a/firmware/usbcdc/usbhw.c +++ b/firmware/usbcdc/usbhw.c @@ -26,6 +26,7 @@ #include "usbhw.h" #include "usbcore.h" #include "usbuser.h" +#include "basic/basic.h" #include "usb/usbmsc.h" @@ -470,6 +471,10 @@ uint32_t USB_ReadEP (uint32_t EPNum, uint8_t *pData) uint32_t USB_WriteEP (uint32_t EPNum, uint8_t *pData, uint32_t cnt) { uint32_t n; + + //this seems rather brutal... + //disable all usb related interrupts or WrCmd might block + USB_DEVINTEN = 0; USB_CTRL = ((EPNum & 0x0F) << 2) | CTRL_WR_EN; /* 3 clock cycles to fetch the packet length from RAM. */ @@ -486,6 +491,9 @@ uint32_t USB_WriteEP (uint32_t EPNum, uint8_t *pData, uint32_t cnt) WrCmdEP(EPNum, CMD_VALID_BUF); + //enable interrupts again + USB_DEVINTEN = DEV_STAT_INT | (0xFF<<1) | (USB_SOF_EVENT ? FRAME_INT : 0); + return (cnt); } From 2edc933ce0e547b616a599d6d18b8235f4ac6058 Mon Sep 17 00:00:00 2001 From: schneider Date: Sat, 10 Dec 2011 21:39:32 +0100 Subject: [PATCH 04/37] speed up for cdc --- firmware/usbcdc/Makefile | 1 - firmware/usbcdc/cdc_buf.c | 161 -------------------------------------- firmware/usbcdc/cdc_buf.h | 29 ------- firmware/usbcdc/cdcuser.c | 108 ++++++++++++++++++------- firmware/usbcdc/cdcuser.h | 2 +- firmware/usbcdc/util.c | 31 ++------ 6 files changed, 86 insertions(+), 246 deletions(-) delete mode 100644 firmware/usbcdc/cdc_buf.c delete mode 100644 firmware/usbcdc/cdc_buf.h diff --git a/firmware/usbcdc/Makefile b/firmware/usbcdc/Makefile index 3a9ea98..3019ad4 100644 --- a/firmware/usbcdc/Makefile +++ b/firmware/usbcdc/Makefile @@ -4,7 +4,6 @@ OBJS = OBJS += cdcuser.o -OBJS += cdc_buf.o OBJS += usbcore.o OBJS += usbdesc.o OBJS += usbhw.o diff --git a/firmware/usbcdc/cdc_buf.c b/firmware/usbcdc/cdc_buf.c deleted file mode 100644 index 2a7c1d5..0000000 --- a/firmware/usbcdc/cdc_buf.c +++ /dev/null @@ -1,161 +0,0 @@ -/******************************************************************* - Copyright (C) 2009 FreakLabs - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - 3. Neither the name of the the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - SUCH DAMAGE. - - Originally written by Christopher Wang aka Akiba. - Please post support questions to the FreakLabs forum. -*******************************************************************/ - -/**************************************************************************/ -/*! - @file cdc_buf.c - @author Christopher Wang (Freaklabs) - Modified by: K. Townsend (microBuilder.eu) - @date 19 May 2010 - - Original code taken from the FreakUSB Open Source USB Device Stack - http://freaklabs.org/index.php/FreakUSB-Open-Source-USB-Device-Stack.html - - If it works well, you can thank Akiba at Freaklabs. If it fails - miserably, you can blame me (since parts of it it were rather - ungraciously modified). :-) - -*/ -/**************************************************************************/ - -#include "cdc_buf.h" - -static cdc_buffer_t cdcfifo; - -/**************************************************************************/ -/*! - Gets a pointer to the fifo buffer -*/ -/**************************************************************************/ -cdc_buffer_t *cdcGetBuffer() -{ - return &cdcfifo; -} - -/**************************************************************************/ -/*! - Initialises the RX FIFO buffer -*/ -/**************************************************************************/ -void cdcBufferInit() -{ - cdcfifo.len = 0; -} - -/**************************************************************************/ -/*! - Read one byte out of the RX buffer. This function will return the byte - located at the array index of the read pointer, and then increment the - read pointer index. If the read pointer exceeds the maximum buffer - size, it will roll over to zero. -*/ -/**************************************************************************/ -uint8_t cdcBufferRead() -{ - uint8_t data; - - data = cdcfifo.buf[cdcfifo.rd_ptr]; - cdcfifo.rd_ptr = (cdcfifo.rd_ptr + 1) % CFG_USBCDC_BUFFERSIZE; - cdcfifo.len--; - return data; -} - -/**************************************************************************/ -/*! - Reads x bytes from cdc buffer - */ -/**************************************************************************/ -uint32_t cdcBufferReadLen(uint8_t* buf, uint32_t len) -{ - uint32_t counter, actual; - counter = actual = 0; - - while(counter != len) - { - // Make sure we don't exceed buffer limits - if (cdcfifo.len > 0) - { - buf[counter] = cdcBufferRead(); - actual++; - counter++; - } - else - { - return actual; - } - } - - return actual; -} - -/**************************************************************************/ -/*! - Write one byte into the RX buffer. This function will write one - byte into the array index specified by the write pointer and increment - the write index. If the write index exceeds the max buffer size, then it - will roll over to zero. -*/ -/**************************************************************************/ -void cdcBufferWrite(uint8_t data) -{ - cdcfifo.buf[cdcfifo.wr_ptr] = data; - cdcfifo.wr_ptr = (cdcfifo.wr_ptr + 1) % CFG_USBCDC_BUFFERSIZE; - cdcfifo.len++; -} - -/**************************************************************************/ -/*! - Clear the fifo read and write pointers and set the length to zero. -*/ -/**************************************************************************/ -void cdcBufferClearFIFO() -{ - cdcfifo.rd_ptr = 0; - cdcfifo.wr_ptr = 0; - cdcfifo.len = 0; -} - -/**************************************************************************/ -/*! - Check whether there is any data pending on the RX buffer. -*/ -/**************************************************************************/ -uint8_t cdcBufferDataPending() -{ - if (cdcfifo.len != 0) - { - return 1; - } - - return 0; -} diff --git a/firmware/usbcdc/cdc_buf.h b/firmware/usbcdc/cdc_buf.h deleted file mode 100644 index c5cdfad..0000000 --- a/firmware/usbcdc/cdc_buf.h +++ /dev/null @@ -1,29 +0,0 @@ -/*---------------------------------------------------------------------------- - * Name: cdc_buf.h - * Purpose: usb cdc buffer handling - * Version: V1.00 - *---------------------------------------------------------------------------*/ - -#ifndef __CDC_BUF_H__ -#define __CDC_BUF_H__ - -#include "projectconfig.h" - -// Buffer used for circular fifo -typedef struct _cdc_buffer_t -{ - volatile uint8_t len; - volatile uint8_t wr_ptr; - volatile uint8_t rd_ptr; - uint8_t buf[CFG_USBCDC_BUFFERSIZE]; -} cdc_buffer_t; - -cdc_buffer_t * cdcGetBuffer(); -void cdcBufferInit(); -uint8_t cdcBufferRead(); -uint32_t cdcBufferReadLen(uint8_t* buf, uint32_t len); -void cdcBufferWrite(uint8_t data); -void cdcBufferClearFIFO(); -uint8_t cdcBufferDataPending(); - -#endif diff --git a/firmware/usbcdc/cdcuser.c b/firmware/usbcdc/cdcuser.c index d553f47..222dae2 100644 --- a/firmware/usbcdc/cdcuser.c +++ b/firmware/usbcdc/cdcuser.c @@ -1,4 +1,4 @@ -/*---------------------------------------------------------------------------- +/*----------/BulkBufOut------------------------------------------------------------------ * U S B - K e r n e l *---------------------------------------------------------------------------- * Name: cdcuser.c @@ -17,6 +17,7 @@ *---------------------------------------------------------------------------*/ #include "projectconfig.h" +#include "basic/basic.h" #include "usb.h" #include "usbhw.h" @@ -24,15 +25,14 @@ #include "usbcore.h" #include "cdc.h" #include "cdcuser.h" -#include "cdc_buf.h" -// unsigned char BulkBufIn [64]; // Buffer to store USB IN packet +unsigned char BulkBufIn [64]; // Buffer to store USB IN packet unsigned char BulkBufOut [64]; // Buffer to store USB OUT packet unsigned char NotificationBuf [10]; CDC_LINE_CODING CDC_LineCoding = {CFG_USBCDC_BAUDRATE, 0, 0, 8}; unsigned short CDC_SerialState = 0x0000; -unsigned short CDC_DepInEmpty = 1; // Data IN EP is empty +volatile unsigned char CDC_DepInEmpty = 1; // Data IN EP is empty /*---------------------------------------------------------------------------- We need a buffer for incoming data on USB port because USB receives @@ -61,6 +61,7 @@ typedef struct __CDC_BUF_T } CDC_BUF_T; CDC_BUF_T CDC_OutBuf; // buffer for all CDC Out data +CDC_BUF_T CDC_InBuf; // buffer for all CDC Out data /*---------------------------------------------------------------------------- read data from CDC_OutBuf @@ -116,6 +117,67 @@ int CDC_OutBufAvailChar (int *availChar) } /* end Buffer handling */ +/*---------------------------------------------------------------------------- + read data from CDC_InBuf + *---------------------------------------------------------------------------*/ +int CDC_RdInBuf (char *buffer, const int *length) +{ + int bytesToRead, bytesRead; + + /* Read *length bytes, block if *bytes are not avaialable */ + bytesToRead = *length; + //bytesToRead = (bytesToRead < (*length)) ? bytesToRead : (*length); + bytesRead = bytesToRead; + + + // ... add code to check for underrun + + while (bytesToRead--) { + *buffer++ = CDC_BUF_RD(CDC_InBuf); + } + return (bytesRead); +} + +/*---------------------------------------------------------------------------- + write data to CDC_InBuf + *---------------------------------------------------------------------------*/ +int CDC_WrInBuf (const char *buffer, int *length) +{ + int bytesToWrite, bytesWritten; + + // Write *length bytes + bytesToWrite = *length; + bytesWritten = bytesToWrite; + + //Just block if we can't write all at once + while( CDC_BUF_SIZE - CDC_BUF_COUNT(CDC_InBuf) < bytesToWrite ); + + //uint8_t flush = CDC_DepInEmpty; + while (bytesToWrite--) { + CDC_BUF_WR(CDC_InBuf, *buffer++); // Copy Data to buffer + } + //if( flush == 1 ){ + if( CDC_DepInEmpty && CDC_BUF_COUNT(CDC_InBuf) ){ + CDC_DepInEmpty = 0; + gpioSetValue (RB_LED2, 0); + CDC_BulkIn(); + } + + return (bytesWritten); +} + +/*---------------------------------------------------------------------------- + check if character(s) are available at CDC_OutBuf + *---------------------------------------------------------------------------*/ +int CDC_InBufAvailChar (int *availChar) +{ + *availChar = CDC_BUF_COUNT(CDC_InBuf); + + return (0); +} +/* end Buffer handling */ + + /*---------------------------------------------------------------------------- CDC Initialisation @@ -129,12 +191,8 @@ void CDC_Init (void) CDC_SerialState = CDC_GetSerialState(); CDC_BUF_RESET(CDC_OutBuf); + CDC_BUF_RESET(CDC_InBuf); - // Initialise the CDC buffer. This is required to buffer outgoing - // data (MCU to PC) since data can only be sent 64 bytes per frame - // with at least 1ms between frames. To see how the buffer is used, - // see 'puts' in systeminit.c - cdcBufferInit(); } @@ -277,25 +335,19 @@ uint32_t CDC_SendBreak (unsigned short wDurationOfBreak) { *---------------------------------------------------------------------------*/ void CDC_BulkIn(void) { -// int numBytesRead, numBytesAvail; -// -// // ToDo: Modify BulkIn to send incoming data to USB -// -// ser_AvailChar (&numBytesAvail); -// -// // ... add code to check for overwrite -// -// numBytesRead = ser_Read ((char *)&BulkBufIn[0], &numBytesAvail); -// -// // send over USB -// if (numBytesRead > 0) { -// USB_WriteEP (CDC_DEP_IN, &BulkBufIn[0], numBytesRead); -// } -// else { -// CDC_DepInEmpty = 1; -// } -// -// + int numBytesRead, numBytesAvail; + CDC_InBufAvailChar(&numBytesAvail); + numBytesRead = CDC_RdInBuf(&BulkBufIn[0], &numBytesAvail); + // send over USB + if (numBytesRead > 0) { + //gpioSetValue (RB_LED0, 1); + USB_WriteEP (CDC_DEP_IN, &BulkBufIn[0], numBytesRead); + //gpioSetValue (RB_LED0, 0); + } else { + //USB_WriteEP (CDC_DEP_IN, "test\r\n", 6); + CDC_DepInEmpty = 1; + //gpioSetValue (RB_LED2, 1); + } } diff --git a/firmware/usbcdc/cdcuser.h b/firmware/usbcdc/cdcuser.h index 55cd910..4a2a82b 100644 --- a/firmware/usbcdc/cdcuser.h +++ b/firmware/usbcdc/cdcuser.h @@ -57,7 +57,7 @@ extern void CDC_Init (void); extern unsigned short CDC_GetSerialState (void); /* flow control */ -extern unsigned short CDC_DepInEmpty; // DataEndPoint IN empty +extern volatile unsigned char CDC_DepInEmpty; // DataEndPoint IN empty #endif /* __CDCUSER_H__ */ diff --git a/firmware/usbcdc/util.c b/firmware/usbcdc/util.c index fd3b041..5f3a69a 100644 --- a/firmware/usbcdc/util.c +++ b/firmware/usbcdc/util.c @@ -16,30 +16,9 @@ volatile unsigned int lastTick; int puts(const char * str){ if(!USB_Configuration) return -1; - - while(*str) - cdcBufferWrite(*str++); - - //XXX: This assumes systick is 1ms, which it isn't for us. - // this makes usbserial unnecessary slow. Ah well.... - - // Check if we can flush the buffer now or if we need to wait - unsigned int currentTick = systickGetTicks(); - if (currentTick != lastTick){ - uint8_t frame[64]; - uint32_t bytesRead = 0; - char repeat=0; - while (cdcBufferDataPending()){ - // Read up to 64 bytes as long as possible - bytesRead = cdcBufferReadLen(frame, 64); - USB_WriteEP (CDC_DEP_IN, frame, bytesRead); - if(repeat) - systickDelay(1); - else - repeat=1; - } - lastTick = currentTick; - } + + int len = strlen(str); + CDC_WrInBuf(str, &len); return 0; } @@ -47,8 +26,8 @@ int puts_plus(const char * str){ if(!USB_Configuration) return -1; - while(*str) - cdcBufferWrite(*str++); + int len = strlen(str); + CDC_WrInBuf(str, &len); return 0; } From ba930408f6760b57564141fc3955f4361b6facb8 Mon Sep 17 00:00:00 2001 From: schneider Date: Sun, 11 Dec 2011 00:20:38 +0100 Subject: [PATCH 05/37] usbcdc: i don't trust these ring buffers... --- firmware/usbcdc/cdcuser.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/firmware/usbcdc/cdcuser.c b/firmware/usbcdc/cdcuser.c index 222dae2..5f5d436 100644 --- a/firmware/usbcdc/cdcuser.c +++ b/firmware/usbcdc/cdcuser.c @@ -25,6 +25,8 @@ #include "usbcore.h" #include "cdc.h" #include "cdcuser.h" +#include "usbreg.h" + unsigned char BulkBufIn [64]; // Buffer to store USB IN packet unsigned char BulkBufOut [64]; // Buffer to store USB OUT packet @@ -149,19 +151,23 @@ int CDC_WrInBuf (const char *buffer, int *length) bytesToWrite = *length; bytesWritten = bytesToWrite; - //Just block if we can't write all at once - while( CDC_BUF_SIZE - CDC_BUF_COUNT(CDC_InBuf) < bytesToWrite ); + // Just block if we can't write all at once + // These ringbuffers smell buggy, so +1 + while( CDC_BUF_SIZE - CDC_BUF_COUNT(CDC_InBuf) < bytesToWrite+1 ); //uint8_t flush = CDC_DepInEmpty; + + USB_DEVINTEN = 0; while (bytesToWrite--) { CDC_BUF_WR(CDC_InBuf, *buffer++); // Copy Data to buffer } //if( flush == 1 ){ - if( CDC_DepInEmpty && CDC_BUF_COUNT(CDC_InBuf) ){ + //if( CDC_DepInEmpty && CDC_BUF_COUNT(CDC_InBuf) ){ + if( CDC_DepInEmpty ){ CDC_DepInEmpty = 0; - gpioSetValue (RB_LED2, 0); - CDC_BulkIn(); + CDC_BulkIn(); } + USB_DEVINTEN = DEV_STAT_INT | (0xFF<<1) | (USB_SOF_EVENT ? FRAME_INT : 0); return (bytesWritten); } @@ -327,7 +333,6 @@ uint32_t CDC_SendBreak (unsigned short wDurationOfBreak) { return (TRUE); } - /*---------------------------------------------------------------------------- CDC_BulkIn call on DataIn Request Parameters: none From 112bc97dc60f77b3abacd765b643f7f69d6556bd Mon Sep 17 00:00:00 2001 From: schneider Date: Sun, 11 Dec 2011 00:26:11 +0100 Subject: [PATCH 06/37] usbcdc: first think, then commit --- firmware/usbcdc/cdcuser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/usbcdc/cdcuser.c b/firmware/usbcdc/cdcuser.c index 5f5d436..fbbb5af 100644 --- a/firmware/usbcdc/cdcuser.c +++ b/firmware/usbcdc/cdcuser.c @@ -152,7 +152,7 @@ int CDC_WrInBuf (const char *buffer, int *length) bytesWritten = bytesToWrite; // Just block if we can't write all at once - // These ringbuffers smell buggy, so +1 + // +1 to prevent an overflow of the ring buffer while( CDC_BUF_SIZE - CDC_BUF_COUNT(CDC_InBuf) < bytesToWrite+1 ); //uint8_t flush = CDC_DepInEmpty; From 69235e7409ecd5169c3651b11fa8973151fcdb47 Mon Sep 17 00:00:00 2001 From: schneider Date: Sun, 11 Dec 2011 16:13:29 +0100 Subject: [PATCH 07/37] removed crp --- firmware/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/main.c b/firmware/main.c index 9b02c7c..4dd682b 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -28,7 +28,7 @@ #define CRP_VALUE 0x0 // ANY non-magic value disables CRP #endif -__attribute__ ((used, section("crp"))) const uint32_t the_crp=CRP_VALUE; +//__attribute__ ((used, section("crp"))) const uint32_t the_crp=CRP_VALUE; /**************************************************************************/ From 02036634d477a2694ba887f66b790c013ae9ba4b Mon Sep 17 00:00:00 2001 From: schneider Date: Sun, 11 Dec 2011 16:13:46 +0100 Subject: [PATCH 08/37] added rf-io application --- firmware/applications/schneider.c | 208 ++++++++++++++++++++++++------ 1 file changed, 171 insertions(+), 37 deletions(-) diff --git a/firmware/applications/schneider.c b/firmware/applications/schneider.c index 9e7ec11..ee24e29 100644 --- a/firmware/applications/schneider.c +++ b/firmware/applications/schneider.c @@ -6,52 +6,186 @@ #include "basic/basic.h" #include "lcd/render.h" #include "lcd/allfonts.h" +#include "basic/config.h" +#include "basic/byteorder.h" +#include "lcd/lcd.h" +#include "lcd/print.h" +#include "funk/nrf24l01p.h" +#include "usbcdc/usb.h" +#include "usbcdc/usbcore.h" +#include "usbcdc/usbhw.h" +#include "usbcdc/cdcuser.h" +#include "usbcdc/cdc_buf.h" +#include "usbcdc/util.h" +#include "core/ssp/ssp.h" -#include "ecc.c" -void backlightInit(void); +#if CFG_USBMSC +#error "MSC is defined" +#endif +#if !CFG_USBCDC +#error "CDC is not defined" +#endif + +#define CHANNEL 81 +#define MAC "\x1\x2\x3\x2\x1" + +#define UB_NONE 0 +#define UB_ESCAPE '\\' +#define UB_STOP '0' +#define UB_PACKETLEN 128 + +struct NRF_CFG config = { + .channel= CHANNEL, + .txmac= MAC, + .nrmacs=1, + .mac0= MAC, + .maclen ="\x10", +}; + +uint8_t serialmsg_message[UB_PACKETLEN]; +uint8_t serialmsg_len = 0; + +void serialmsg_init(void); +uint8_t serialmsg_put(uint8_t data); +char snd_pkt_no_crc(int size, uint8_t * pkt); + +#define INPUTLEN 99 /**************************************************************************/ -void main_schneider(void) { - //int yctr=8; - int dx=0; - char key; - int ctr = 1; - backlightInit(); - font_direction = FONT_DIR_LTR; // LeftToRight is the default - //yctr=18; - bitstr_parse(poly, "800000000000000000000000000000000000000c9"); - bitstr_parse(coeff_b, "20a601907b8c953ca1481eb10512f78744a3205fd"); - bitstr_parse(base_x, "3f0eba16286a2d57ea0991168d4994637e8343e36"); - bitstr_parse(base_y, "0d51fbc6c71a0094fa2cdd545b11c5c0c797324f1"); - bitstr_parse(base_order, "40000000000000000000292fe77e70c12a4234c33"); +void main_schneider(void) +{ + GLOBAL(daytrig)=10; + GLOBAL(lcdbacklight)=10; + char input[INPUTLEN+1]; - ECIES_generate_key_pair(); // generate a public/private key pair - while (1) { - key= getInput(); - font=&Font_7x8; - - // Easy flashing - if(key==BTN_LEFT){ - DoString(0,8,"Enter ISP!"); - lcdDisplay(); - ISPandReset(); - }; - - // Display nickname - //font = &Font_Ubuntu36pt; - dx=DoString(0,0,"Test"); - dx=DoInt(dx,0,ctr++); - lcdDisplay(); - encryption_decryption_demo("This is encrypted", - "1c56d302cf642a8e1ba4b48cc4fbe2845ee32dce7", - "45f46eb303edf2e62f74bd68368d979e265ee3c03", - "0e10e787036941e6c78daf8a0e8e1dbfac68e26d2"); + usbCDCInit(); + delayms(500); + nrf_init(); + nrf_config_set(&config); + + nrf_rcv_pkt_start(); + while(1){ + int l=INPUTLEN, i, status; + CDC_OutBufAvailChar (&l); + if(l>0){ + CDC_RdOutBuf (input, &l); + for(i=0; i 0 ){ + puts("\\1"); + CDC_WrInBuf(buf, &len); + puts("\\0"); + } } - return; } void tick_schneider(void){ return; }; +inline void xmit_spi(uint8_t dat) { + sspSend(0, (uint8_t*) &dat, 1); +} + +inline void rcv_spi(uint8_t *dat) { + sspReceive(0, dat, 1); +} + +#define CS_LOW() gpioSetValue(RB_SPI_NRF_CS, 0) +#define CS_HIGH() gpioSetValue(RB_SPI_NRF_CS, 1) +#define CE_LOW() gpioSetValue(RB_NRF_CE, 0) +#define CE_HIGH() gpioSetValue(RB_NRF_CE, 1) + +char snd_pkt_no_crc(int size, uint8_t * pkt) +{ + if(size > MAX_PKT) + size=MAX_PKT; + + nrf_write_reg(R_CONFIG, + R_CONFIG_PWR_UP| // Power on + R_CONFIG_EN_CRC // CRC on, single byte + ); + + CS_LOW(); + xmit_spi(C_W_TX_PAYLOAD); + sspSend(0,pkt,size); + CS_HIGH(); + + CE_HIGH(); + delayms(1); // Send it. (only needs >10ys, i think) + CE_LOW(); + + return nrf_cmd_status(C_NOP); +}; + +void serialmsg_init(void) +{ + serialmsg_len = 0; +} + +//returns the message type or UB_NONE +uint8_t serialmsg_put(uint8_t data) +{ + static uint8_t escaped = 0; + static uint8_t msgtype = UB_NONE; + + if( data == UB_ESCAPE && escaped == 0 ){ + //a control code will follow + escaped = 1; + return UB_NONE; + }else if( escaped ){ + escaped = 0; + if( data != UB_ESCAPE ){ + if( data == UB_STOP ){ + uint8_t tmp = msgtype; + msgtype = UB_NONE; + return tmp; + } + msgtype = data; + serialmsg_len=0; + return UB_NONE; + } + } + serialmsg_message[serialmsg_len++] = data; + + //prevent a buffer overflow + if( serialmsg_len == UB_PACKETLEN ) + serialmsg_len--; + + return UB_NONE; +} + + From c02b2279874c6432888602710c0f6b618d52826f Mon Sep 17 00:00:00 2001 From: schneider Date: Sun, 11 Dec 2011 16:15:05 +0100 Subject: [PATCH 09/37] added bridge protocol --- firmware/BRIDGE-PROTOCOL | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 firmware/BRIDGE-PROTOCOL diff --git a/firmware/BRIDGE-PROTOCOL b/firmware/BRIDGE-PROTOCOL new file mode 100644 index 0000000..e812eae --- /dev/null +++ b/firmware/BRIDGE-PROTOCOL @@ -0,0 +1,18 @@ +Receiving: +r->h: \1\0 + +Sending: +h->r: \1\0 +r->h: \2\0 + +Settings: +h->r:\3\0 +r->h: \2\0 +h->r:\4\0 +r->h: \2\0 +h->r:\5\0 +r->h: \2\0 +h->r:\6\0 +r->h: \2\0 + + From 4c7669af24ee7a3d8259ccbf52a85eb6a0490ca3 Mon Sep 17 00:00:00 2001 From: schneider Date: Sun, 11 Dec 2011 18:24:11 +0100 Subject: [PATCH 10/37] encode packets sent to usb --- firmware/applications/schneider.c | 52 ++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/firmware/applications/schneider.c b/firmware/applications/schneider.c index ee24e29..27ace29 100644 --- a/firmware/applications/schneider.c +++ b/firmware/applications/schneider.c @@ -30,10 +30,10 @@ #define CHANNEL 81 #define MAC "\x1\x2\x3\x2\x1" -#define UB_NONE 0 -#define UB_ESCAPE '\\' -#define UB_STOP '0' -#define UB_PACKETLEN 128 +#define SERIAL_NONE 0 +#define SERIAL_ESCAPE '\\' +#define SERIAL_STOP '0' +#define SERIAL_PACKETLEN 128 struct NRF_CFG config = { .channel= CHANNEL, @@ -43,12 +43,13 @@ struct NRF_CFG config = { .maclen ="\x10", }; -uint8_t serialmsg_message[UB_PACKETLEN]; +uint8_t serialmsg_message[SERIAL_PACKETLEN]; uint8_t serialmsg_len = 0; void serialmsg_init(void); uint8_t serialmsg_put(uint8_t data); char snd_pkt_no_crc(int size, uint8_t * pkt); +void dump_encoded(int len, uint8_t *data); #define INPUTLEN 99 /**************************************************************************/ @@ -57,7 +58,7 @@ void main_schneider(void) { GLOBAL(daytrig)=10; GLOBAL(lcdbacklight)=10; - char input[INPUTLEN+1]; + char input[64]; usbCDCInit(); delayms(500); @@ -66,13 +67,13 @@ void main_schneider(void) nrf_rcv_pkt_start(); while(1){ - int l=INPUTLEN, i, status; + int l, i, status; CDC_OutBufAvailChar (&l); if(l>0){ CDC_RdOutBuf (input, &l); for(i=0; i 0 ){ puts("\\1"); - CDC_WrInBuf(buf, &len); + dump_encoded(len, buf); puts("\\0"); } } } +void dump_encoded(int len, uint8_t *data) +{ + int i=0,j=0; + uint8_t buf[SERIAL_PACKETLEN*2]; + for(i=0; i Date: Sun, 11 Dec 2011 18:32:34 +0100 Subject: [PATCH 11/37] small error with position leds --- firmware/applications/default.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/applications/default.c b/firmware/applications/default.c index b5a357c..7c518ea 100644 --- a/firmware/applications/default.c +++ b/firmware/applications/default.c @@ -82,7 +82,7 @@ void tick_default(void) { gpioSetValue (RB_LED0, 1); gpioSetValue (RB_LED2, 1); posleds = 1; - }else if( posleds = 1 ){ + }else if( posleds ){ gpioSetValue (RB_LED0, 0); gpioSetValue (RB_LED2, 0); } From bebae321e2b0c81b1f415610941efb3d31a16718 Mon Sep 17 00:00:00 2001 From: schneider Date: Sun, 11 Dec 2011 18:37:53 +0100 Subject: [PATCH 12/37] fixed some compiler warnings, removed dead code --- firmware/usbcdc/cdcuser.c | 2 +- firmware/usbcdc/cdcuser.h | 1 + firmware/usbcdc/util.c | 16 ++-------------- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/firmware/usbcdc/cdcuser.c b/firmware/usbcdc/cdcuser.c index fbbb5af..85fd77d 100644 --- a/firmware/usbcdc/cdcuser.c +++ b/firmware/usbcdc/cdcuser.c @@ -342,7 +342,7 @@ void CDC_BulkIn(void) { int numBytesRead, numBytesAvail; CDC_InBufAvailChar(&numBytesAvail); - numBytesRead = CDC_RdInBuf(&BulkBufIn[0], &numBytesAvail); + numBytesRead = CDC_RdInBuf((char*)&BulkBufIn[0], &numBytesAvail); // send over USB if (numBytesRead > 0) { //gpioSetValue (RB_LED0, 1); diff --git a/firmware/usbcdc/cdcuser.h b/firmware/usbcdc/cdcuser.h index 4a2a82b..d491b93 100644 --- a/firmware/usbcdc/cdcuser.h +++ b/firmware/usbcdc/cdcuser.h @@ -24,6 +24,7 @@ extern int CDC_RdOutBuf (char *buffer, const int *length); extern int CDC_WrOutBuf (const char *buffer, int *length); extern int CDC_OutBufAvailChar (int *availChar); +extern int CDC_WrInBuf (const char *buffer, int *length); /* CDC Data In/Out Endpoint Address */ #define CDC_DEP_IN 0x83 diff --git a/firmware/usbcdc/util.c b/firmware/usbcdc/util.c index 5f3a69a..d2c0c03 100644 --- a/firmware/usbcdc/util.c +++ b/firmware/usbcdc/util.c @@ -1,4 +1,5 @@ #include +#include #include "usbcdc/usb.h" #include "usbcdc/usbcore.h" @@ -6,13 +7,6 @@ #include "usbcdc/usbhw.h" #include "usbcdc/cdcuser.h" -#include "basic/basic.h" - -volatile unsigned int lastTick; - -// There must be at least 1ms between USB frames (of up to 64 bytes) -// This buffers all data and writes it out from the buffer one frame -// and one millisecond at a time int puts(const char * str){ if(!USB_Configuration) return -1; @@ -23,16 +17,10 @@ int puts(const char * str){ } int puts_plus(const char * str){ - if(!USB_Configuration) - return -1; - - int len = strlen(str); - CDC_WrInBuf(str, &len); - return 0; + return puts(str); } void usbCDCInit(){ - lastTick = systickGetTicks(); // Used to control output/printf timing CDC_Init(); // Initialise VCOM USB_Init(); // USB Initialization USB_Connect(TRUE); // USB Connect From b2dc462ce70af8debe10ae3708b540848d1730f1 Mon Sep 17 00:00:00 2001 From: schneider Date: Sun, 11 Dec 2011 18:38:40 +0100 Subject: [PATCH 13/37] rf-io: fixed small error --- firmware/applications/schneider.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/applications/schneider.c b/firmware/applications/schneider.c index 27ace29..cf7c3e4 100644 --- a/firmware/applications/schneider.c +++ b/firmware/applications/schneider.c @@ -123,7 +123,7 @@ void dump_encoded(int len, uint8_t *data) } buf[j++] = data[i]; } - CDC_WrInBuf(buf, j); + CDC_WrInBuf((char*)buf, &j); } void tick_schneider(void){ From 50e16d52cd77c2213300e2c565e55a2111e9eca3 Mon Sep 17 00:00:00 2001 From: schneider Date: Sun, 11 Dec 2011 18:39:19 +0100 Subject: [PATCH 14/37] ew do not encrypt l0dables anymore --- firmware/SECRETS.release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/SECRETS.release b/firmware/SECRETS.release index 84d2d01..a87bb87 100644 --- a/firmware/SECRETS.release +++ b/firmware/SECRETS.release @@ -1,7 +1,7 @@ #ifndef _SECRETS_ #define _SECRETS_ -#define ENCRYPT_L0DABLE 1 +#undef ENCRYPT_L0DABLE 1 static uint32_t const meshkey[4] = { 0xafbbcb26, 0x39108427, 0x455ef5e5, 0x51b482d2 From 6e30f473f7af569b3907d3f2f2dda07108440f5a Mon Sep 17 00:00:00 2001 From: schneider Date: Sun, 11 Dec 2011 18:39:50 +0100 Subject: [PATCH 15/37] removed old lodable dir --- firmware/loadable/.gitignore | 4 - firmware/loadable/Makefile | 12 - firmware/loadable/Makefile.sub | 57 ---- firmware/loadable/bin2h.pl | 41 --- firmware/loadable/blinktest.c | 10 - firmware/loadable/mandelbrot.c | 190 ------------ firmware/loadable/ram.ld | 47 --- firmware/loadable/recvcard.c | 338 ---------------------- firmware/loadable/sendcard.c | 285 ------------------ firmware/loadable/spaceinvaders.c | 462 ------------------------------ 10 files changed, 1446 deletions(-) delete mode 100644 firmware/loadable/.gitignore delete mode 100644 firmware/loadable/Makefile delete mode 100644 firmware/loadable/Makefile.sub delete mode 100755 firmware/loadable/bin2h.pl delete mode 100644 firmware/loadable/blinktest.c delete mode 100644 firmware/loadable/mandelbrot.c delete mode 100644 firmware/loadable/ram.ld delete mode 100644 firmware/loadable/recvcard.c delete mode 100644 firmware/loadable/sendcard.c delete mode 100644 firmware/loadable/spaceinvaders.c diff --git a/firmware/loadable/.gitignore b/firmware/loadable/.gitignore deleted file mode 100644 index 7652361..0000000 --- a/firmware/loadable/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -*.elf -*.bin -*.h -loadable.ld diff --git a/firmware/loadable/Makefile b/firmware/loadable/Makefile deleted file mode 100644 index fa86f1e..0000000 --- a/firmware/loadable/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -# Make doesn't allow dependencies on parent directory, so we need to -# run make from one level up: - -MAKEFILE=loadable/Makefile.sub -MAKE+=--no-print-directory - -all: - @cd .. && $(MAKE) -f $(MAKEFILE) - -clean: - @cd .. && $(MAKE) -f $(MAKEFILE) clean - diff --git a/firmware/loadable/Makefile.sub b/firmware/loadable/Makefile.sub deleted file mode 100644 index aa2f3ee..0000000 --- a/firmware/loadable/Makefile.sub +++ /dev/null @@ -1,57 +0,0 @@ -DIR?= loadable - -########################################################################## -# User configuration and firmware specific object files -########################################################################## -SRCS = $(wildcard $(DIR)/*.c) -OBJS = $(foreach mod,$(SRCS),$(subst .c,.o,$(mod))) -ELFS = $(foreach mod,$(SRCS),$(subst .c,.elf,$(mod))) -BINS = $(foreach mod,$(SRCS),$(subst .c,.bin,$(mod))) -HDRS = $(foreach mod,$(SRCS),$(subst .c,.h,$(mod))) - -########################################################################## -# GNU GCC compiler flags -########################################################################## -ROOT_PATH?= . - -INCLUDE_PATHS = -I$(ROOT_PATH) -I$(ROOT_PATH)/core - -include $(ROOT_PATH)/Makefile.inc - -########################################################################## -# Compiler settings, parameters and flags -########################################################################## -FIRMWARE=$(ROOT_PATH)/$(OUTFILE).elf -LDSRCFILE=$(DIR)/ram.ld -LDFILE=$(DIR)/loadable.ld -CFLAGS+=-mlong-calls -fno-toplevel-reorder -LDFLAGS+= -R $(FIRMWARE) - -all: $(OBJS) $(ELFS) $(BINS) $(HDRS) - -$(LDFILE): - -@echo "MEMORY" > $(LDFILE) - -@echo "{" >> $(LDFILE) - -@echo " sram(rwx): ORIGIN = 0x10002000 - $(RAMCODE), LENGTH = $(RAMCODE)" >> $(LDFILE) - -@echo "}" >> $(LDFILE) - -@echo "INCLUDE $(LDSRCFILE)" >> $(LDFILE) - -%.o : %.c - $(CC) $(CFLAGS) -o $@ $< - -%.elf: %.o $(FIRMWARE) $(LDFILE) - $(LD) $(LDFLAGS) -T $(LDFILE) -o $@ $< - $(SIZE) $@ - -%.bin: %.elf - $(OBJCOPY) $(OCFLAGS) -O binary $< $@ - -%.h: %.bin $(DIR)/bin2h.pl - $(DIR)/bin2h.pl $< - -clean: - cd $(DIR) && rm -f *.o *.elf *.bin - -.SUFFIXES: - -.PHONY: $(LDFILE) diff --git a/firmware/loadable/bin2h.pl b/firmware/loadable/bin2h.pl deleted file mode 100755 index 185bc54..0000000 --- a/firmware/loadable/bin2h.pl +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/perl -# -# vim:set ts=4 sw=4: - -use strict; - -sub dwim{ - local $/=undef; - my $file=shift; - - open(IN,"<:bytes",$file) || die "Can't open $file: $!"; - my @bytes=unpack("C*",); - close(IN); - - $file=~s/\.[^.]+$//; - - open(OUT,">","${file}.h") || die "Can't write ${file}.h: $!"; - - $file=~s!.*/!!; - - print OUT "const uint16_t loadable_${file}_size = ", scalar @bytes, ";\n"; - print OUT "const uint8_t loadable_${file}[] = {\n"; - - my $ctr=0; - for(@bytes){ - print OUT "\t" if($ctr==0); - printf OUT "0x%02x, ",$_; - if(++$ctr==8){ - print OUT "\n"; - $ctr=0; - }; - }; - print OUT "\n" if($ctr!=0); - - print OUT "};\n"; - close(OUT); -}; - -for(@ARGV){ - dwim($_); -}; diff --git a/firmware/loadable/blinktest.c b/firmware/loadable/blinktest.c deleted file mode 100644 index 7530f14..0000000 --- a/firmware/loadable/blinktest.c +++ /dev/null @@ -1,10 +0,0 @@ -#include - -#include "basic/basic.h" - -void ram(void){ - for (int x=0;x<20;x++){ - gpioSetValue (RB_LED1, x%2); - delayms(50); - }; -}; diff --git a/firmware/loadable/mandelbrot.c b/firmware/loadable/mandelbrot.c deleted file mode 100644 index f66b691..0000000 --- a/firmware/loadable/mandelbrot.c +++ /dev/null @@ -1,190 +0,0 @@ -#include - -#include "basic/basic.h" - -#include "lcd/render.h" -#include "lcd/display.h" -#include "lcd/allfonts.h" - -#define FIXSIZE 25 -#define mul(a,b) ((((long long)a)*(b))>>FIXSIZE) -#define fixpt(a) ((long)(((a)*(1<>FIXSIZE) - -#define ZOOM_RATIO 0.90 -#define ITERATION_MAX 150 - -void mandelInit(); -void mandelMove(); -void mandelUpdate(); - -void ram(void) { - int key; - mandelInit(); - while (1) { - lcdDisplay(); - mandelMove(); - mandelUpdate(); - - // Exit on enter+direction - key=getInputRaw(); - if(key&BTN_ENTER && key>BTN_ENTER) - return; - } - return; -} - -struct mb { - long rmin, rmax, imin, imax; - bool dirty, dup, ddown, dleft, dright, clickmark; - int count, limitZIn, limitZOut; -} mandel; - -void mandelInit() { - //mandel.rmin = -2.2*0.9; - //mandel.rmax = 1.0*0.9; - //mandel.imin = -2.0*0.9; - //mandel.imax = 2.0*0.9; - mandel.rmin = fixpt(-2); - mandel.rmax = fixpt(1); - mandel.imin = fixpt(-2); - mandel.imax = fixpt(2); - mandel.count = 0; - mandel.limitZIn = 40; - mandel.limitZOut = 30; - - mandel.dirty = true; - mandel.dup = false; - mandel.ddown = false; - mandel.dleft = false; - mandel.dright = false; - mandel.clickmark = false; -} - -void mandelMove() { - //long delta_r = (mandel.rmax - mandel.rmin)/10; - //long delta_i = (mandel.imax - mandel.imin)/10; - - long rs =(mandel.rmax-mandel.rmin)/RESY; - long is =(mandel.imax-mandel.imin)/RESX; - - char key = getInputRaw(); - - if (key == BTN_LEFT) { - mandel.imax -=is; - mandel.imin -=is; - mandel.dleft = true; - } else if (key == BTN_RIGHT) { - mandel.imax += is; - mandel.imin += is; - mandel.dright = true; - } else if (key == BTN_DOWN) { - mandel.rmax += rs; - mandel.rmin += rs; - mandel.ddown = true; - } else if (key == BTN_UP) { - mandel.rmax -= rs; - mandel.rmin -= rs; - mandel.dup = true; - } else if (key == BTN_ENTER) { - if (mandel.count < mandel.limitZIn) { - mandel.count = mandel.count + 1; - } - } else if (key == BTN_NONE) { - if(mandel.count > 0 ) { - mandel.count = mandel.count - 1; - mandel.clickmark = true; - } - if (mandel.count == 0 ) { - mandel.clickmark = false; - } - } - if (mandel.count > mandel.limitZOut && mandel.clickmark && key == BTN_ENTER) { - mandel.imin = mandel.imin - (mandel.imax-mandel.imin)/10; - mandel.imax = mandel.imax + (mandel.imax-mandel.imin)/10; - mandel.rmin = mandel.rmin -(mandel.rmax-mandel.rmin)/10; - mandel.rmax = mandel.rmax +(mandel.rmax-mandel.rmin)/10; - mandel.dirty = true; - } - if (mandel.count == mandel.limitZIn && key == BTN_ENTER) { - mandel.imin = mandel.imin + (mandel.imax-mandel.imin)/10; - mandel.imax = mandel.imax - (mandel.imax-mandel.imin)/10; - mandel.rmin = mandel.rmin +(mandel.rmax-mandel.rmin)/10; - mandel.rmax = mandel.rmax -(mandel.rmax-mandel.rmin)/10; - mandel.dirty = true; - } -} - -void mandelPixel(int x, int y) { - long r0,i0,rn, p,q; - long rs,is; - int iteration; - - rs=(mandel.rmax-mandel.rmin)/RESY; - is=(mandel.imax-mandel.imin)/RESX; - //p=fixpt(mandel.rmin+y*rs); - //q=fixpt(mandel.imin+x*is); - p=mandel.rmin+y*rs; - q=mandel.imin+x*is; - - rn=0; - r0=0; - i0=0; - iteration=0; - while ((mul(rn,rn)+mul(i0,i0))1); - lcdSetPixel(x, y, pixel); -} - -void mandelUpdate() { - int xmin,xmax,ymin,ymax; - if (mandel.dirty) { - xmin = 0; - xmax = RESX; - ymin = 0; - ymax = RESY; - mandel.dirty = false; - } else if (mandel.dleft) { - lcdShift(1,0,false); - xmin = 0; - xmax = 1; - ymin = 0; - ymax = RESY; - mandel.dleft = false; - } else if (mandel.dright) { - lcdShift(-1,0,false); - xmin = RESX-1; - xmax = RESX; - ymin = 0; - ymax = RESY; - mandel.dright = false; - } else if (mandel.dup) { - lcdShift(0,-1,true); - xmin=0; - xmax=RESX; - ymin=0; - ymax=1; - mandel.dup = false; - } else if (mandel.ddown) { - lcdShift(0,1,true); - xmin=0; - xmax=RESX; - ymin=RESY-1; - ymax=RESY; - mandel.ddown = false; - } else { - return; - } - - for (int x = xmin; x sram - - /* - * More information about Special Section Indexes is available in the - * free "ELF for the ARM Architecture" document from ARM Limited - * http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044d/IHI0044D_aaelf.pdf - * - */ - .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } > sram - __exidx_start = .; - .ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } > sram - __exidx_end = .; - - _etext = .; - - .data : AT (__exidx_end) - { - _data = .; - *(vtable) - *(.data*) - _edata = .; - } > sram - - /* zero initialized data */ - .bss : - { - _bss = .; - *(.bss*) - *(COMMON) - _ebss = .; - } > sram - - end = .; - - /* For GDB compatibility we decrease the top with 16 bytes */ - stack_entry = sram_top - 16; -} diff --git a/firmware/loadable/recvcard.c b/firmware/loadable/recvcard.c deleted file mode 100644 index 8571f7c..0000000 --- a/firmware/loadable/recvcard.c +++ /dev/null @@ -1,338 +0,0 @@ -#include -#include -#include -#include -#include -#include "basic/basic.h" -#include "lcd/render.h" -#include "lcd/allfonts.h" -#include "basic/ecc.h" -#include "funk/nrf24l01p.h" -#include "filesystem/ff.h" -#include "filesystem/diskio.h" -#include "funk/filetransfer.h" -#include "lcd/print.h" - -uint8_t mac[5] = {1,2,3,2,1}; - -void ram(void) -{ - if( sendKeys() ) - return; - - char priv[42]; - UINT readbytes; - FIL file; - - if( f_open(&file, "priv.key", FA_OPEN_EXISTING|FA_READ) ){ - return; - } - if( f_read(&file, priv, 41, &readbytes) || readbytes != 41 ){ - return; - } - f_close(&file); - priv[41] = 0; - - uint8_t done = 0; - uint8_t key; - uint8_t k1[16], k2[16], rx[4*NUMWORDS], ry[4*NUMWORDS]; - - while( !done ){ - lcdClear(); - lcdPrintln("Receiving file"); - lcdPrintln("Down=Abort"); - lcdRefresh(); - key = getInput(); - delayms(20); - if( key == BTN_DOWN ){ - return -1; - } - if( receiveR(rx,ry) ) - continue; - lcdPrintln("Creating key"); - lcdRefresh(); - ECIES_decryptkeygen(rx, ry, k1, k2, priv); - if( filetransfer_receive(mac,(uint32_t*)k1) < 0 ) - continue; - lcdPrintln("Right=OK"); - lcdPrintln("Left=Retry"); - lcdPrintln("Down=Abort"); - lcdRefresh(); - - while(1){ - key = getInput(); - delayms(20); - if( key == BTN_LEFT ){ - break; - }else if( key == BTN_RIGHT ){ - done = 1; - break; - }else if( key == BTN_DOWN ){ - return -1; - } - } - } -} - -void sendPublicKey(void) -{ - uint8_t exp[2 + 4*NUMWORDS + 2]; - char buf[42]; - UINT readbytes; - FIL file; - - if( f_open(&file, "pubx.key", FA_OPEN_EXISTING|FA_READ) ){ - return; - } - if( f_read(&file, buf, 41, &readbytes) || readbytes != 41 ){ - return; - } - f_close(&file); - buf[41] = 0; - - exp[0] = 'P'; - bitstr_parse_export((char*)exp+2, buf); - exp[1] = 'X'; - nrf_snd_pkt_crc(32, exp); - delayms(10); - - if( f_open(&file, "puby.key", FA_OPEN_EXISTING|FA_READ) ){ - return; - } - if( f_read(&file, buf, 41, &readbytes) || readbytes != 41 ){ - return; - } - f_close(&file); - buf[41] = 0; - - exp[1] = 'Y'; - bitstr_parse_export((char*)exp+2, buf); - nrf_snd_pkt_crc(32, exp); - delayms(10); -} - - -int receiveKey(uint8_t type, uint8_t *x, uint8_t *y) -{ - uint8_t buf[32]; - uint8_t n; - - n = nrf_rcv_pkt_time(1000, 32, buf); - if( n == 32 && buf[0] == type && buf[1] == 'X' ){ - for(int i=0; i -#include "funk/nrf24l01p.h" -#include "funk/filetransfer.h" -#include "funk/rftransfer.h" -#include "basic/basic.h" -#include "basic/xxtea.h" -#include "filesystem/ff.h" -#include "lcd/print.h" - - -int filetransfer_receive(uint8_t *mac, uint32_t const k[4]) -{ - uint8_t buf[MAXSIZE+1]; - uint16_t size; - uint8_t n; - - UINT written = 0; - FIL file; - FRESULT res; - //uint8_t macbuf[5]; - //nrf_get_rx_max(0,5,macbuf); - - uint8_t metadata[32]; - - //nrf_set_rx_mac(0, 32, 5, mac); - n = nrf_rcv_pkt_time_encr(3000, 32, metadata, k); - if( n != 32 ) - return 1; //timeout - //nrf_set_rx_mac(0, 32, 5, macbuf); - //lcdPrintln("got meta"); lcdRefresh(); - metadata[19] = 0; //enforce termination - size = (metadata[20] << 8) | metadata[21]; - - if( size > MAXSIZE ) {lcdPrintln("too big"); lcdRefresh(); while(1);} - if( size > MAXSIZE ) return 1; //file to big - //if(fileexists(metadata)) return 1; //file already exists - - //lcdPrint("open"); lcdPrintln((const char*)metadata); lcdRefresh(); - res = f_open(&file, (const char*)metadata, FA_OPEN_ALWAYS|FA_WRITE); - - //lcdPrintln("file opened"); lcdRefresh(); - if( res ) {lcdPrintln("res"); lcdPrint(f_get_rc_string(res)); lcdRefresh(); while(1);} - if( res ) - return res; - - uint16_t wordcount = (size+3)/4; - - //nrf_set_rx_mac(0, 32, 5, mac); - //lcdPrintln("get file"); lcdRefresh(); - int fres = rftransfer_receive(buf, wordcount*4, 1000); - if( fres == -1 ){ - lcdPrintln("checksum wrong"); - }else if( fres == -2 ){ - lcdPrintln("timeout"); - }else{ - //lcdPrintln("got file"); - } - lcdRefresh(); - if( fres < 0 ) - return 1; - //nrf_set_rx_mac(0, 32, 5, macbuf); - - xxtea_decode_words((uint32_t *)buf, wordcount, k); - - res = f_write(&file, buf, size, &written); - f_close(&file); - if( res ) - return res; - if( written != size ) - return 1; //error while writing - lcdClear(); - lcdPrintln("Received"); lcdPrintln((const char*)metadata); lcdRefresh(); - - return 0; -} - -#define MAXPACKET 32 -int16_t rftransfer_receive(uint8_t *buffer, uint16_t maxlen, uint16_t timeout) -{ - uint8_t buf[MAXPACKET]; - uint8_t state = 0; - uint16_t pos = 0, seq = 0, size = 0, rand = 0, crc = 0; - int n,i; - unsigned int currentTick = systickGetTicks(); - unsigned int startTick = currentTick; - - while(systickGetTicks() < (startTick+timeout) ){//this fails if either overflows - n = nrf_rcv_pkt_time(1000, MAXPACKET, buf); - switch(state){ - case 0: - if( n == 32 && buf[0] == 'L' ){ - size = (buf[1] << 8) | buf[2]; - rand = (buf[3] << 8) | buf[4]; - seq = 0; - pos = 0; - if( size <= maxlen ){ - //lcdClear(); - //lcdPrint("got l="); lcdPrintInt(size); - //lcdPrintln(""); lcdRefresh(); - state = 1; - } - } - break; - case 1: - if( n == 32 && buf[0] == 'D' && ((buf[3]<<8)|buf[4])==rand ){ - //lcdPrint("got d"); lcdRefresh(); - if( seq == ((buf[1]<<8)|buf[2]) ){ - //lcdPrintln(" in seq"); lcdRefresh(); - for(i=5; i -#include -#include -#include -#include -#include "basic/basic.h" -#include "lcd/render.h" -#include "lcd/allfonts.h" -#include "basic/ecc.h" -#include "funk/nrf24l01p.h" -#include "filesystem/ff.h" -#include "filesystem/diskio.h" -#include "funk/filetransfer.h" -#include "lcd/print.h" - - - -uint8_t mac[5] = {1,2,3,2,1}; - -void ram(void) -{ - char file[13]; - selectFile(file,"TXT"); - sendFile(file); -} - -void sendR(uint8_t *rx, uint8_t *ry) -{ - uint8_t exp[2 + 4*NUMWORDS + 2]; - exp[0] = 'R'; - for(int i=0; i<4*NUMWORDS; i++) - exp[2+i] = rx[i]; - exp[1] = 'X'; - nrf_snd_pkt_crc(32, exp); - delayms(10); - exp[1] = 'Y'; - for(int i=0; i<4*NUMWORDS; i++) - exp[2+i] = ry[i]; - nrf_snd_pkt_crc(32, exp); - delayms(10); -} - -int receiveKey(uint8_t type, uint8_t *x, uint8_t *y) -{ - uint8_t buf[32]; - uint8_t n; - - n = nrf_rcv_pkt_time(1000, 32, buf); - if( n == 32 && buf[0] == type && buf[1] == 'X' ){ - for(int i=0; i -#include "funk/nrf24l01p.h" -#include "funk/filetransfer.h" -#include "funk/rftransfer.h" -#include "basic/basic.h" -#include "basic/xxtea.h" -#include "filesystem/ff.h" -#include "lcd/print.h" - - -//TODO: use a proper MAC to sign the message -int filetransfer_send(uint8_t *filename, uint16_t size, - uint8_t *mac, uint32_t const k[4]) -{ - uint8_t buf[MAXSIZE]; - FIL file; - FRESULT res; - UINT readbytes; - - - if( size > MAXSIZE ) - return 1; //File to big - - res=f_open(&file, (const char*)filename, FA_OPEN_EXISTING|FA_READ); - if( res ) - return res; - - //res = f_read(&file, (char *)buf, size, &readbytes); - for(uint16_t i=0; i> 8; - metadata[21] = size & 0xFF; - - //nrf_get_tx_max(5,macbuf); - - //nrf_set_tx_mac(5, mac); - nrf_snd_pkt_crc_encr(32, metadata, k); - delayms(20); - xxtea_encode_words((uint32_t *)buf, wordcount, k); - rftransfer_send(wordcount*4, buf); - //nrf_set_tx_mac(5, macbuf); - return 0; -} - -#include "funk/rftransfer.h" -#include "funk/nrf24l01p.h" -#include -#include -#include -#include - -#define MAXPACKET 32 -void rftransfer_send(uint16_t size, uint8_t *data) -{ - uint8_t buf[MAXPACKET]; - buf[0] = 'L'; - buf[1] = size >> 8; - buf[2] = size & 0xFF; - - uint16_t rand = getRandom() & 0xFFFF; - buf[3] = rand >> 8; - buf[4] = rand & 0xFF; - - nrf_snd_pkt_crc(32,buf); //setup packet - delayms(20); - uint16_t index = 0; - uint8_t i; - uint16_t crc = crc16(data,size); - - while(size){ - buf[0] = 'D'; - buf[1] = index >> 8; - buf[2] = index & 0xFF; - buf[3] = rand >> 8; - buf[4] = rand & 0xFF; - for(i=5; i0; i++,size--){ - buf[i] = *data++; - } - index++; - nrf_snd_pkt_crc(32,buf); //data packet - delayms(20); - } - - buf[0] = 'C'; - buf[1] = crc >> 8; - buf[2] = crc & 0xFF; - buf[3] = rand >> 8; - buf[4] = rand & 0xFF; - nrf_snd_pkt_crc(32,buf); //setup packet - delayms(20); -} - diff --git a/firmware/loadable/spaceinvaders.c b/firmware/loadable/spaceinvaders.c deleted file mode 100644 index fbcf38a..0000000 --- a/firmware/loadable/spaceinvaders.c +++ /dev/null @@ -1,462 +0,0 @@ -#include -#include - -#include "basic/basic.h" -#include "basic/random.h" - -#include "lcd/render.h" -#include "lcd/display.h" -#include "lcd/allfonts.h" - -/**************************************************************************/ -#define POS_PLAYER_Y 60 -#define POS_PLAYER_X RESX/2-3 -#define POS_UFO_Y 0 -#define ENEMY_ROWS 3 -#define ENEMY_COLUMNS 6 -#define DISABLED 255 - -#define UFO_PROB 1024 - -#define TYPE_PLAYER 1 -#define TYPE_ENEMY_A 3 -#define TYPE_ENEMY_B 2 -#define TYPE_ENEMY_C 4 -#define TYPE_UFO 5 - -#define BUNKERS 3 -#define BUNKER_WIDTH 10 -static const uint8_t BUNKER_X[] = {15, RESX/2-BUNKER_WIDTH/2,RESX-BUNKER_WIDTH-15}; -static const uint8_t ENEMY_WIDTHS[] = {8,10,12}; - -struct gamestate { - char player; - char ufo; - char shot_x, shot_y; - char shots_x[ENEMY_COLUMNS]; - char shots_y[ENEMY_COLUMNS]; - char alive; - int16_t move; - char direction, lastcol; - bool killed; - bool step; - uint32_t score; - uint16_t level; - int8_t rokets; - char enemy_x[ENEMY_ROWS][ENEMY_COLUMNS]; - char enemy_row_y[ENEMY_ROWS]; - uint8_t bunker[BUNKERS][BUNKER_WIDTH]; -} game; -char key; - -void init_game(); -void init_enemy(); -void check_end(); -void move_ufo(); -void move_shot(); -void move_shots(); -void move_player(); -void move_enemy(); -void draw_score(); -void draw_bunker(); -void draw_player(); -void draw_enemy(); -void draw_shots(); -void draw_sprite(char type, char x, char y); -void draw_ufo(); -void screen_intro(); -void screen_gameover(); -void screen_level(); -bool check_bunker(char xpos, char ypos, int8_t shift); - -void ram(void) { - //gpioSetValue (RB_LED1, CFG_LED_OFF); - //backlightInit(); - while(1) { - screen_intro(); - game.rokets = 3; - game.level = 1; - init_game(); - screen_level(); - while (game.rokets>=0) { - ////checkISP(); - lcdFill(0); - check_end(); - move_ufo(); - move_shot(); - move_shots(); - move_player(); - move_enemy(); - draw_score(); - draw_ufo(); - draw_bunker(); - draw_player(); - draw_enemy(); - draw_shots(); - // draw_status(); - lcdDisplay(); - delayms(12); - } - screen_gameover(); - } - return; -} - -void screen_intro() { - char key=0; - while(key==0) { - lcdFill(0); - font = &Font_Invaders; - DoString(28,25,"ABC"); - font = &Font_7x8; - DoString (28,40,"SPACE"); - DoString (18,50,"INVADERS"); - //DoString (20,RESY-24, "Highscore"); - DoString (0, 0, "12345"); - DoString (0, 9, "iggy"); - lcdDisplay(); - - delayms_queue(50); - key=getInput(); - } -} - -void screen_gameover() { - char key =0; - while(key==0) { - lcdFill(0); - font = &Font_7x8; - DoString (12,32, "GAME OVER"); - DoInt (0,0, game.score); - DoString (0,9,"HIGHSCORE!"); - lcdDisplay(); - delayms_queue(50); - key=getInput(); - } -} - -void screen_level() { - lcdFill(0); - draw_score(); - font = &Font_7x8; - int dx = DoString(20,32, "Level "); - DoInt(dx,32,game.level); - lcdDisplay(); - delayms(500); -} - -void init_game(void) { - game.player = POS_PLAYER_X; - game.shot_x = DISABLED; - game.shot_y = 0; - game.alive = ENEMY_ROWS*ENEMY_COLUMNS; - game.move = 0; - if (getRandom()%2 == 0) { - game.direction = -1; - game.lastcol = ENEMY_COLUMNS-1; - } else { - game.direction = 1; - game.lastcol = 0; - } - game.killed = 0; - game.step = false; - game.ufo = DISABLED; - game.score = 0; - init_enemy(); - - for (int col=0; colBUNKER_X[BUNKERS-1-b] && - xposRESY-16) { - int offset = BUNKER_WIDTH - (xpos-BUNKER_X[BUNKERS-1-b]); - if (game.bunker[b][offset]!=0) { - if (shift>0) - game.bunker[b][offset]&=game.bunker[b][offset]<>-shift; - return true; - } - } - } - return false; -} - -void move_shot() { - //No shot, do nothing - if(game.shot_x == DISABLED) { - return; - } - - //moving out of top, end shot - if (game.shot_y <= 0) { - game.shot_x = DISABLED; - return; - } - - if (check_bunker(game.shot_x,game.shot_y-5,1 )) - game.shot_x=DISABLED; - - //check for collision with enemy, kill enemy if - for (int row=0; row= game.shot_y && game.enemy_row_y[row]+6 < game.shot_y+7) { - for(int col = 0; col= game.enemy_x[row][col] && game.shot_x < game.enemy_x[row][col]+ENEMY_WIDTHS[row]) { - game.enemy_x[row][col]=DISABLED; - game.shot_x = DISABLED; - game.alive--; - game.score+=(3-row)*10; - return; - } - } - } - } - - //check for collision with ufo - if (game.ufo != DISABLED && - game.shot_x>game.ufo && - game.shot_x= RESY) { - game.shots_x[col] = DISABLED; - return; - } - //check for collision with bunker - if (check_bunker(game.shots_x[col],game.shots_y[col],-1)) - game.shots_x[col]=DISABLED; - - //check for collision with player - if (game.shots_y[col] >= RESY-13 && - game.shots_x[col] > game.player+1 && - game.shots_x[col] < game.player+6) { - - game.killed = true; - } - - //move shots down - game.shots_y[col] += 1; - } -} - -void move_ufo() { - if (game.ufo == DISABLED) { - if ((getRandom()%UFO_PROB)==0) { - game.ufo = 0; - } - return; - } - if (game.ufo >= RESX){ - game.ufo = DISABLED; - return; - } - game.ufo++; -} - -void move_player() { - if(gpioGetValue(RB_BTN0)==0 && game.player > 0 ){ - game.player-=1; - } - - if(gpioGetValue(RB_BTN1)==0 && game.player < RESX-8){ - game.player+=1; - } - - if(gpioGetValue(RB_BTN4)==0 && game.shot_x == 255){ - game.shot_x = game.player+4; - game.shot_y = POS_PLAYER_Y; - } -} - -void move_enemy() { - if(game.move > 0){ - game.move-=game.level/5+1; - return; - } - - game.step = !game.step; - for (int col = 0; col < ENEMY_COLUMNS; col++) { - for (int row = 0; row < ENEMY_ROWS; row++) { - char pos = game.enemy_x[row][(game.direction==1)?(ENEMY_COLUMNS-(col+1)):col]; - if (pos != DISABLED) { - //Check collision with player - if((game.enemy_row_y[row]+8 >= POS_PLAYER_Y && pos+8 >= game.player && pos < game.player+8) || - game.enemy_row_y[row]+8 >= POS_PLAYER_Y+8) { - for(int row=0; row=RESX-10 && game.direction == 1)){ - game.direction = (game.direction==1)?-1:1; - for (int r = 0; r=23?4:2; - } - return; - } - game.enemy_x[row][(game.direction==1)?(ENEMY_COLUMNS-(col+1)):col] += game.direction; - } - } - } - - game.move = game.alive*2-1; -} - -void draw_player() { - draw_sprite(TYPE_PLAYER, game.player, POS_PLAYER_Y); -} - -void draw_ufo() { - if (game.ufo!=DISABLED) - draw_sprite(TYPE_UFO, game.ufo, POS_UFO_Y); -} - -void draw_enemy() { - for (int row = 0; row Date: Sun, 11 Dec 2011 18:42:40 +0100 Subject: [PATCH 16/37] find games, join games, display text from game --- firmware/l0dable/r_player.c | 223 ++++++++++++++++++++++++++++++------ 1 file changed, 186 insertions(+), 37 deletions(-) diff --git a/firmware/l0dable/r_player.c b/firmware/l0dable/r_player.c index 9db06f9..08f6d35 100644 --- a/firmware/l0dable/r_player.c +++ b/firmware/l0dable/r_player.c @@ -42,40 +42,60 @@ struct NRF_CFG config = { //for some reason this has to be global .txmac= GAME_MAC, .nrmacs=1, .mac0= PLAYER_MAC, - .maclen ="\x10", + .maclen ="\x20", }; struct packet{ uint8_t len; uint8_t protocol; uint8_t command; + uint32_t id; + uint32_t ctr; - //union with 11 bytes data + //union with 19 bytes data union content{ struct button{ uint8_t button; - uint32_t id; - uint32_t cnt; - uint8_t reserved[2]; + uint8_t reserved[18]; }button; struct text{ uint8_t x; uint8_t y; uint8_t flags; - uint8_t text[8]; + uint8_t text[16]; }text; struct nick{ uint8_t flags; - uint8_t text[10]; + uint8_t text[18]; }nick; struct nickrequest{ - uint8_t reserved[11]; + uint8_t reserved[19]; }nickrequest; + struct ack{ + uint8_t flags; + uint8_t reserved[19]; + }ack; + struct announce{ + uint8_t gameMac[5]; + uint8_t gameChannel; + //uint8_t playerMac[5]; playerMac = gameMac+1; + uint32_t gameId; + uint8_t gameFlags; + uint8_t gameTitle[8]; + }announce; + struct join{ + uint32_t gameId; + }join; }c; uint16_t crc; }; +#define FLAGS_MASS_GAME 1 + +#define FLAGS_ACK_JOINOK 1 + +#define MASS_ID 1 /**************************************************************************/ /* l0dable for playing games which are announced by other r0kets with the l0dabel r_game */ @@ -84,23 +104,42 @@ struct packet{ * T: packet sent by game, contain text for display * N: packet sent by game, requesting nick * n: packet sent player, containing nick + * A: packet sent by game, announcing game + * J: packet sent by player, requesting to join game + * a: ack, packet with $ctr was received */ + uint32_t ctr; uint32_t id; +uint32_t gameId; void sendButton(uint8_t button); -void sendJoin(uint8_t game); +void sendJoin(uint32_t game); void processPacket(struct packet *p); +void processAnnounce(struct announce *a); + +uint8_t selectGame(); +void playGame(); + +struct announce games[10]; +uint8_t gamecount; void ram(void) { - nrf_config_set(&config); id = getRandom(); ctr = 1; + + while( selectGame() ){ + playGame(); + } +}; + +void playGame(void) +{ int len; struct packet p; - + while(1){ uint8_t button = getInputRaw(); sendButton(button); @@ -115,57 +154,167 @@ void ram(void) } delayms(20); }; -}; -//getInputRaw(); +} + +void showGames(uint8_t selected) +{ + int i; + lcdClear(); + lcdPrintln("Games:"); + if( gamecount ){ + for(i=0;i 0 ){ + selected--; + } + break; + case BTN_LEFT: + return 0; + case BTN_ENTER: + case BTN_RIGHT: + if( gamecount == 0 ) + return 0; + gameId = games[selected].gameId; + memcpy(config.txmac, games[selected].gameMac, 5); + memcpy(config.mac0, games[selected].gameMac, 5); + config.mac0[4]++; + config.channel = games[selected].gameChannel; + nrf_config_set(&config); + if( games[selected].gameFlags & FLAGS_MASS_GAME ) + return 1; + else + return joinGame(); + } + } +} + + void processPacket(struct packet *p) { - if ((p->len==16) && (p->protocol=='G')){ //check sanity, protocol + if ((p->len==32) && (p->protocol=='G') && p->id == id){ //check sanity, protocol, id if (p->command=='T'){ //processText(&(p->c.text)); } else if (p->command=='N'){ //processNick(&(p->c.nickrequest)); } + else if (p->command=='A'){ + processAnnounce(&(p->c.announce)); + } } } +void processAnnounce(struct announce *a) +{ + if( gamecount < sizeof(games)/sizeof(games[0]) ){ + games[gamecount] = *a; + gamecount++; + } +} //increment ctr and send button state, id and ctr void sendButton(uint8_t button) { - uint8_t buf[16]; - buf[0]=0x10; // Length: 16 bytes - buf[1]='G'; // Proto - buf[2]='B'; - buf[3]=button; - - ctr++; - *(int*)(buf+4)=ctr; - ctr+=4; - *(int*)(buf+4)=id; + struct packet p; + p.len=sizeof(p); + p.protocol='G'; // Proto + p.command='B'; + p.id= id; + p.ctr= ++ctr; + p.c.button.button=button; //lcdClear(); //lcdPrint("Key:"); lcdPrintInt(buf[2]); lcdNl(); - nrf_snd_pkt_crc(16,buf); + nrf_snd_pkt_crc(sizeof(p),(uint8_t*)&p); } //send join request for game -void sendJoin(uint8_t game) +void sendJoin(uint32_t game) { - uint8_t buf[16]; - buf[0]=0x10; // Length: 16 bytes - buf[1]='G'; // Proto - buf[2]='J'; - buf[3]=game; + struct packet p; + p.len=sizeof(p); + p.protocol='G'; + p.command='J'; + p.ctr= ++ctr; + p.id=id; + p.c.join.gameId=game; - ctr++; - *(int*)(buf+4)=ctr; - ctr+=4; - *(int*)(buf+4)=id; - - nrf_snd_pkt_crc(16,buf); + nrf_snd_pkt_crc(sizeof(p),(uint8_t*)&p); } From e359032580523acc9467e2bac1d2f07dd4a1df87 Mon Sep 17 00:00:00 2001 From: schneider Date: Mon, 12 Dec 2011 18:00:14 +0100 Subject: [PATCH 17/37] structs neeed to be packed, added some dbg output --- firmware/l0dable/r_player.c | 51 ++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/firmware/l0dable/r_player.c b/firmware/l0dable/r_player.c index 08f6d35..6163461 100644 --- a/firmware/l0dable/r_player.c +++ b/firmware/l0dable/r_player.c @@ -37,13 +37,7 @@ //#error "CDC is not defined" //#endif -struct NRF_CFG config = { //for some reason this has to be global - .channel= REMOTE_CHANNEL, - .txmac= GAME_MAC, - .nrmacs=1, - .mac0= PLAYER_MAC, - .maclen ="\x20", - }; +struct NRF_CFG config; struct packet{ uint8_t len; @@ -57,25 +51,24 @@ struct packet{ struct button{ uint8_t button; uint8_t reserved[18]; - }button; - + }__attribute__((packed)) button; struct text{ uint8_t x; uint8_t y; uint8_t flags; uint8_t text[16]; - }text; + }__attribute__((packed)) text; struct nick{ uint8_t flags; uint8_t text[18]; - }nick; + }__attribute__((packed)) nick; struct nickrequest{ uint8_t reserved[19]; - }nickrequest; + }__attribute__((packed)) nickrequest; struct ack{ uint8_t flags; - uint8_t reserved[19]; - }ack; + uint8_t reserved[18]; + }__attribute__((packed)) ack; struct announce{ uint8_t gameMac[5]; uint8_t gameChannel; @@ -83,13 +76,16 @@ struct packet{ uint32_t gameId; uint8_t gameFlags; uint8_t gameTitle[8]; - }announce; + }__attribute__((packed)) announce; struct join{ uint32_t gameId; - }join; + uint8_t reserved[15]; + }__attribute__((packed)) join; }c; uint16_t crc; -}; +}__attribute__((packed)); + +#define sizeof(p) (sizeof(struct packet)) #define FLAGS_MASS_GAME 1 @@ -122,11 +118,14 @@ void processAnnounce(struct announce *a); uint8_t selectGame(); void playGame(); -struct announce games[10]; +struct announce games[7]; uint8_t gamecount; void ram(void) { + config.nrmacs=1; + config.maclen[0] = 32; + id = getRandom(); ctr = 1; @@ -147,7 +146,7 @@ void playGame(void) while(1){ len = nrf_rcv_pkt_time(30,sizeof(p),(uint8_t*)&p); if(len==sizeof(p)){ - processPacket(&p); + processPacket(&p); }else{ break; } @@ -181,6 +180,7 @@ void showGames(uint8_t selected) uint8_t joinGame() { int i; + lcdClear(); for(i=0; i<10; i++){ struct packet p; p.len=sizeof(p); @@ -189,8 +189,9 @@ uint8_t joinGame() p.id= id; p.ctr= ++ctr; p.c.join.gameId=gameId; - nrf_snd_pkt_crc(sizeof(p),(uint8_t*)&p); - + int r = nrf_snd_pkt_crc(sizeof(p),(uint8_t*)&p); + lcdPrint("send: "); lcdPrintInt(r);lcdPrintln(""); + lcdRefresh(); int len; len = nrf_rcv_pkt_time(30,sizeof(p),(uint8_t*)&p); if( len==sizeof(p) ){ @@ -212,16 +213,18 @@ uint8_t selectGame() { int len, i, selected; struct packet p; - + int a = 0; config.channel = REMOTE_CHANNEL; memcpy(config.txmac, GAME_MAC, 5); memcpy(config.mac0, PLAYER_MAC, 5); nrf_config_set(&config); gamecount = 0; - for(i=0;i<30;i++){ + for(i=0;i<60;i++){ len= nrf_rcv_pkt_time(30, sizeof(p), (uint8_t*)&p); if (len==sizeof(p)){ + if( a ) a = 0; else a = 1; + gpioSetValue (RB_LED2, a); processPacket(&p); } } @@ -265,7 +268,7 @@ uint8_t selectGame() void processPacket(struct packet *p) { - if ((p->len==32) && (p->protocol=='G') && p->id == id){ //check sanity, protocol, id + if ((p->len==32) && (p->protocol=='G') && (p->id == id || p->id == 0) ){ //check sanity, protocol, id if (p->command=='T'){ //processText(&(p->c.text)); } From 50fd8a6613303736092c85e023d448b6c4891e0c Mon Sep 17 00:00:00 2001 From: schneider Date: Mon, 12 Dec 2011 18:01:01 +0100 Subject: [PATCH 18/37] bridge: default maclen 32bytes --- firmware/applications/schneider.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/firmware/applications/schneider.c b/firmware/applications/schneider.c index cf7c3e4..ae8d5e5 100644 --- a/firmware/applications/schneider.c +++ b/firmware/applications/schneider.c @@ -40,7 +40,7 @@ struct NRF_CFG config = { .txmac= MAC, .nrmacs=1, .mac0= MAC, - .maclen ="\x10", + .maclen ="\x20", }; uint8_t serialmsg_message[SERIAL_PACKETLEN]; @@ -79,6 +79,7 @@ void main_schneider(void) // can we loose packets here? nrf_rcv_pkt_end(); status=snd_pkt_no_crc(serialmsg_len, serialmsg_message); + //status=nrf_snd_pkt_crc(serialmsg_len, serialmsg_message); nrf_rcv_pkt_start(); break; case '3': From 8fc54d1b2dec8bc9c11aaf6293abdd95de7f9e91 Mon Sep 17 00:00:00 2001 From: schneider Date: Mon, 12 Dec 2011 18:02:24 +0100 Subject: [PATCH 19/37] added first drafts of simple gamelib --- tools/game/r0ketrem0te/__init__.py | 0 tools/game/r0ketrem0te/rem0te.py | 53 +++++++++++++ tools/game/r0ketrem0te/serialinterface.py | 92 +++++++++++++++++++++++ tools/game/setup.py | 5 ++ tools/game/simpletest.py | 11 +++ 5 files changed, 161 insertions(+) create mode 100644 tools/game/r0ketrem0te/__init__.py create mode 100644 tools/game/r0ketrem0te/rem0te.py create mode 100644 tools/game/r0ketrem0te/serialinterface.py create mode 100644 tools/game/setup.py create mode 100644 tools/game/simpletest.py diff --git a/tools/game/r0ketrem0te/__init__.py b/tools/game/r0ketrem0te/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tools/game/r0ketrem0te/rem0te.py b/tools/game/r0ketrem0te/rem0te.py new file mode 100644 index 0000000..080e82b --- /dev/null +++ b/tools/game/r0ketrem0te/rem0te.py @@ -0,0 +1,53 @@ +import serialinterface +import thread +import threading +import Queue +import crcmod + +class r0ket: + def __init__(self, path2device): + self.ser = serialinterface.SerialInterface(path2device, 115200, 0) + self.free = threading.Lock() + self.packets = Queue.Queue() + thread.start_new_thread(self.readerThread,()) + self.setPacketLength(0x20) + self.crc = crcmod.predefined.mkCrcFun('crc-ccitt-false') + + def writeCommand(self, command, data): + crc = self.crc(data) + data += chr(crc>>8); + data += chr(crc&0xFF); + self.free.acquire() + print 'sending command:', command, 'len:', len(data), 'data:', list(data) + self.ser.writeMessage(command,data); + + def readerThread(self): + while True: + try: + (command, data) = self.ser.readMessage() + if command == '1': + self.newPacket(data) + elif command == '2': + self.free.release() + elif command: + while True: + pass + except Exception as e: + print e + + def newPacket(self, data): + print "received:", list(data) + self.packets.put(data) + + def gotPacket(self): + return not self.packets.empty() + + def getPacket(self): + return self.packets.get() + + def sendPacket(self, packet): + self.writeCommand('1', packet) + + def setPacketLength(self, length): + self.free.acquire() + self.ser.writeMessage('6', '%c'%length) diff --git a/tools/game/r0ketrem0te/serialinterface.py b/tools/game/r0ketrem0te/serialinterface.py new file mode 100644 index 0000000..67821c1 --- /dev/null +++ b/tools/game/r0ketrem0te/serialinterface.py @@ -0,0 +1,92 @@ +import serial +import string +import sys +import time + +class SerialInterface: + def __init__ ( self, path2device, baudrate, timeout=0): + self.portopen = False + while not self.portopen: + try: + self.ser = serial.Serial(path2device, baudrate) + self.path2device = path2device + self.baudrate = baudrate + self.timeout = timeout+1 + self.ser.flushInput() + self.ser.flushOutput() + if timeout: + self.ser.setTimeout(timeout+1) + self.portopen = True + except serial.SerialException: + print "exception while opening" + pass + time.sleep(1) + #print "done" + def close(self): + try: + self.portopen = False + self.ser.close() + except serial.SerialException: + pass + def reinit(self): + self.close() + print "reopening" + while not self.portopen: + self.__init__(self.path2device, self.baudrate, self.timeout) + time.sleep(1) + print "done" + + def writeMessage(self,command,message): + enc = "\\"+ command + message.replace('\\','\\\\') + "\\0"; + #print 'writing %s' % list(enc) + try: + self.ser.write(enc) + except : + pass + #self.reinit() + + def readMessage(self): + data = "" + escaped = False + stop = False + start = False + inframe = False + command = '' + while True: + starttime = time.time() + c = self.ser.read(1) + endtime = time.time() + if len(c) == 0: #A timout occured + if endtime-starttime < self.timeout - 1: + print "port broken" + self.reinit() + raise Exception() + else: + #print 'TIMEOUT' + return (False, '') + if escaped: + if c == '\\': + d = '\\' + elif c == '0': + stop = True + inframe = False + else: + start = True + inframe = True + command = c + data = "" + escaped = False + elif c == '\\': + escaped = 1 + else: + d = c + + if start and inframe: + start = False + elif stop: + #print 'received message: len=%d data=%s'%(len(data),data) + #print 'received message. command=',command, "data=" ,list(data) + return (command, data) + elif escaped == False and inframe: + data += str(d) + diff --git a/tools/game/setup.py b/tools/game/setup.py new file mode 100644 index 0000000..9ef3851 --- /dev/null +++ b/tools/game/setup.py @@ -0,0 +1,5 @@ +from distutils.core import setup +setup(name='r0ketrem0te', + version='1.0', + packages=['r0ketrem0te'], + ) diff --git a/tools/game/simpletest.py b/tools/game/simpletest.py new file mode 100644 index 0000000..f757cae --- /dev/null +++ b/tools/game/simpletest.py @@ -0,0 +1,11 @@ +import r0ketrem0te.rem0te +import time +r = r0ketrem0te.rem0te.r0ket('/dev/ttyACM0') + +while True: + r.sendPacket("\x20GA\x00\x00\x00\x00\x01\x02\x03\x04\x01\x02\x03\x01\x02\x51\x01\x02\x03\x04\x00test\x00\x06\x07\x08") + time.sleep(1) + #packet = r.getPacket() + #print list(packet) + #r.sendPacket('12345678901234567890123456789012') + From 2190feafcc246f4be2ec2a60fc95bc54d1675b99 Mon Sep 17 00:00:00 2001 From: schneider Date: Mon, 12 Dec 2011 21:06:57 +0100 Subject: [PATCH 20/37] disabled CRP in linker script --- firmware/lpc1xxx/linkscript.ld | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/firmware/lpc1xxx/linkscript.ld b/firmware/lpc1xxx/linkscript.ld index 7138861..a6a7523 100644 --- a/firmware/lpc1xxx/linkscript.ld +++ b/firmware/lpc1xxx/linkscript.ld @@ -37,9 +37,11 @@ SECTIONS { KEEP(*(.irq_vectors)) KEEP(*(table)) - *(.text.main) -. = 0x000002FC ; /* or 1FC for LPC2000 */ +/* or 1FC for LPC2000*/ +/* *(.text.main) +. = 0x000002FC ; KEEP(*(crp)) +*/ *(.text*) *(.rodata*) } > flash From 0517035110c91cfc762b5fc2a123de50d2d2a401 Mon Sep 17 00:00:00 2001 From: schneider Date: Wed, 14 Dec 2011 17:59:32 +0100 Subject: [PATCH 21/37] modified system to work with threads --- tools/game/r0ketrem0te/packets.py | 197 ++++++++++++++++++++++++++++++ tools/game/r0ketrem0te/rem0te.py | 167 ++++++++++++++++++++++--- tools/game/simpletest.py | 28 ++++- 3 files changed, 371 insertions(+), 21 deletions(-) create mode 100644 tools/game/r0ketrem0te/packets.py diff --git a/tools/game/r0ketrem0te/packets.py b/tools/game/r0ketrem0te/packets.py new file mode 100644 index 0000000..b5e6e7f --- /dev/null +++ b/tools/game/r0ketrem0te/packets.py @@ -0,0 +1,197 @@ +def inttouint32(v): + return chr(v&0xff)+chr((v>>8)&0xff)+chr((v>>16)&0xff)+chr((v>>24)&0xff) + +def uint32toint(v): + return (ord(v[3])<< 24) + (ord(v[2])<<16) + (ord(v[1])<<8) + (ord(v[0])) + +class Packet: + def __init__(self, command, id=None, ctr=None): + if id == None and ctr == None: + message = command + command = message[2] + id = uint32toint(message[3:7]) + ctr = uint32toint(message[7:11]) + self.length = 32 + self.protocol = 'G' + self.command = command + self.id = id + self.ctr = ctr + + def toMessage(self): + message = chr(self.length) + message += self.protocol + message += self.command + message += inttouint32(self.id) + message += inttouint32(self.ctr) + return message + + def headerString(self): + return "id=%d, ctr=%d"%(self.id, self.ctr) + + def __str__(self): + s = "Packet with protocol=" + self.protocol + s += ", command=" + self.command + s += ", "+ self.headerString() + return s + +class Button(Packet): + def __init__(self, id, ctr=None, button=None): + if ctr != None and button!= None: + Packet.__init__(self, 'B', id, ctr) + else: + message = id + Packet.__init__(self, message) + button = ord(message[11]) + self.button = button + + def toMessage(self): + base = Packet.toMessage(self) + return base + chr(self.button) + '\x00'*18 + + def __str__(self): + s = "Button packet with " + self.headerString() + s += ", button=%d"%self.button + return s + +class Announce(Packet): + def __init__(self, id, ctr, gameMac, gameChannel, gameId, gameFlags, gameTitle): + Packet.__init__(self, 'A', id, ctr) + self.gameMac = gameMac + self.gameChannel = gameChannel + self.gameId = gameId + self.gameFlags = gameFlags + self.gameTitle = gameTitle[0:8] + + def toMessage(self): + message = Packet.toMessage(self) + message += ''.join([chr(x) for x in self.gameMac]) + message += chr(self.gameChannel) + message += inttouint32(self.gameId) + message += chr(self.gameFlags) + message += self.gameTitle + if len(self.gameTitle) < 8: + message += '\x00'*(8-len(self.gameTitle)) + return message + + def __str__(self): + s = "Announce packet with " + self.headerString() + s += ", gameMac="+str(self.gameMac) + s += ", gameChannel=%d"%self.gameChannel + s += ", gameId=%d"%self.gameId + s += ", gameFlags=%d"%self.gameFlags + s += ", gameTitle="+self.gameTitle + return s + +class Join(Packet): + def __init__(self, id, ctr=None, gameId=None): + if ctr != None and gameId != None: + Packet.__init__(self, 'J', id, ctr) + else: + message = id + Packet.__init__(self, message) + gameId = uint32toint(message[11:15]) + self.gameId = gameId + + def toMessage(self): + message = Packet.toMessage(self) + message += inttouint32(self.gameId) + message += '\x00'*15 + return message + + def __str__(self): + s = "Join packet with " + self.headerString() + s += ", gameId=%d"%self.gameId + return s + +class Ack(Packet): + def __init__(self, id, ctr=None, flags=None): + if ctr != None and flags != None: + Packet.__init__(self, 'a', id, ctr) + else: + message = id + Packet.__init__(self, message) + flags = ord(message[11]) + self.flags = flags + + def toMessage(self): + message = Packet.toMessage(self) + message += chr(self.flags) + message += '\x00'*18 + return message + + def __str__(self): + s = "Ack packet with " + self.headerString() + s += ", flags=%d"%self.flags + return s + +class Nickrequest(Packet): + def __init__(self, id, ctr): + Packet.__init__(self, 'N', id, ctr) + + def __str__(self): + s = "Nickrequest packet with " + self.headerString() + return s + +class Nick(Packet): + def __init__(self, id, ctr=None, flags=None, nick=None): + if ctr != None and flags != None and nick != None: + Packet.__init__(self, 'n', id, ctr) + else: + message = id + Packet.__init__(self, message) + flags = ord(message[11]) + nick = message[12:30].rstrip(' \t\r\n\0') + self.flags = flags + self.nick = nick + + def toMessage(self): + message = Packet.toMessage(self) + message += chr(self.flags) + message += self.nick + if len(self.nick) < 18: + message += '\x00'*(18-len(self.nick)) + return message + + def __str__(self): + s = "Nick packet with " + self.headerString() + s += ", flags=%d"%self.flags + s += ", nick="+self.nick + return s + +class Text(Packet): + def __init__(self, id, ctr, x, y, flags, text): + Packet.__init__(self, 'T', id, ctr) + self.x = x + self.y = y + self. flags = flags + self.text = text[0:16] + + def toMessage(self): + message = Packet.toMessage(self) + message += chr(self.x) + message += chr(self.y) + message += chr(self.flags) + message += self.text + if len(self.text) < 16: + message += '\x00'*(16-len(self.text)) + return message + + def __str__(self): + s = "Text packet with " + self.headerString() + s += ", x=%d"%self.x + s += ", y=%d"%self.y + s += ", flags=%d"%self.flags + s += ", text="+self.text + return s + +def fromMessage(message): + if len(message) >= 30 and ord(message[0]) == 32 and message[1] == 'G': + if message[2] == 'B': + return Button(message) + if message[2] == 'n': + return Nick(message) + if message[2] == 'J': + return Join(message) + if message[2] == 'a': + return Ack(message) + return None diff --git a/tools/game/r0ketrem0te/rem0te.py b/tools/game/r0ketrem0te/rem0te.py index 080e82b..3882eb1 100644 --- a/tools/game/r0ketrem0te/rem0te.py +++ b/tools/game/r0ketrem0te/rem0te.py @@ -1,25 +1,135 @@ import serialinterface -import thread import threading import Queue import crcmod +import packets -class r0ket: +class QueuePacket: + def __init__(self, channel, mac, acked, packet): + self.channel = channel + self.mac = mac + self.acked = acked + self.packet = packet + self.priority = 5 + self.retriesleft = 5 + self.timeout = 0.1 + self.timer = None + self.timedout = False + self.lock = threading.RLock() + self.isdone = False + + def __cmp__(self, other): + if not isinstance(other,QueuePacket): + return 1 + if self.priority < other.priority: + return -1 + if self.priority > other.priority: + return 1 + return 0 + + def valid(self): + with self.lock: + return self.retriesleft > 0 and not self.acked + + def sent(self, timeoutcallback): + with self.lock: + self.timedout = False + if self.acked: + self.timeoutcallback = timeoutcallback + self.timer = threading.Timer(self.timeout, self.timercallback) + self.timer.start() + + def done(self): + with self.lock: + if self.timer != None: + self.timer.cancel() + self.timer = None + self.isdone = True + + def timercallback(self): + with self.lock: + self.timedout = True + self.timeoutcallback(self) + +class Bridge: def __init__(self, path2device): self.ser = serialinterface.SerialInterface(path2device, 115200, 0) self.free = threading.Lock() - self.packets = Queue.Queue() - thread.start_new_thread(self.readerThread,()) - self.setPacketLength(0x20) + self.queueslock = threading.Lock() + self.packets = Queue.PriorityQueue() + self.outpackets = Queue.Queue() self.crc = crcmod.predefined.mkCrcFun('crc-ccitt-false') + self.queues = {} + + self.reader = threading.Thread(target = self.readerThread) + self.reader.daemon = True + + self.writer = threading.Thread(target = self.writerThread) + self.writer.daemon = True + + self.writer.start() + self.reader.start() - def writeCommand(self, command, data): - crc = self.crc(data) - data += chr(crc>>8); - data += chr(crc&0xFF); - self.free.acquire() - print 'sending command:', command, 'len:', len(data), 'data:', list(data) - self.ser.writeMessage(command,data); + self.setPacketLength(0x20) + self.setTxMAC((1,2,3,2,1)) + self.setRxMAC((1,2,3,2,1)) + self.setChannel(81) + + def registerQueue(self, queue): + if queue not in self.queues: + self.queues[queue] = None + + def putInQueue(self, queue, qp): + if queue in self.queues: + queue.put(qp); + self.checkQueues() + + def processAck(self, ack): + #find the corresponding packet in the queues + found = False + for pq in self.queues.values(): + if pq.packet.id == ack.id and pq.packet.ctr == ack.ctr: + #notify it + pq.done() + found = True + #notify the queue system + if found: + self.checkQueues() + else: + print "got an ack for an unknown packet" + + def packetTimeout(self, qp): + self.checkQueues() + + def checkQueues(self): + with self.queueslock: + for q in self.queues: + #check if a packet has to be resent + #remove it from the packet slot if it has been resent to often + qp = self.queues[q] + if qp != None: + if qp.valid(): + self.queues[q] = None + elif qp.timedout: + print "packet timed out" + qp.packet + self.outpackets.put(qp) + #check if a idle queue has a new packet in line + qp = self.queues[q] + if qp == None and not q.empty(): + qp = q.get() + self.queues[q] = qp + self.outpackets.put(qp) + + def writerThread(self): + while True: + try: + #wait until we have packets to take care of + qp = self.outpackets.get() + #send it and notify the queuepacket + self.sendPacket(qp.packet) + qp.sent(self.packetTimeout) + except Exception as e: + print e def readerThread(self): while True: @@ -36,8 +146,15 @@ class r0ket: print e def newPacket(self, data): - print "received:", list(data) - self.packets.put(data) + #print "received:", list(data) + crc = self.crc(data[:-2]) + if data[-2:] == chr(crc>>8) + chr(crc&0xFF): + packet = packets.fromMessage(data) + print "received:", packet + if isinstance(packet,packets.Ack): + self.ProcessAck(packet) + else: + self.packets.put(packet) def gotPacket(self): return not self.packets.empty() @@ -46,8 +163,28 @@ class r0ket: return self.packets.get() def sendPacket(self, packet): - self.writeCommand('1', packet) + print 'sending', packet + data = packet.toMessage() + crc = self.crc(data) + data += chr(crc>>8); + data += chr(crc&0xFF); + self.free.acquire() + #print 'sending packet: len:', len(data), 'data:', list(data) + self.ser.writeMessage('1',data); def setPacketLength(self, length): self.free.acquire() self.ser.writeMessage('6', '%c'%length) + + def setTxMAC(self, mac): + self.free.acquire() + self.ser.writeMessage('3', ''.join([chr(x) for x in mac])) + + def setRxMAC(self, mac): + self.free.acquire() + self.ser.writeMessage('4', ''.join([chr(x) for x in mac])) + + def setChannel(self, channel): + self.free.acquire() + self.ser.writeMessage('5', '%c'%channel) + diff --git a/tools/game/simpletest.py b/tools/game/simpletest.py index f757cae..cff1019 100644 --- a/tools/game/simpletest.py +++ b/tools/game/simpletest.py @@ -1,11 +1,27 @@ import r0ketrem0te.rem0te +import r0ketrem0te.packets import time -r = r0ketrem0te.rem0te.r0ket('/dev/ttyACM0') +import Queue + +announcequeue = Queue.Queue() +r = r0ketrem0te.rem0te.Bridge('/dev/ttyACM0') + +r.registerQueue(announcequeue) + +a = r0ketrem0te.packets.Announce(0,2,(1,2,3,2,1), 81, 1, 0, "testgame") +aq = r0ketrem0te.rem0te.QueuePacket(81, (1,2,3,2,1), False, a) +aq.priority = 4 while True: - r.sendPacket("\x20GA\x00\x00\x00\x00\x01\x02\x03\x04\x01\x02\x03\x01\x02\x51\x01\x02\x03\x04\x00test\x00\x06\x07\x08") - time.sleep(1) - #packet = r.getPacket() - #print list(packet) - #r.sendPacket('12345678901234567890123456789012') + r.putInQueue(announcequeue, aq) + for i in range(1,1000): + if r.gotPacket(): + packet = r.getPacket() + if isinstance(packet, r0ketrem0te.packets.Join): + r.sendPacket(r0ketrem0te.packets.Ack(packet.id, packet.ctr, 1)) + time.sleep(.001) + + + + From 6609d3253c2c5ee055c2eb47ed47af68c059957c Mon Sep 17 00:00:00 2001 From: schneider Date: Thu, 15 Dec 2011 16:56:06 +0100 Subject: [PATCH 22/37] remote: added done/timeout callback, channel/mac switch --- tools/game/r0ketrem0te/rem0te.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/tools/game/r0ketrem0te/rem0te.py b/tools/game/r0ketrem0te/rem0te.py index 3882eb1..d7abe9f 100644 --- a/tools/game/r0ketrem0te/rem0te.py +++ b/tools/game/r0ketrem0te/rem0te.py @@ -5,7 +5,7 @@ import crcmod import packets class QueuePacket: - def __init__(self, channel, mac, acked, packet): + def __init__(self, channel, mac, acked, packet, callback=None): self.channel = channel self.mac = mac self.acked = acked @@ -17,6 +17,7 @@ class QueuePacket: self.timedout = False self.lock = threading.RLock() self.isdone = False + self.callback = callback def __cmp__(self, other): if not isinstance(other,QueuePacket): @@ -34,10 +35,14 @@ class QueuePacket: def sent(self, timeoutcallback): with self.lock: self.timedout = False + if self.retrieslef > 0: + self.retriesleft-=1 if self.acked: self.timeoutcallback = timeoutcallback self.timer = threading.Timer(self.timeout, self.timercallback) self.timer.start() + elif self.callback: + self.callback('done') def done(self): with self.lock: @@ -45,11 +50,15 @@ class QueuePacket: self.timer.cancel() self.timer = None self.isdone = True + if self.callback: + self.callback('done') def timercallback(self): with self.lock: self.timedout = True self.timeoutcallback(self) + if retriesleft == 0: + self.callback('timeout') class Bridge: def __init__(self, path2device): @@ -69,6 +78,11 @@ class Bridge: self.writer.start() self.reader.start() + + self.packetlength = None + self.txmac = None + self.rxmac = None + self.channel = None self.setPacketLength(0x20) self.setTxMAC((1,2,3,2,1)) @@ -127,7 +141,10 @@ class Bridge: qp = self.outpackets.get() #send it and notify the queuepacket self.sendPacket(qp.packet) + self.setTxMac(qp.mac) + self.setChannel(qp.channel) qp.sent(self.packetTimeout) + self.setChannel(self.gameChannel) except Exception as e: print e @@ -173,18 +190,30 @@ class Bridge: self.ser.writeMessage('1',data); def setPacketLength(self, length): + if length == self.packetlength: + return self.free.acquire() self.ser.writeMessage('6', '%c'%length) + self.packetLength = length def setTxMAC(self, mac): + if mac == self.txmax: + return self.free.acquire() self.ser.writeMessage('3', ''.join([chr(x) for x in mac])) + self.txmac = mac def setRxMAC(self, mac): + if mac == self.rxmac: + return self.free.acquire() self.ser.writeMessage('4', ''.join([chr(x) for x in mac])) + self.rxmac = mac def setChannel(self, channel): + if channel == self.channel: + return self.free.acquire() self.ser.writeMessage('5', '%c'%channel) + self.channel = channel From e994e7fb2479dd23c5112a9c0fd8ebbb87c98ae1 Mon Sep 17 00:00:00 2001 From: schneider Date: Thu, 15 Dec 2011 19:09:05 +0100 Subject: [PATCH 23/37] remote: added game class and callbacks --- .../game/r0ketrem0te/{rem0te.py => bridge.py} | 42 +++++++++++------ tools/game/r0ketrem0te/game.py | 34 ++++++++++++++ tools/game/r0ketrem0te/packets.py | 47 ++++++++++--------- tools/game/simpletest.py | 32 ++++++------- 4 files changed, 102 insertions(+), 53 deletions(-) rename tools/game/r0ketrem0te/{rem0te.py => bridge.py} (86%) create mode 100644 tools/game/r0ketrem0te/game.py diff --git a/tools/game/r0ketrem0te/rem0te.py b/tools/game/r0ketrem0te/bridge.py similarity index 86% rename from tools/game/r0ketrem0te/rem0te.py rename to tools/game/r0ketrem0te/bridge.py index d7abe9f..024edfe 100644 --- a/tools/game/r0ketrem0te/rem0te.py +++ b/tools/game/r0ketrem0te/bridge.py @@ -10,7 +10,7 @@ class QueuePacket: self.mac = mac self.acked = acked self.packet = packet - self.priority = 5 + self.priority = packet.priority self.retriesleft = 5 self.timeout = 0.1 self.timer = None @@ -35,7 +35,7 @@ class QueuePacket: def sent(self, timeoutcallback): with self.lock: self.timedout = False - if self.retrieslef > 0: + if self.retriesleft > 0: self.retriesleft-=1 if self.acked: self.timeoutcallback = timeoutcallback @@ -61,7 +61,7 @@ class QueuePacket: self.callback('timeout') class Bridge: - def __init__(self, path2device): + def __init__(self, path2device, channel, rxmac): self.ser = serialinterface.SerialInterface(path2device, 115200, 0) self.free = threading.Lock() self.queueslock = threading.Lock() @@ -69,6 +69,7 @@ class Bridge: self.outpackets = Queue.Queue() self.crc = crcmod.predefined.mkCrcFun('crc-ccitt-false') self.queues = {} + self.callbacks = [] self.reader = threading.Thread(target = self.readerThread) self.reader.daemon = True @@ -83,11 +84,16 @@ class Bridge: self.txmac = None self.rxmac = None self.channel = None + self.gameChannel = channel self.setPacketLength(0x20) - self.setTxMAC((1,2,3,2,1)) - self.setRxMAC((1,2,3,2,1)) - self.setChannel(81) + self.setRxMAC(rxmac) + self.setChannel(channel) + + self.ctr = 0 + def registerCallback(self, callback): + if callback not in self.callbacks: + self.callbacks.append(callback) def registerQueue(self, queue): if queue not in self.queues: @@ -131,6 +137,9 @@ class Bridge: qp = self.queues[q] if qp == None and not q.empty(): qp = q.get() + if not isinstance(qp.packet,packets.Ack): + self.ctr+=1 + qp.packet.ctr = self.ctr self.queues[q] = qp self.outpackets.put(qp) @@ -140,9 +149,9 @@ class Bridge: #wait until we have packets to take care of qp = self.outpackets.get() #send it and notify the queuepacket - self.sendPacket(qp.packet) - self.setTxMac(qp.mac) + self.setTxMAC(qp.mac) self.setChannel(qp.channel) + self.sendPacket(qp.packet) qp.sent(self.packetTimeout) self.setChannel(self.gameChannel) except Exception as e: @@ -171,13 +180,15 @@ class Bridge: if isinstance(packet,packets.Ack): self.ProcessAck(packet) else: - self.packets.put(packet) + for callback in self.callbacks: + callback(packet) + #self.packets.put(packet) - def gotPacket(self): - return not self.packets.empty() +# def gotPacket(self): +# return not self.packets.empty() - def getPacket(self): - return self.packets.get() +# def getPacket(self): +# return self.packets.get() def sendPacket(self, packet): print 'sending', packet @@ -197,9 +208,10 @@ class Bridge: self.packetLength = length def setTxMAC(self, mac): - if mac == self.txmax: + if mac == self.txmac: return self.free.acquire() + print "setting tx mac", mac self.ser.writeMessage('3', ''.join([chr(x) for x in mac])) self.txmac = mac @@ -207,6 +219,7 @@ class Bridge: if mac == self.rxmac: return self.free.acquire() + print "setting rx mac", mac self.ser.writeMessage('4', ''.join([chr(x) for x in mac])) self.rxmac = mac @@ -214,6 +227,7 @@ class Bridge: if channel == self.channel: return self.free.acquire() + print "setting channel", channel self.ser.writeMessage('5', '%c'%channel) self.channel = channel diff --git a/tools/game/r0ketrem0te/game.py b/tools/game/r0ketrem0te/game.py new file mode 100644 index 0000000..eb54596 --- /dev/null +++ b/tools/game/r0ketrem0te/game.py @@ -0,0 +1,34 @@ +import bridge +import packets +import time +import Queue +import random +import threading + +class Game: + def __init__(self, device, gameName, gameChannel, announcechannel, announcemac): + self.gameName = gameName + self.channel = gameChannel + self.gamemac = [int(random.random()*254) for x in range(1,6)] + self.playermac = list(self.gamemac) + self.playermac[4]+=1 + self.gameid = int(random.random()*(2**31)) + + self.bridge = bridge.Bridge(device, self.channel, self.gamemac) + self.announce = packets.Announce(self.gamemac, self.channel, + self.gameid, 0, "testgame") + + self.announcequeue = Queue.Queue() + self.bridge.registerQueue(self.announcequeue) + self.announcechannel = announcechannel + self.announcemac = announcemac + + self.sendAnnounce() + + def sendAnnounce(self): + aq = bridge.QueuePacket(self.announcechannel, + self.announcemac, False, self.announce) + self.bridge.putInQueue(self.announcequeue, aq) + self.announcetimer = threading.Timer(1, self.sendAnnounce) + self.announcetimer.start() + diff --git a/tools/game/r0ketrem0te/packets.py b/tools/game/r0ketrem0te/packets.py index b5e6e7f..9646603 100644 --- a/tools/game/r0ketrem0te/packets.py +++ b/tools/game/r0ketrem0te/packets.py @@ -5,17 +5,18 @@ def uint32toint(v): return (ord(v[3])<< 24) + (ord(v[2])<<16) + (ord(v[1])<<8) + (ord(v[0])) class Packet: - def __init__(self, command, id=None, ctr=None): - if id == None and ctr == None: + def __init__(self, command, id=None): + self.ctr = 0 + if id == None: message = command command = message[2] id = uint32toint(message[3:7]) - ctr = uint32toint(message[7:11]) + self.ctr = uint32toint(message[7:11]) self.length = 32 self.protocol = 'G' self.command = command self.id = id - self.ctr = ctr + self.priority = 5 def toMessage(self): message = chr(self.length) @@ -35,9 +36,9 @@ class Packet: return s class Button(Packet): - def __init__(self, id, ctr=None, button=None): - if ctr != None and button!= None: - Packet.__init__(self, 'B', id, ctr) + def __init__(self, id, button=None): + if button!= None: + Packet.__init__(self, 'B', id) else: message = id Packet.__init__(self, message) @@ -54,13 +55,15 @@ class Button(Packet): return s class Announce(Packet): - def __init__(self, id, ctr, gameMac, gameChannel, gameId, gameFlags, gameTitle): - Packet.__init__(self, 'A', id, ctr) + def __init__(self, gameMac, gameChannel, gameId, gameFlags, gameTitle): + #always a broadcast + Packet.__init__(self, 'A', 0) self.gameMac = gameMac self.gameChannel = gameChannel self.gameId = gameId self.gameFlags = gameFlags self.gameTitle = gameTitle[0:8] + self.priority = 3 def toMessage(self): message = Packet.toMessage(self) @@ -83,9 +86,9 @@ class Announce(Packet): return s class Join(Packet): - def __init__(self, id, ctr=None, gameId=None): - if ctr != None and gameId != None: - Packet.__init__(self, 'J', id, ctr) + def __init__(self, id, gameId=None): + if gameId != None: + Packet.__init__(self, 'J', id) else: message = id Packet.__init__(self, message) @@ -104,14 +107,16 @@ class Join(Packet): return s class Ack(Packet): - def __init__(self, id, ctr=None, flags=None): + def __init__(self, id, ctr, flags=None): if ctr != None and flags != None: - Packet.__init__(self, 'a', id, ctr) + Packet.__init__(self, 'a', id) + self.ctr = ctr else: message = id Packet.__init__(self, message) flags = ord(message[11]) self.flags = flags + self.priority = 3 def toMessage(self): message = Packet.toMessage(self) @@ -125,17 +130,17 @@ class Ack(Packet): return s class Nickrequest(Packet): - def __init__(self, id, ctr): - Packet.__init__(self, 'N', id, ctr) + def __init__(self, id): + Packet.__init__(self, 'N', id) def __str__(self): s = "Nickrequest packet with " + self.headerString() return s class Nick(Packet): - def __init__(self, id, ctr=None, flags=None, nick=None): - if ctr != None and flags != None and nick != None: - Packet.__init__(self, 'n', id, ctr) + def __init__(self, id, flags=None, nick=None): + if flags != None and nick != None: + Packet.__init__(self, 'n', id) else: message = id Packet.__init__(self, message) @@ -159,8 +164,8 @@ class Nick(Packet): return s class Text(Packet): - def __init__(self, id, ctr, x, y, flags, text): - Packet.__init__(self, 'T', id, ctr) + def __init__(self, id, x, y, flags, text): + Packet.__init__(self, 'T', id) self.x = x self.y = y self. flags = flags diff --git a/tools/game/simpletest.py b/tools/game/simpletest.py index cff1019..c0b1c80 100644 --- a/tools/game/simpletest.py +++ b/tools/game/simpletest.py @@ -1,27 +1,23 @@ -import r0ketrem0te.rem0te +import r0ketrem0te.game +import r0ketrem0te.bridge import r0ketrem0te.packets import time import Queue -announcequeue = Queue.Queue() -r = r0ketrem0te.rem0te.Bridge('/dev/ttyACM0') +def receivedPacket(packet): + if isinstance(packet, r0ketrem0te.packets.Join): + # flags = 1: join ok + # flags = 0: join not ok + ack = r0ketrem0te.packets.Ack(packet.id, packet.ctr, 1) + qp = r0ketrem0te.bridge.QueuePacket(game.channel, game.playermac, False, ack) + game.bridge.putInQueue(queue, qp) -r.registerQueue(announcequeue) +game = r0ketrem0te.game.Game('/dev/ttyACM0', "testgame", 83, 81, (1,2,3,2,1)) -a = r0ketrem0te.packets.Announce(0,2,(1,2,3,2,1), 81, 1, 0, "testgame") -aq = r0ketrem0te.rem0te.QueuePacket(81, (1,2,3,2,1), False, a) -aq.priority = 4 +queue = Queue.Queue() +game.bridge.registerQueue(queue) +game.bridge.registerCallback(receivedPacket) while True: - r.putInQueue(announcequeue, aq) - for i in range(1,1000): - if r.gotPacket(): - packet = r.getPacket() - if isinstance(packet, r0ketrem0te.packets.Join): - r.sendPacket(r0ketrem0te.packets.Ack(packet.id, packet.ctr, 1)) - time.sleep(.001) - - - - + time.sleep(1) From 0a1c959bf9e2fa93ccb5954532d87fca73bab4ce Mon Sep 17 00:00:00 2001 From: schneider Date: Thu, 15 Dec 2011 19:54:56 +0100 Subject: [PATCH 24/37] remote: bridge: reject duplicates --- tools/game/r0ketrem0te/bridge.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/game/r0ketrem0te/bridge.py b/tools/game/r0ketrem0te/bridge.py index 024edfe..8b8336e 100644 --- a/tools/game/r0ketrem0te/bridge.py +++ b/tools/game/r0ketrem0te/bridge.py @@ -70,7 +70,8 @@ class Bridge: self.crc = crcmod.predefined.mkCrcFun('crc-ccitt-false') self.queues = {} self.callbacks = [] - + self.ctrs = {} + self.reader = threading.Thread(target = self.readerThread) self.reader.daemon = True @@ -91,6 +92,7 @@ class Bridge: self.setChannel(channel) self.ctr = 0 + def registerCallback(self, callback): if callback not in self.callbacks: self.callbacks.append(callback) @@ -177,6 +179,8 @@ class Bridge: if data[-2:] == chr(crc>>8) + chr(crc&0xFF): packet = packets.fromMessage(data) print "received:", packet + if packet.id in self.ctrs and self.ctrs[packet.id] == packet.ctr: + return if isinstance(packet,packets.Ack): self.ProcessAck(packet) else: From 9784f9a5447bbdb9656c7542a857850179bbc81e Mon Sep 17 00:00:00 2001 From: schneider Date: Thu, 15 Dec 2011 19:55:20 +0100 Subject: [PATCH 25/37] remote: example: hold count of players --- tools/game/simpletest.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/tools/game/simpletest.py b/tools/game/simpletest.py index c0b1c80..54e608a 100644 --- a/tools/game/simpletest.py +++ b/tools/game/simpletest.py @@ -4,14 +4,23 @@ import r0ketrem0te.packets import time import Queue +maxplayer = 2 +players = {} + def receivedPacket(packet): if isinstance(packet, r0ketrem0te.packets.Join): # flags = 1: join ok # flags = 0: join not ok - ack = r0ketrem0te.packets.Ack(packet.id, packet.ctr, 1) + flags = 0 + if len(players) < maxplayer: + flags = 1 + players[packet.id] = 10 + ack = r0ketrem0te.packets.Ack(packet.id, packet.ctr, flags) qp = r0ketrem0te.bridge.QueuePacket(game.channel, game.playermac, False, ack) game.bridge.putInQueue(queue, qp) - + elif packet.id in players: + players[packet.id] = 10 + game = r0ketrem0te.game.Game('/dev/ttyACM0', "testgame", 83, 81, (1,2,3,2,1)) queue = Queue.Queue() @@ -20,4 +29,11 @@ game.bridge.registerCallback(receivedPacket) while True: time.sleep(1) - + toremove = [] + for player in players: + players[player]-=1 + if players[player] == 0: + toremove.append(player) + for player in toremove: + print "removing player", player + del players[player] From 919f5eb3f0b24e25f7de56958a43c16d0b70abd9 Mon Sep 17 00:00:00 2001 From: schneider Date: Thu, 15 Dec 2011 19:56:00 +0100 Subject: [PATCH 26/37] remote: renamed simpletest to testgame --- tools/game/testgame.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tools/game/testgame.py diff --git a/tools/game/testgame.py b/tools/game/testgame.py new file mode 100644 index 0000000..54e608a --- /dev/null +++ b/tools/game/testgame.py @@ -0,0 +1,39 @@ +import r0ketrem0te.game +import r0ketrem0te.bridge +import r0ketrem0te.packets +import time +import Queue + +maxplayer = 2 +players = {} + +def receivedPacket(packet): + if isinstance(packet, r0ketrem0te.packets.Join): + # flags = 1: join ok + # flags = 0: join not ok + flags = 0 + if len(players) < maxplayer: + flags = 1 + players[packet.id] = 10 + ack = r0ketrem0te.packets.Ack(packet.id, packet.ctr, flags) + qp = r0ketrem0te.bridge.QueuePacket(game.channel, game.playermac, False, ack) + game.bridge.putInQueue(queue, qp) + elif packet.id in players: + players[packet.id] = 10 + +game = r0ketrem0te.game.Game('/dev/ttyACM0', "testgame", 83, 81, (1,2,3,2,1)) + +queue = Queue.Queue() +game.bridge.registerQueue(queue) +game.bridge.registerCallback(receivedPacket) + +while True: + time.sleep(1) + toremove = [] + for player in players: + players[player]-=1 + if players[player] == 0: + toremove.append(player) + for player in toremove: + print "removing player", player + del players[player] From 6429ebde910dfcf1f9ad05e3b6eaa54a56ab8ca1 Mon Sep 17 00:00:00 2001 From: schneider Date: Thu, 15 Dec 2011 21:08:02 +0100 Subject: [PATCH 27/37] added py-pong --- tools/game/py-pong/assets/ball.png | Bin 0 -> 2801 bytes tools/game/py-pong/assets/bounce-paddle.wav | Bin 0 -> 5532 bytes tools/game/py-pong/assets/bounce-wall.wav | Bin 0 -> 10924 bytes tools/game/py-pong/assets/digit_0.png | Bin 0 -> 2851 bytes tools/game/py-pong/assets/digit_1.png | Bin 0 -> 2834 bytes tools/game/py-pong/assets/digit_2.png | Bin 0 -> 2855 bytes tools/game/py-pong/assets/digit_3.png | Bin 0 -> 2850 bytes tools/game/py-pong/assets/digit_4.png | Bin 0 -> 2855 bytes tools/game/py-pong/assets/digit_5.png | Bin 0 -> 2856 bytes tools/game/py-pong/assets/digit_6.png | Bin 0 -> 2860 bytes tools/game/py-pong/assets/digit_7.png | Bin 0 -> 2843 bytes tools/game/py-pong/assets/digit_8.png | Bin 0 -> 2854 bytes tools/game/py-pong/assets/digit_9.png | Bin 0 -> 2857 bytes tools/game/py-pong/assets/dividing-line.png | Bin 0 -> 2837 bytes tools/game/py-pong/assets/logo.png | Bin 0 -> 1083 bytes tools/game/py-pong/assets/missed-ball.wav | Bin 0 -> 50860 bytes tools/game/py-pong/assets/paddle.png | Bin 0 -> 2805 bytes tools/game/py-pong/main.py | 67 +++++++++ tools/game/py-pong/pypong/__init__.py | 150 ++++++++++++++++++++ tools/game/py-pong/pypong/entity.py | 87 ++++++++++++ tools/game/py-pong/pypong/player.py | 81 +++++++++++ 21 files changed, 385 insertions(+) create mode 100644 tools/game/py-pong/assets/ball.png create mode 100644 tools/game/py-pong/assets/bounce-paddle.wav create mode 100644 tools/game/py-pong/assets/bounce-wall.wav create mode 100644 tools/game/py-pong/assets/digit_0.png create mode 100644 tools/game/py-pong/assets/digit_1.png create mode 100644 tools/game/py-pong/assets/digit_2.png create mode 100644 tools/game/py-pong/assets/digit_3.png create mode 100644 tools/game/py-pong/assets/digit_4.png create mode 100644 tools/game/py-pong/assets/digit_5.png create mode 100644 tools/game/py-pong/assets/digit_6.png create mode 100644 tools/game/py-pong/assets/digit_7.png create mode 100644 tools/game/py-pong/assets/digit_8.png create mode 100644 tools/game/py-pong/assets/digit_9.png create mode 100644 tools/game/py-pong/assets/dividing-line.png create mode 100644 tools/game/py-pong/assets/logo.png create mode 100644 tools/game/py-pong/assets/missed-ball.wav create mode 100644 tools/game/py-pong/assets/paddle.png create mode 100644 tools/game/py-pong/main.py create mode 100644 tools/game/py-pong/pypong/__init__.py create mode 100644 tools/game/py-pong/pypong/entity.py create mode 100644 tools/game/py-pong/pypong/player.py diff --git a/tools/game/py-pong/assets/ball.png b/tools/game/py-pong/assets/ball.png new file mode 100644 index 0000000000000000000000000000000000000000..f80e5218673ef787e1ddaa0c6adfdcc8a97a01a1 GIT binary patch literal 2801 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 E0Bp?U=l}o! literal 0 HcmV?d00001 diff --git a/tools/game/py-pong/assets/bounce-wall.wav b/tools/game/py-pong/assets/bounce-wall.wav new file mode 100644 index 0000000000000000000000000000000000000000..bd9f843c281971b7890cfa1831bba812661ed2dc GIT binary patch literal 10924 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 BLk<7{ literal 0 HcmV?d00001 diff --git a/tools/game/py-pong/assets/digit_1.png b/tools/game/py-pong/assets/digit_1.png new file mode 100644 index 0000000000000000000000000000000000000000..69ff19572c8c3f463e839e76f87778c70f4beb51 GIT binary patch literal 2834 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 FV1kFZNEZMA literal 0 HcmV?d00001 diff --git a/tools/game/py-pong/assets/digit_3.png b/tools/game/py-pong/assets/digit_3.png new file mode 100644 index 0000000000000000000000000000000000000000..74df4fe8df3f669be78c0758c1eb0d02486e55b4 GIT binary patch literal 2850 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 ArvLx| literal 0 HcmV?d00001 diff --git a/tools/game/py-pong/assets/digit_4.png b/tools/game/py-pong/assets/digit_4.png new file mode 100644 index 0000000000000000000000000000000000000000..5f5b150f0db5ca49d034bbdddc6b1141daba9e29 GIT binary patch literal 2855 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 FV1nKEL;(N* literal 0 HcmV?d00001 diff --git a/tools/game/py-pong/assets/digit_5.png b/tools/game/py-pong/assets/digit_5.png new file mode 100644 index 0000000000000000000000000000000000000000..24eaa605e863ca6721d8988ee8fd0228a02a8f53 GIT binary patch literal 2856 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 Eg1^;7O8@`> literal 0 HcmV?d00001 diff --git a/tools/game/py-pong/assets/digit_9.png b/tools/game/py-pong/assets/digit_9.png new file mode 100644 index 0000000000000000000000000000000000000000..c80204b3281297b450120222ae0a85d17bee0e9f GIT binary patch literal 2857 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 Hu0mjfF@!`s literal 0 HcmV?d00001 diff --git a/tools/game/py-pong/assets/dividing-line.png b/tools/game/py-pong/assets/dividing-line.png new file mode 100644 index 0000000000000000000000000000000000000000..105682b4df770adb862c2dc42c57781e8a5280b6 GIT binary patch literal 2837 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⁣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 literal 0 HcmV?d00001 diff --git a/tools/game/py-pong/assets/paddle.png b/tools/game/py-pong/assets/paddle.png new file mode 100644 index 0000000000000000000000000000000000000000..7204eec9e0c10f35a648193cf5348673838e4774 GIT binary patch literal 2805 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 0 and timestamp < now: + timestamp = now + 5000 + print clock.get_fps() + input_state['key'] = pygame.key.get_pressed() + input_state['mouse'] = pygame.mouse.get_pos() + game.update() + game.draw(output_surface) + #~ pygame.surfarray.pixels_alpha(output_surface)[:,::2] = 12 + display_surface.blit(output_surface, (0,0)) + if debug_surface: + display_surface.blit(debug_surface, (0,0)) + pygame.display.flip() + for event in pygame.event.get(): + if event.type == pygame.QUIT: + game.running = False + elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: + game.running = False + +if __name__ == '__main__': run() diff --git a/tools/game/py-pong/pypong/__init__.py b/tools/game/py-pong/pypong/__init__.py new file mode 100644 index 0000000..856f0e5 --- /dev/null +++ b/tools/game/py-pong/pypong/__init__.py @@ -0,0 +1,150 @@ +import pygame, math, random, entity + +def load_image(path): + surface = pygame.image.load(path) + surface.convert() + pygame.surfarray.pixels3d(surface)[:,:,0:1:] = 0 + return surface + +def line_line_intersect(x1, y1, x2, y2, x3, y3, x4, y4): + # Taken from http://paulbourke.net/geometry/lineline2d/ + # Denominator for ua and ub are the same, so store this calculation + d = float((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)) + # n_a and n_b are calculated as seperate values for readability + n_a = float((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) + n_b = float((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) + # Make sure there is not a division by zero - this also indicates that + # the lines are parallel. + # If n_a and n_b were both equal to zero the lines would be on top of each + # other (coincidental). This check is not done because it is not + # necessary for this implementation (the parallel check accounts for this). + if d == 0: + return False + # Calculate the intermediate fractional point that the lines potentially intersect. + ua = n_a / d + ub = n_b / d + # The fractional point will be between 0 and 1 inclusive if the lines + # intersect. If the fractional calculation is larger than 1 or smaller + # than 0 the lines would need to be longer to intersect. + if ua >= 0. and ua <= 1. and ub >= 0. and ub <= 1.: + return [x1 + (ua * (x2 - x1)), y1 + (ua * (y2 - y1))] + return False + +class Game(object): + def __init__(self, player_left, player_right, configuration): + self.player_left = player_left + self.player_right = player_right + self.configuration = configuration + self.background = pygame.Surface(configuration['screen_size']) + self.sprites = pygame.sprite.OrderedUpdates() + line = entity.Line(load_image(configuration['line_image']), self.sprites) + line.rect.topleft = ((configuration['screen_size'][0]-line.rect.width)/2, 0) + paddle_image = load_image(configuration['paddle_image']) + self.paddle_left = entity.Paddle(configuration['paddle_velocity'], paddle_image, configuration['paddle_bounds'], self.sprites) + self.paddle_right = entity.Paddle(configuration['paddle_velocity'], paddle_image, configuration['paddle_bounds'], self.sprites) + self.paddle_left.rect.topleft = (self.configuration['paddle_left_position'], (self.configuration['screen_size'][1]-self.paddle_left.rect.height)/2) + self.paddle_right.rect.topleft = (self.configuration['paddle_right_position'], (self.configuration['screen_size'][1]-self.paddle_left.rect.height)/2) + digit_images = [load_image(configuration['digit_image'] % n) for n in xrange(10)] + self.score_left = entity.Score(digit_images, self.sprites) + self.score_left.rect.topleft = configuration['score_left_position'] + self.score_right = entity.Score(digit_images, self.sprites) + self.score_right.rect.topleft = configuration['score_right_position'] + ball_image = load_image(configuration['ball_image']) + self.ball = entity.Ball(self.configuration['ball_velocity'], ball_image, self.sprites) + self.bounds = pygame.Rect(20, 0, configuration['screen_size'][0]-ball_image.get_width()-20, configuration['screen_size'][1]-ball_image.get_height()) + self.sound_missed = pygame.mixer.Sound(configuration['sound_missed']) + self.sound_paddle = pygame.mixer.Sound(configuration['sound_paddle']) + self.sound_wall = pygame.mixer.Sound(configuration['sound_wall']) + self.reset_game(random.random()<0.5) + self.running = True + + def play_sound(self, sound): + if self.configuration['sound']: + sound.play() + + def reset_game(self, serveLeft=True): + y = self.configuration['screen_size'][1] - self.ball.rect.height + self.ball.position_x = (self.configuration['screen_size'][0]-self.ball.rect.width)/2.0 + self.ball.position_y = y * random.random() + self.ball.velocity = self.configuration['ball_velocity'] + a = random.random() * math.pi / 2. - math.pi / 4. + self.ball.velocity_vec[0] = self.ball.velocity * math.cos(a) + self.ball.velocity_vec[1] = self.ball.velocity * math.sin(a) + if random.random() < 0.5: + self.ball.velocity_vec[1] = -self.ball.velocity_vec[1] + if serveLeft: + self.ball.velocity_vec[0] *= -1 + + def update(self): + # Store previous ball position for line-line intersect test later + ball_position_x = self.ball.position_x + ball_position_y = self.ball.position_y + # Update sprites and players + self.sprites.update() + self.player_left.update(self.paddle_left, self) + self.player_right.update(self.paddle_right, self) + # Paddle collision check. Could probably just do a line-line intersect but I think I prefer having the pixel-pefect result of a rect-rect intersect test as well. + if self.ball.rect.x < self.bounds.centerx: + # Left side bullet-through-paper check on ball and paddle + if self.ball.velocity_vec[0] < 0: + intersect_point = line_line_intersect( + self.paddle_left.rect.right, self.paddle_left.rect.top, + self.paddle_left.rect.right, self.paddle_left.rect.bottom, + ball_position_x-self.ball.rect.width/2, ball_position_y+self.ball.rect.height/2, + self.ball.position_x-self.ball.rect.width/2, self.ball.position_y+self.ball.rect.height/2 + ) + if intersect_point: + self.ball.position_y = intersect_point[1]-self.ball.rect.height/2 + if intersect_point or (self.paddle_left.rect.colliderect(self.ball.rect) and self.ball.rect.right > self.paddle_left.rect.right): + self.ball.position_x = self.paddle_left.rect.right + velocity = self.paddle_left.calculate_bounce(min(1,max(0,(self.ball.rect.centery - self.paddle_left.rect.y)/float(self.paddle_left.rect.height)))) + self.ball.velocity = min(self.configuration['ball_velocity_max'], self.ball.velocity * self.configuration['ball_velocity_bounce_multiplier']) + self.ball.velocity_vec[0] = velocity[0] * self.ball.velocity + self.ball.velocity_vec[1] = velocity[1] * self.ball.velocity + self.player_left.hit() + self.play_sound(self.sound_paddle) + else: + # Right side bullet-through-paper check on ball and paddle. + if self.ball.velocity_vec[0] > 0: + intersect_point = line_line_intersect( + self.paddle_right.rect.left, self.paddle_right.rect.top, + self.paddle_right.rect.left, self.paddle_right.rect.bottom, + ball_position_x-self.ball.rect.width/2, ball_position_y+self.ball.rect.height/2, + self.ball.position_x-self.ball.rect.width/2, self.ball.position_y+self.ball.rect.height/2 + ) + if intersect_point: + self.ball.position_y = intersect_point[1]-self.ball.rect.height/2 + if intersect_point or (self.paddle_right.rect.colliderect(self.ball.rect) and self.ball.rect.x < self.paddle_right.rect.x): + self.ball.position_x = self.paddle_right.rect.x - self.ball.rect.width + velocity = self.paddle_right.calculate_bounce(min(1,max(0,(self.ball.rect.centery - self.paddle_right.rect.y)/float(self.paddle_right.rect.height)))) + self.ball.velocity = min(self.configuration['ball_velocity_max'], self.ball.velocity * self.configuration['ball_velocity_bounce_multiplier']) + self.ball.velocity_vec[0] = -velocity[0] * self.ball.velocity + self.ball.velocity_vec[1] = velocity[1] * self.ball.velocity + self.player_right.hit() + self.play_sound(self.sound_paddle) + # Bounds collision check + if self.ball.rect.y < self.bounds.top: + self.ball.position_y = float(self.bounds.top) + self.ball.velocity_vec[1] = -self.ball.velocity_vec[1] + self.play_sound(self.sound_wall) + elif self.ball.rect.y > self.bounds.bottom: + self.ball.position_y = float(self.bounds.bottom) + self.ball.velocity_vec[1] = -self.ball.velocity_vec[1] + self.play_sound(self.sound_wall) + # Check the ball is still in play + if self.ball.rect.x < self.bounds.x: + self.player_left.lost() + self.player_right.won() + self.score_right.score += 1 + self.reset_game(False) + self.play_sound(self.sound_missed) + if self.ball.rect.x > self.bounds.right: + self.player_left.won() + self.player_right.lost() + self.score_left.score += 1 + self.reset_game(True) + self.play_sound(self.sound_missed) + + def draw(self, display_surface): + self.sprites.clear(display_surface, self.background) + return self.sprites.draw(display_surface) diff --git a/tools/game/py-pong/pypong/entity.py b/tools/game/py-pong/pypong/entity.py new file mode 100644 index 0000000..170be57 --- /dev/null +++ b/tools/game/py-pong/pypong/entity.py @@ -0,0 +1,87 @@ +import pygame, math +from pygame.sprite import Sprite + +class Paddle(Sprite): + def __init__(self, velocity, image, bounds_y, *groups): + Sprite.__init__(self, *groups) + self.image = image + self.rect = self.image.get_rect() + self.direction = 0 + self.velocity = velocity + self.bounds_y = bounds_y + # Like original pong, we break this up into 8 segments from the edge angle (acute_angle) to pi/2 at the center + # Changing acute_angle lets us change the extreme edge angle of the paddle. + acute_angle = .125 + # Build the angles from acute_angle to the first 0.5 center value then append the values going from the + # second center 0.5 value by using the values we just calculated reversed. + angles = [acute_angle + (0.5-acute_angle)/3.0 * n for n in xrange(4)] + angles += map(lambda x: 1 + x * -1, reversed(angles)) + # Final table is the output vector (x,y) of each angle + self.bounce_table = [(math.cos(n*math.pi-math.pi/2.0), math.sin(n*math.pi-math.pi/2.0)) for n in angles] + + def update(self): + self.rect.y = max(self.bounds_y[0], min(self.bounds_y[1]-self.rect.height, self.rect.y + self.direction * self.velocity)) + + def calculate_bounce(self, delta): + return self.bounce_table[int(round(delta * (len(self.bounce_table)-1)))] + +class Line(Sprite): + def __init__(self, image, *groups): + Sprite.__init__(self, *groups) + self.image = image + self.rect = self.image.get_rect() + +class Ball(Sprite): + def __init__(self, velocity, image, *groups): + Sprite.__init__(self, *groups) + self.velocity = velocity + self.image = image + self.rect = self.image.get_rect() + self.position_vec = [0., 0.] + self.velocity_vec = [0., 0.] + + def update(self): + self.position_vec[0] += self.velocity_vec[0] + self.position_vec[1] += self.velocity_vec[1] + self.rect.x = self.position_vec[0] + self.rect.y = self.position_vec[1] + + def set_position_x(self, value): + self.position_vec[0] = value + self.rect.left = value + position_x = property(lambda self: self.position_vec[0], set_position_x) + + def set_position_y(self, value): + self.position_vec[1] = value + self.rect.top = value + position_y = property(lambda self: self.position_vec[1], set_position_y) + +class Score(Sprite): + def __init__(self, image_list, *groups): + Sprite.__init__(self, *groups) + self.image_list = image_list + self.image = None + self.rect = pygame.Rect(0,0,0,0) + self.score = 0 + + def get_score(self): + return self.score_value + + def set_score(self, value): + self.score_value = value + digit_spacing = 8 + digit_width = self.image_list[0].get_width() + digit_height = self.image_list[0].get_height() + values = map(int, reversed(str(self.score_value))) + surface_width = len(values) * digit_width + (len(values)-1) * digit_spacing + if not self.image or self.image.get_width() < surface_width: + self.image = pygame.Surface((surface_width, digit_height)) + self.image.fill((0,0,0)) + self.rect.width = self.image.get_width() + self.rect.height = self.image.get_height() + offset = self.image.get_width()-digit_width + for i in values: + self.image.blit(self.image_list[i], (offset, 0)) + offset = offset - (digit_width + digit_spacing) + + score = property(get_score, set_score) diff --git a/tools/game/py-pong/pypong/player.py b/tools/game/py-pong/pypong/player.py new file mode 100644 index 0000000..076bd0d --- /dev/null +++ b/tools/game/py-pong/pypong/player.py @@ -0,0 +1,81 @@ +import pygame, random + +class BasicAIPlayer(object): + def __init__(self): + self.bias = random.random() - 0.5 + self.hit_count = 0 + + def update(self, paddle, game): + # Dead simple AI, waits until the ball is on its side of the screen then moves the paddle to intercept. + # A bias is used to decide which edge of the paddle is going to be favored. + if (paddle.rect.x < game.bounds.centerx and game.ball.rect.x < game.bounds.centerx) or (paddle.rect.x > game.bounds.centerx and game.ball.rect.x > game.bounds.centerx): + delta = (paddle.rect.centery + self.bias * paddle.rect.height) - game.ball.rect.centery + if abs(delta) > paddle.velocity: + if delta > 0: + paddle.direction = -1 + else: + paddle.direction = 1 + else: + paddle.direction = 0 + else: + paddle.direction = 0 + + def hit(self): + self.hit_count += 1 + if self.hit_count > 6: + self.bias = random.random() - 0.5 # Recalculate our bias, this game is going on forever + self.hit_count = 0 + + def lost(self): + # If we lose, randomise the bias again + self.bias = random.random() - 0.5 + + def won(self): + pass + +class KeyboardPlayer(object): + def __init__(self, input_state, up_key=None, down_key=None): + self.input_state = input_state + self.up_key = up_key + self.down_key = down_key + + def update(self, paddle, game): + if self.input_state['key'][self.up_key]: + paddle.direction = -1 + elif self.input_state['key'][self.down_key]: + paddle.direction = 1 + else: + paddle.direction = 0 + + def hit(self): + pass + + def lost(self): + pass + + def won(self): + pass + +class MousePlayer(object): + def __init__(self, input_state): + self.input_state = input_state + pygame.mouse.set_visible(False) + + def update(self, paddle, game): + centery = paddle.rect.centery/int(paddle.velocity) + mousey = self.input_state['mouse'][1]/int(paddle.velocity) + if centery > mousey: + paddle.direction = -1 + elif centery < mousey: + paddle.direction = 1 + else: + paddle.direction = 0 + + def hit(self): + pass + + def lost(self): + pass + + def won(self): + pass From 0960c2e0200e522524364532c077765c0c6662da Mon Sep 17 00:00:00 2001 From: schneider Date: Thu, 15 Dec 2011 21:08:23 +0100 Subject: [PATCH 28/37] added basic r0ket support to py-pong --- tools/game/py-pong/main.py | 9 ++-- tools/game/py-pong/pypong/player.py | 64 +++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/tools/game/py-pong/main.py b/tools/game/py-pong/main.py index fc0d78d..12fbdab 100644 --- a/tools/game/py-pong/main.py +++ b/tools/game/py-pong/main.py @@ -1,5 +1,5 @@ import pygame, pypong -from pypong.player import BasicAIPlayer, KeyboardPlayer, MousePlayer +from pypong.player import BasicAIPlayer, KeyboardPlayer, MousePlayer, Rem0te def run(): configuration = { @@ -34,11 +34,12 @@ def run(): input_state = {'key': None, 'mouse': None} # Prepare game - player_left = KeyboardPlayer(input_state, pygame.K_w, pygame.K_s) + #player_left = KeyboardPlayer(input_state, pygame.K_w, pygame.K_s) #~ player_right = MousePlayer(input_state) - #player_left = BasicAIPlayer() - player_right = BasicAIPlayer() + player_left = BasicAIPlayer() + #player_right = BasicAIPlayer() + player_right = Rem0te() game = pypong.Game(player_left, player_right, configuration) # Main game loop diff --git a/tools/game/py-pong/pypong/player.py b/tools/game/py-pong/pypong/player.py index 076bd0d..1835c4a 100644 --- a/tools/game/py-pong/pypong/player.py +++ b/tools/game/py-pong/pypong/player.py @@ -1,5 +1,69 @@ import pygame, random +import r0ketrem0te.game +import r0ketrem0te.bridge +import r0ketrem0te.packets +import time +import Queue +import threading +class Rem0te(object): + def __init__(self): + self.maxplayer = 1 + self.players = {} + self.game = r0ketrem0te.game.Game('/dev/ttyACM0', "pong", 83, 81, (1,2,3,2,1)) + + self.queue = Queue.Queue() + self.game.bridge.registerQueue(self.queue) + self.game.bridge.registerCallback(self.receivedPacket) + self.state = 0 + self.checkPlayers() + + def checkPlayers(self): + toremove = [] + for player in self.players: + self.players[player]-=1 + if self.players[player] == 0: + toremove.append(player) + for player in toremove: + print "removing player", player + del self.players[player] + self.timer = threading.Timer(1, self.checkPlayers) + self.timer.start() + + def receivedPacket(self, packet): + if isinstance(packet, r0ketrem0te.packets.Join): + # flags = 1: join ok + # flags = 0: join not ok + flags = 0 + if len(self.players) < self.maxplayer: + flags = 1 + self.players[packet.id] = 10 + ack = r0ketrem0te.packets.Ack(packet.id, packet.ctr, flags) + qp = r0ketrem0te.bridge.QueuePacket( + self.game.channel, self.game.playermac, False, ack) + self.game.bridge.putInQueue(self.queue, qp) + elif packet.id in self.players: + self.players[packet.id] = 10 + if isinstance(packet, r0ketrem0te.packets.Button): + self.state = packet.button + + def update(self, paddle, game): + if self.state == 1: + paddle.direction = -1 + elif self.state == 2: + paddle.direction = 1 + else: + paddle.direction = 0 + + def hit(self): + pass + + def lost(self): + pass + + def won(self): + pass + class BasicAIPlayer(object): def __init__(self): self.bias = random.random() - 0.5 From cdb96bab0156b5b3395da0d873693e8a05886809 Mon Sep 17 00:00:00 2001 From: schneider Date: Fri, 16 Dec 2011 00:33:26 +0100 Subject: [PATCH 29/37] remote: added player management to game class --- tools/game/r0ketrem0te/game.py | 48 +++++++++++++++++++++++++++++++++- tools/game/testgame.py | 27 ++----------------- 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/tools/game/r0ketrem0te/game.py b/tools/game/r0ketrem0te/game.py index eb54596..1be8823 100644 --- a/tools/game/r0ketrem0te/game.py +++ b/tools/game/r0ketrem0te/game.py @@ -6,7 +6,7 @@ import random import threading class Game: - def __init__(self, device, gameName, gameChannel, announcechannel, announcemac): + def __init__(self, device, gameName, gameChannel, announcechannel, announcemac, maxplayer=0): self.gameName = gameName self.channel = gameChannel self.gamemac = [int(random.random()*254) for x in range(1,6)] @@ -25,6 +25,49 @@ class Game: self.sendAnnounce() + self.maxplayer = maxplayer + self.players = {} + self.callbacks = [] + self.queue = Queue.Queue() + self.bridge.registerQueue(self.queue) + self.bridge.registerCallback(self.receivedPacket) + self.checkPlayers() + + def checkPlayers(self): + if self.maxplayer > 0: + toremove = [] + for player in self.players: + self.players[player]-=1 + if self.players[player] == 0: + toremove.append(player) + for player in toremove: + print "removing player", player + del self.players[player] + for callback in self.callbacks: + callback("removed", player) + self.timer = threading.Timer(1, self.checkPlayers) + self.timer.start() + + def receivedPacket(self, packet): + if self.maxplayer == 0: + return + if isinstance(packet, packets.Join): + # flags = 1: join ok + # flags = 0: join not ok + flags = 0 + if len(self.players) < self.maxplayer: + flags = 1 + self.players[packet.id] = 10 + for callback in self.callbacks: + callback("added", packet.id) + + ack = packets.Ack(packet.id, packet.ctr, flags) + qp = bridge.QueuePacket( + self.channel, self.playermac, False, ack) + self.bridge.putInQueue(self.queue, qp) + elif packet.id in self.players: + self.players[packet.id] = 10 + def sendAnnounce(self): aq = bridge.QueuePacket(self.announcechannel, self.announcemac, False, self.announce) @@ -32,3 +75,6 @@ class Game: self.announcetimer = threading.Timer(1, self.sendAnnounce) self.announcetimer.start() + def registerPlayerCallback(self, callback): + if not callback in self.callbacks: + self.callbacks.append(callback) diff --git a/tools/game/testgame.py b/tools/game/testgame.py index 54e608a..33a725d 100644 --- a/tools/game/testgame.py +++ b/tools/game/testgame.py @@ -4,24 +4,9 @@ import r0ketrem0te.packets import time import Queue -maxplayer = 2 -players = {} - def receivedPacket(packet): - if isinstance(packet, r0ketrem0te.packets.Join): - # flags = 1: join ok - # flags = 0: join not ok - flags = 0 - if len(players) < maxplayer: - flags = 1 - players[packet.id] = 10 - ack = r0ketrem0te.packets.Ack(packet.id, packet.ctr, flags) - qp = r0ketrem0te.bridge.QueuePacket(game.channel, game.playermac, False, ack) - game.bridge.putInQueue(queue, qp) - elif packet.id in players: - players[packet.id] = 10 - -game = r0ketrem0te.game.Game('/dev/ttyACM0', "testgame", 83, 81, (1,2,3,2,1)) + pass +game = r0ketrem0te.game.Game('/dev/ttyACM0', "testgame", 83, 81, (1,2,3,2,1), 2) queue = Queue.Queue() game.bridge.registerQueue(queue) @@ -29,11 +14,3 @@ game.bridge.registerCallback(receivedPacket) while True: time.sleep(1) - toremove = [] - for player in players: - players[player]-=1 - if players[player] == 0: - toremove.append(player) - for player in toremove: - print "removing player", player - del players[player] From 47f7b3ac38084f76be1d755da20e1b27b3f40e82 Mon Sep 17 00:00:00 2001 From: schneider Date: Fri, 16 Dec 2011 00:34:40 +0100 Subject: [PATCH 30/37] remote: removed simpletest --- tools/game/simpletest.py | 39 --------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 tools/game/simpletest.py diff --git a/tools/game/simpletest.py b/tools/game/simpletest.py deleted file mode 100644 index 54e608a..0000000 --- a/tools/game/simpletest.py +++ /dev/null @@ -1,39 +0,0 @@ -import r0ketrem0te.game -import r0ketrem0te.bridge -import r0ketrem0te.packets -import time -import Queue - -maxplayer = 2 -players = {} - -def receivedPacket(packet): - if isinstance(packet, r0ketrem0te.packets.Join): - # flags = 1: join ok - # flags = 0: join not ok - flags = 0 - if len(players) < maxplayer: - flags = 1 - players[packet.id] = 10 - ack = r0ketrem0te.packets.Ack(packet.id, packet.ctr, flags) - qp = r0ketrem0te.bridge.QueuePacket(game.channel, game.playermac, False, ack) - game.bridge.putInQueue(queue, qp) - elif packet.id in players: - players[packet.id] = 10 - -game = r0ketrem0te.game.Game('/dev/ttyACM0', "testgame", 83, 81, (1,2,3,2,1)) - -queue = Queue.Queue() -game.bridge.registerQueue(queue) -game.bridge.registerCallback(receivedPacket) - -while True: - time.sleep(1) - toremove = [] - for player in players: - players[player]-=1 - if players[player] == 0: - toremove.append(player) - for player in toremove: - print "removing player", player - del players[player] From b5026b5b63f0b28589b2f491988482eb109b59e7 Mon Sep 17 00:00:00 2001 From: schneider Date: Fri, 16 Dec 2011 00:34:55 +0100 Subject: [PATCH 31/37] remote: pong: multiplayer --- tools/game/py-pong/main.py | 168 +++++++++++++++++----------- tools/game/py-pong/pypong/player.py | 41 +------ 2 files changed, 109 insertions(+), 100 deletions(-) diff --git a/tools/game/py-pong/main.py b/tools/game/py-pong/main.py index 12fbdab..8ef33a5 100644 --- a/tools/game/py-pong/main.py +++ b/tools/game/py-pong/main.py @@ -1,68 +1,106 @@ import pygame, pypong -from pypong.player import BasicAIPlayer, KeyboardPlayer, MousePlayer, Rem0te +from pypong.player import BasicAIPlayer, KeyboardPlayer, MousePlayer, Rem0tePlayer +import r0ketrem0te.game +import time + +class Pong: + def __init__(self): + self.configuration = { + 'screen_size': (686,488), + 'paddle_image': 'assets/paddle.png', + 'paddle_left_position': 84., + 'paddle_right_position': 594., + 'paddle_velocity': 6., + 'paddle_bounds': (0, 488), # This sets the upper and lower paddle boundary.The original game didn't allow the paddle to touch the edge, + 'line_image': 'assets/dividing-line.png', + 'ball_image': 'assets/ball.png', + 'ball_velocity': 4., + 'ball_velocity_bounce_multiplier': 1.105, + 'ball_velocity_max': 32., + 'score_left_position': (141, 30), + 'score_right_position': (473, 30), + 'digit_image': 'assets/digit_%i.png', + 'sound_missed': 'assets/missed-ball.wav', + 'sound_paddle': 'assets/bounce-paddle.wav', + 'sound_wall': 'assets/bounce-wall.wav', + 'sound': True, + } + pygame.mixer.pre_init(22050, -16, 2, 1024) + pygame.init() + + self.rem0te = r0ketrem0te.game.Game('/dev/ttyACM0', "pong", 83, 81, (1,2,3,2,1), 2) + self.rem0te.registerPlayerCallback(self.playercallback) + + self.player_right = Rem0tePlayer(self.rem0te) + self.player_left = Rem0tePlayer(self.rem0te) + + self.stop = True + self.start = False + self.restart() + + + def playercallback(self, action, player): + if action == 'added': + if self.player_left.player == 0: + self.player_left.player = player + elif self.player_right.player == 0: + self.player_right.player = player + if self.player_left.player and self.player_right.player: + self.start = True + elif action == 'removed': + if self.player_left.player == player: + self.player_left.player = 0 + elif self.player_right.player == player: + self.player_right.player = 0 + if self.player_left.player == 0 or self.player_right.player == 0: + self.stop = True -def run(): - configuration = { - 'screen_size': (686,488), - 'paddle_image': 'assets/paddle.png', - 'paddle_left_position': 84., - 'paddle_right_position': 594., - 'paddle_velocity': 6., - 'paddle_bounds': (0, 488), # This sets the upper and lower paddle boundary.The original game didn't allow the paddle to touch the edge, - 'line_image': 'assets/dividing-line.png', - 'ball_image': 'assets/ball.png', - 'ball_velocity': 4., - 'ball_velocity_bounce_multiplier': 1.105, - 'ball_velocity_max': 32., - 'score_left_position': (141, 30), - 'score_right_position': (473, 30), - 'digit_image': 'assets/digit_%i.png', - 'sound_missed': 'assets/missed-ball.wav', - 'sound_paddle': 'assets/bounce-paddle.wav', - 'sound_wall': 'assets/bounce-wall.wav', - 'sound': True, - } - pygame.mixer.pre_init(22050, -16, 2, 1024) - pygame.init() - display_surface = pygame.display.set_mode(configuration['screen_size']) - output_surface = display_surface.copy().convert_alpha() - output_surface.fill((0,0,0)) - #~ debug_surface = output_surface.copy() - #~ debug_surface.fill((0,0,0,0)) - debug_surface = None - clock = pygame.time.Clock() - input_state = {'key': None, 'mouse': None} - - # Prepare game - #player_left = KeyboardPlayer(input_state, pygame.K_w, pygame.K_s) - #~ player_right = MousePlayer(input_state) - - player_left = BasicAIPlayer() - #player_right = BasicAIPlayer() - player_right = Rem0te() - game = pypong.Game(player_left, player_right, configuration) - - # Main game loop - timestamp = 1 - while game.running: - clock.tick(60) - now = pygame.time.get_ticks() - if timestamp > 0 and timestamp < now: - timestamp = now + 5000 - print clock.get_fps() - input_state['key'] = pygame.key.get_pressed() - input_state['mouse'] = pygame.mouse.get_pos() - game.update() - game.draw(output_surface) - #~ pygame.surfarray.pixels_alpha(output_surface)[:,::2] = 12 - display_surface.blit(output_surface, (0,0)) - if debug_surface: - display_surface.blit(debug_surface, (0,0)) - pygame.display.flip() - for event in pygame.event.get(): - if event.type == pygame.QUIT: - game.running = False - elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: - game.running = False + def restart(self): + self.display_surface = pygame.display.set_mode(self.configuration['screen_size']) + self.output_surface = self.display_surface.copy().convert_alpha() + self.output_surface.fill((0,0,0)) + #~ debug_surface = output_surface.copy() + #~ debug_surface.fill((0,0,0,0)) + self.debug_surface = None + self.clock = pygame.time.Clock() + self.input_state = {'key': None, 'mouse': None} -if __name__ == '__main__': run() + # Prepare game + + self.game = pypong.Game(self.player_left, self.player_right, self.configuration) + + def run(self): + # Main game loop + timestamp = 1 + while self.game.running: + if self.start: + self.restart() + self.start = False + self.stop = False + if self.stop: + time.sleep(0.1) + continue + + self.clock.tick(60) + now = pygame.time.get_ticks() + if timestamp > 0 and timestamp < now: + timestamp = now + 5000 + print self.clock.get_fps() + self.input_state['key'] = pygame.key.get_pressed() + self.input_state['mouse'] = pygame.mouse.get_pos() + self.game.update() + self.game.draw(self.output_surface) + #~ pygame.surfarray.pixels_alpha(output_surface)[:,::2] = 12 + self.display_surface.blit(self.output_surface, (0,0)) + if self.debug_surface: + self.display_surface.blit(self.debug_surface, (0,0)) + pygame.display.flip() + for event in pygame.event.get(): + if event.type == pygame.QUIT: + self.game.running = False + elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: + self.game.running = False + +if __name__ == '__main__': + pong = Pong() + pong.run() diff --git a/tools/game/py-pong/pypong/player.py b/tools/game/py-pong/pypong/player.py index 1835c4a..7c227fc 100644 --- a/tools/game/py-pong/pypong/player.py +++ b/tools/game/py-pong/pypong/player.py @@ -6,44 +6,15 @@ import time import Queue import threading -class Rem0te(object): - def __init__(self): - self.maxplayer = 1 - self.players = {} - self.game = r0ketrem0te.game.Game('/dev/ttyACM0', "pong", 83, 81, (1,2,3,2,1)) - - self.queue = Queue.Queue() - self.game.bridge.registerQueue(self.queue) - self.game.bridge.registerCallback(self.receivedPacket) +class Rem0tePlayer(object): + def __init__(self, rem0te): + self.rem0te = rem0te + self.rem0te.bridge.registerCallback(self.receivedPacket) self.state = 0 - self.checkPlayers() - - def checkPlayers(self): - toremove = [] - for player in self.players: - self.players[player]-=1 - if self.players[player] == 0: - toremove.append(player) - for player in toremove: - print "removing player", player - del self.players[player] - self.timer = threading.Timer(1, self.checkPlayers) - self.timer.start() + self.player = 0 def receivedPacket(self, packet): - if isinstance(packet, r0ketrem0te.packets.Join): - # flags = 1: join ok - # flags = 0: join not ok - flags = 0 - if len(self.players) < self.maxplayer: - flags = 1 - self.players[packet.id] = 10 - ack = r0ketrem0te.packets.Ack(packet.id, packet.ctr, flags) - qp = r0ketrem0te.bridge.QueuePacket( - self.game.channel, self.game.playermac, False, ack) - self.game.bridge.putInQueue(self.queue, qp) - elif packet.id in self.players: - self.players[packet.id] = 10 + if packet.id == self.player: if isinstance(packet, r0ketrem0te.packets.Button): self.state = packet.button From b7610d29e7f46cadb61f9397351fbadba343e263 Mon Sep 17 00:00:00 2001 From: schneider Date: Fri, 16 Dec 2011 00:52:26 +0100 Subject: [PATCH 32/37] r_player: removed old code, fixed up rf parameter handling --- firmware/l0dable/r_player.c | 168 ++++++------------------------------ 1 file changed, 24 insertions(+), 144 deletions(-) diff --git a/firmware/l0dable/r_player.c b/firmware/l0dable/r_player.c index 6163461..a4c0a3f 100644 --- a/firmware/l0dable/r_player.c +++ b/firmware/l0dable/r_player.c @@ -2,23 +2,12 @@ #include "basic/basic.h" #include "basic/byteorder.h" - #include "lcd/lcd.h" #include "lcd/print.h" - #include "funk/nrf24l01p.h" - -//includes useful for r_game: -//#include "usbcdc/usb.h" -//#include "usbcdc/usbcore.h" -//#include "usbcdc/usbhw.h" -//#include "usbcdc/cdcuser.h" -//#include "usbcdc/cdc_buf.h" -//#include "usbcdc/util.h" - #include #include "basic/random.h" - +#include "basic/config.h" #include "usetable.h" #define REMOTE_CHANNEL 81 @@ -29,14 +18,6 @@ //mac that the game receives #define GAME_MAC "\x1\x2\x3\x2\x1" -//#if CFG_USBMSC -//#error "MSC is defined" -//#endif - -//#if !CFG_USBCDC -//#error "CDC is not defined" -//#endif - struct NRF_CFG config; struct packet{ @@ -123,22 +104,29 @@ uint8_t gamecount; void ram(void) { + int priv = GLOBAL(privacy); + GLOBAL(privacy) = 3; config.nrmacs=1; config.maclen[0] = 32; + config.channel = REMOTE_CHANNEL; + memcpy(config.txmac, GAME_MAC, 5); + memcpy(config.mac0, PLAYER_MAC, 5); + nrf_config_set(&config); id = getRandom(); ctr = 1; - + while( selectGame() ){ playGame(); } + GLOBAL(privacy) = priv; }; void playGame(void) { int len; struct packet p; - + while(1){ uint8_t button = getInputRaw(); sendButton(button); @@ -180,9 +168,17 @@ void showGames(uint8_t selected) uint8_t joinGame() { int i; + struct packet p; + + //config.nrmacs=1; + //config.maclen[0] = 32; + //config.channel = REMOTE_CHANNEL; + //memcpy(config.txmac, GAME_MAC, 5); + //memcpy(config.mac0, PLAYER_MAC, 5); + //nrf_config_set(&config); + lcdClear(); for(i=0; i<10; i++){ - struct packet p; p.len=sizeof(p); p.protocol='G'; p.command='J'; @@ -190,8 +186,10 @@ uint8_t joinGame() p.ctr= ++ctr; p.c.join.gameId=gameId; int r = nrf_snd_pkt_crc(sizeof(p),(uint8_t*)&p); - lcdPrint("send: "); lcdPrintInt(r);lcdPrintln(""); - lcdRefresh(); + //lcdPrint("send: "); lcdPrintInt(r);lcdPrintln(""); + //lcdRefresh(); + + int len; len = nrf_rcv_pkt_time(30,sizeof(p),(uint8_t*)&p); if( len==sizeof(p) ){ @@ -204,6 +202,7 @@ uint8_t joinGame() } } } + delayms(70); } return 0; @@ -320,122 +319,3 @@ void sendJoin(uint32_t game) nrf_snd_pkt_crc(sizeof(p),(uint8_t*)&p); } - -/* -void s_init(void){ - usbCDCInit(); - nrf_init(); - - struct NRF_CFG config = { - .channel= REMOTE_CHANNEL, - .txmac= REMOTE_MAC, - .nrmacs=1, - .mac0= REMOTE_MAC, - .maclen ="\x10", - }; - - nrf_config_set(&config); -}; -*/ - -/* void process(uint8_t * input){ - __attribute__ ((aligned (4))) uint8_t buf[32]; - puts("process: "); - puts(input); - puts("\r\n"); - if(input[0]=='M'){ - buf[0]=0x10; // Length: 16 bytes - buf[1]='M'; // Proto - buf[2]=0x01; - buf[3]=0x01; // Unused - - uint32touint8p(0,buf+4); - - uint32touint8p(0x41424344,buf+8); - - buf[12]=0xff; // salt (0xffff always?) - buf[13]=0xff; - nrf_snd_pkt_crc_encr(16,buf,remotekey); - nrf_rcv_pkt_start(); - }; - -}; -*/ - -/* -#define INPUTLEN 99 -void r_recv(void){ - __attribute__ ((aligned (4))) uint8_t buf[32]; - int len; - - uint8_t input[INPUTLEN+1]; - int inputptr=0; - - nrf_rcv_pkt_start(); - puts("D start"); - - getInputWaitRelease(); - - while(!getInputRaw()){ - delayms(100); - - // Input - int l=INPUTLEN-inputptr; - CDC_OutBufAvailChar (&l); - - if(l>0){ - CDC_RdOutBuf (input+inputptr, &l); - input[inputptr+l+1]=0; - for(int i=0;i Date: Fri, 16 Dec 2011 01:10:27 +0100 Subject: [PATCH 33/37] r_player: changed default mac, added more feedback --- firmware/l0dable/r_player.c | 53 ++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/firmware/l0dable/r_player.c b/firmware/l0dable/r_player.c index a4c0a3f..fb46308 100644 --- a/firmware/l0dable/r_player.c +++ b/firmware/l0dable/r_player.c @@ -10,13 +10,10 @@ #include "basic/config.h" #include "usetable.h" -#define REMOTE_CHANNEL 81 -//mac that the player receives -#define PLAYER_MAC "\x1\x2\x3\x2\x1" - -//mac that the game receives -#define GAME_MAC "\x1\x2\x3\x2\x1" +//channel and mac used to transmit game announcements +#define ANNOUNCE_CHANNEL 81 +#define ANNOUNCE_MAC "REM0T" struct NRF_CFG config; @@ -66,12 +63,8 @@ struct packet{ uint16_t crc; }__attribute__((packed)); -#define sizeof(p) (sizeof(struct packet)) - #define FLAGS_MASS_GAME 1 - #define FLAGS_ACK_JOINOK 1 - #define MASS_ID 1 /**************************************************************************/ @@ -108,9 +101,8 @@ void ram(void) GLOBAL(privacy) = 3; config.nrmacs=1; config.maclen[0] = 32; - config.channel = REMOTE_CHANNEL; - memcpy(config.txmac, GAME_MAC, 5); - memcpy(config.mac0, PLAYER_MAC, 5); + config.channel = ANNOUNCE_CHANNEL; + memcpy(config.mac0, ANNOUNCE_MAC, 5); nrf_config_set(&config); id = getRandom(); @@ -170,15 +162,11 @@ uint8_t joinGame() int i; struct packet p; - //config.nrmacs=1; - //config.maclen[0] = 32; - //config.channel = REMOTE_CHANNEL; - //memcpy(config.txmac, GAME_MAC, 5); - //memcpy(config.mac0, PLAYER_MAC, 5); - //nrf_config_set(&config); - - lcdClear(); for(i=0; i<10; i++){ + lcdClear(); + lcdPrintln("Joining game"); + lcdRefresh(); + p.len=sizeof(p); p.protocol='G'; p.command='J'; @@ -195,16 +183,28 @@ uint8_t joinGame() if( len==sizeof(p) ){ if( (p.len==32) && (p.protocol=='G') && p.command=='a' ){ //check sanity, protocol if( p.id == id && p.ctr == ctr ){ - if( p.c.ack.flags & FLAGS_ACK_JOINOK ) + if( p.c.ack.flags & FLAGS_ACK_JOINOK ){ + lcdPrintln("Join OK"); + lcdRefresh(); return 1; - else + }else{ + lcdPrintln("Join rejected"); + lcdRefresh(); + getInputWait(); + getInputWaitRelease(); return 0; + } } } } delayms(70); } + lcdPrintln("timeout :("); + lcdRefresh(); + getInputWait(); + getInputWaitRelease(); + return 0; } @@ -213,9 +213,8 @@ uint8_t selectGame() int len, i, selected; struct packet p; int a = 0; - config.channel = REMOTE_CHANNEL; - memcpy(config.txmac, GAME_MAC, 5); - memcpy(config.mac0, PLAYER_MAC, 5); + config.channel = ANNOUNCE_CHANNEL; + memcpy(config.mac0, ANNOUNCE_MAC, 5); nrf_config_set(&config); gamecount = 0; @@ -272,7 +271,7 @@ void processPacket(struct packet *p) //processText(&(p->c.text)); } else if (p->command=='N'){ - //processNick(&(p->c.nickrequest)); + //processNickRequest(&(p->c.nickrequest)); } else if (p->command=='A'){ processAnnounce(&(p->c.announce)); From 8978eca3fa297b706aaaafcf666963832848823f Mon Sep 17 00:00:00 2001 From: schneider Date: Fri, 16 Dec 2011 01:15:25 +0100 Subject: [PATCH 34/37] r_player: cleanup --- firmware/l0dable/r_player.c | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/firmware/l0dable/r_player.c b/firmware/l0dable/r_player.c index fb46308..5366215 100644 --- a/firmware/l0dable/r_player.c +++ b/firmware/l0dable/r_player.c @@ -161,25 +161,20 @@ uint8_t joinGame() { int i; struct packet p; + p.len=sizeof(p); + p.protocol='G'; + p.command='J'; + p.id= id; + p.ctr= ++ctr; + p.c.join.gameId=gameId; + lcdClear(); + lcdPrintln("Joining game"); + lcdRefresh(); for(i=0; i<10; i++){ - lcdClear(); - lcdPrintln("Joining game"); - lcdRefresh(); + nrf_snd_pkt_crc(sizeof(p),(uint8_t*)&p); - p.len=sizeof(p); - p.protocol='G'; - p.command='J'; - p.id= id; - p.ctr= ++ctr; - p.c.join.gameId=gameId; - int r = nrf_snd_pkt_crc(sizeof(p),(uint8_t*)&p); - //lcdPrint("send: "); lcdPrintInt(r);lcdPrintln(""); - //lcdRefresh(); - - - int len; - len = nrf_rcv_pkt_time(30,sizeof(p),(uint8_t*)&p); + int len = nrf_rcv_pkt_time(30,sizeof(p),(uint8_t*)&p); if( len==sizeof(p) ){ if( (p.len==32) && (p.protocol=='G') && p.command=='a' ){ //check sanity, protocol if( p.id == id && p.ctr == ctr ){ @@ -197,7 +192,6 @@ uint8_t joinGame() } } } - delayms(70); } lcdPrintln("timeout :("); From d51f5c9caf29c7b2045b837113525886aa8f1716 Mon Sep 17 00:00:00 2001 From: schneider Date: Fri, 16 Dec 2011 03:04:31 +0100 Subject: [PATCH 35/37] remote: added nick transport --- firmware/l0dable/r_player.c | 36 ++++++++--------- tools/game/py-pong/main.py | 40 +++++++++++++------ tools/game/py-pong/pypong/player.py | 6 ++- tools/game/r0ketrem0te/bridge.py | 13 +++--- tools/game/r0ketrem0te/game.py | 62 ++++++++++++++++++++++------- tools/game/r0ketrem0te/packets.py | 5 +++ tools/game/testgame.py | 4 +- 7 files changed, 113 insertions(+), 53 deletions(-) diff --git a/firmware/l0dable/r_player.c b/firmware/l0dable/r_player.c index 5366215..59513f4 100644 --- a/firmware/l0dable/r_player.c +++ b/firmware/l0dable/r_player.c @@ -38,7 +38,7 @@ struct packet{ }__attribute__((packed)) text; struct nick{ uint8_t flags; - uint8_t text[18]; + uint8_t nick[18]; }__attribute__((packed)) nick; struct nickrequest{ uint8_t reserved[19]; @@ -124,7 +124,7 @@ void playGame(void) sendButton(button); while(1){ - len = nrf_rcv_pkt_time(30,sizeof(p),(uint8_t*)&p); + len = nrf_rcv_pkt_time(32,sizeof(p),(uint8_t*)&p); if(len==sizeof(p)){ processPacket(&p); }else{ @@ -255,8 +255,22 @@ uint8_t selectGame() } } } - +void processNickRequest( struct nickrequest *nq) +{ + struct packet p; + p.len=sizeof(p); + p.protocol='G'; // Proto + p.command='n'; + p.id= id; + p.ctr= ++ctr; + p.c.nick.flags = 0; + uint8_t *nick = GLOBAL(nickname); + strcpy(p.c.nick.nick, nick); + //p.c.nick.nick[0] = 'S'; + //p.c.nick.nick[1] = 0; + nrf_snd_pkt_crc(sizeof(p),(uint8_t*)&p); +} void processPacket(struct packet *p) { @@ -265,7 +279,7 @@ void processPacket(struct packet *p) //processText(&(p->c.text)); } else if (p->command=='N'){ - //processNickRequest(&(p->c.nickrequest)); + processNickRequest(&(p->c.nickrequest)); } else if (p->command=='A'){ processAnnounce(&(p->c.announce)); @@ -298,17 +312,3 @@ void sendButton(uint8_t button) nrf_snd_pkt_crc(sizeof(p),(uint8_t*)&p); } -//send join request for game -void sendJoin(uint32_t game) -{ - struct packet p; - p.len=sizeof(p); - p.protocol='G'; - p.command='J'; - p.ctr= ++ctr; - p.id=id; - p.c.join.gameId=game; - - nrf_snd_pkt_crc(sizeof(p),(uint8_t*)&p); -} - diff --git a/tools/game/py-pong/main.py b/tools/game/py-pong/main.py index 8ef33a5..b4b0dc0 100644 --- a/tools/game/py-pong/main.py +++ b/tools/game/py-pong/main.py @@ -27,8 +27,9 @@ class Pong: } pygame.mixer.pre_init(22050, -16, 2, 1024) pygame.init() - - self.rem0te = r0ketrem0te.game.Game('/dev/ttyACM0', "pong", 83, 81, (1,2,3,2,1), 2) + + self.rem0te = r0ketrem0te.game.Game('/dev/ttyACM0', "pong", 83, + 81, [ord(x) for x in 'REM0T'], 2, True) self.rem0te.registerPlayerCallback(self.playercallback) self.player_right = Rem0tePlayer(self.rem0te) @@ -38,21 +39,24 @@ class Pong: self.start = False self.restart() - def playercallback(self, action, player): if action == 'added': - if self.player_left.player == 0: + if self.player_left.player == None: self.player_left.player = player - elif self.player_right.player == 0: + elif self.player_right.player == None: self.player_right.player = player if self.player_left.player and self.player_right.player: self.start = True elif action == 'removed': + print 'got remove for', player.nick if self.player_left.player == player: - self.player_left.player = 0 + print 'removing left player' + self.player_left.player = None elif self.player_right.player == player: - self.player_right.player = 0 - if self.player_left.player == 0 or self.player_right.player == 0: + print 'removing right player' + self.player_right.player = None + if self.player_left.player == None or self.player_right.player == None: + print 'halting game' self.stop = True def restart(self): @@ -77,9 +81,6 @@ class Pong: self.restart() self.start = False self.stop = False - if self.stop: - time.sleep(0.1) - continue self.clock.tick(60) now = pygame.time.get_ticks() @@ -88,10 +89,25 @@ class Pong: print self.clock.get_fps() self.input_state['key'] = pygame.key.get_pressed() self.input_state['mouse'] = pygame.mouse.get_pos() - self.game.update() + if not self.stop: + self.game.update() self.game.draw(self.output_surface) + + #~ pygame.surfarray.pixels_alpha(output_surface)[:,::2] = 12 self.display_surface.blit(self.output_surface, (0,0)) + + font = pygame.font.Font(None, 36) + if self.player_left.player: + text = font.render(self.player_left.player.nick, 1, (0, 255, 0)) + textpos = text.get_rect(centerx=self.output_surface.get_width()/4) + self.display_surface.blit(text, textpos) + + if self.player_right.player: + text = font.render(self.player_right.player.nick, 1, (0, 255, 0)) + textpos = text.get_rect(centerx=self.output_surface.get_width()/4*3) + self.display_surface.blit(text, textpos) + if self.debug_surface: self.display_surface.blit(self.debug_surface, (0,0)) pygame.display.flip() diff --git a/tools/game/py-pong/pypong/player.py b/tools/game/py-pong/pypong/player.py index 7c227fc..788b65d 100644 --- a/tools/game/py-pong/pypong/player.py +++ b/tools/game/py-pong/pypong/player.py @@ -11,10 +11,12 @@ class Rem0tePlayer(object): self.rem0te = rem0te self.rem0te.bridge.registerCallback(self.receivedPacket) self.state = 0 - self.player = 0 + self.player = None def receivedPacket(self, packet): - if packet.id == self.player: + if self.player == None: + return + if packet.id == self.player.id: if isinstance(packet, r0ketrem0te.packets.Button): self.state = packet.button diff --git a/tools/game/r0ketrem0te/bridge.py b/tools/game/r0ketrem0te/bridge.py index 8b8336e..b1d1bff 100644 --- a/tools/game/r0ketrem0te/bridge.py +++ b/tools/game/r0ketrem0te/bridge.py @@ -3,6 +3,7 @@ import threading import Queue import crcmod import packets +import traceback class QueuePacket: def __init__(self, channel, mac, acked, packet, callback=None): @@ -158,6 +159,7 @@ class Bridge: self.setChannel(self.gameChannel) except Exception as e: print e + traceback.print_stack() def readerThread(self): while True: @@ -167,20 +169,21 @@ class Bridge: self.newPacket(data) elif command == '2': self.free.release() - elif command: - while True: - pass except Exception as e: print e + traceback.print_stack() def newPacket(self, data): - #print "received:", list(data) + print "received:", list(data) crc = self.crc(data[:-2]) if data[-2:] == chr(crc>>8) + chr(crc&0xFF): packet = packets.fromMessage(data) print "received:", packet - if packet.id in self.ctrs and self.ctrs[packet.id] == packet.ctr: + if packet == None: return + #if packet.id in self.ctrs and self.ctrs[packet.id] == packet.ctr: + # print 'ignoring duplicate' + # return if isinstance(packet,packets.Ack): self.ProcessAck(packet) else: diff --git a/tools/game/r0ketrem0te/game.py b/tools/game/r0ketrem0te/game.py index 1be8823..3ee25c1 100644 --- a/tools/game/r0ketrem0te/game.py +++ b/tools/game/r0ketrem0te/game.py @@ -5,8 +5,15 @@ import Queue import random import threading +class Player(): + def __init__(self, id): + self.id = id + self.nick = 'anonymous' + self.timeout = 10 + self.active = False + class Game: - def __init__(self, device, gameName, gameChannel, announcechannel, announcemac, maxplayer=0): + def __init__(self, device, gameName, gameChannel, announcechannel, announcemac, maxplayer=0, askname=False): self.gameName = gameName self.channel = gameChannel self.gamemac = [int(random.random()*254) for x in range(1,6)] @@ -22,6 +29,7 @@ class Game: self.bridge.registerQueue(self.announcequeue) self.announcechannel = announcechannel self.announcemac = announcemac + self.askname = askname self.sendAnnounce() @@ -36,15 +44,23 @@ class Game: def checkPlayers(self): if self.maxplayer > 0: toremove = [] - for player in self.players: - self.players[player]-=1 - if self.players[player] == 0: - toremove.append(player) - for player in toremove: - print "removing player", player - del self.players[player] - for callback in self.callbacks: - callback("removed", player) + for id in self.players: + player = self.players[id] + player.timeout-=1 + if player.timeout == 0: + toremove.append(id) + for id in toremove: + player = self.players[id] + if self.askname: + print "removing player", player.nick + else: + print "removing player", id + + del self.players[id] + if player.active: + player.active = False + for callback in self.callbacks: + callback("removed", player) self.timer = threading.Timer(1, self.checkPlayers) self.timer.start() @@ -57,16 +73,34 @@ class Game: flags = 0 if len(self.players) < self.maxplayer: flags = 1 - self.players[packet.id] = 10 - for callback in self.callbacks: - callback("added", packet.id) + self.players[packet.id] = Player(packet.id) ack = packets.Ack(packet.id, packet.ctr, flags) qp = bridge.QueuePacket( self.channel, self.playermac, False, ack) self.bridge.putInQueue(self.queue, qp) elif packet.id in self.players: - self.players[packet.id] = 10 + print "player known:", packet.id + player = self.players[packet.id] + player.timeout = 10 + if not player.active and isinstance(packet, packets.Button): + if self.askname: + nickrequest = packets.Nickrequest(packet.id) + qp = bridge.QueuePacket(self.channel, + self.playermac, False, nickrequest) + self.bridge.putInQueue(self.queue, qp) + else: + player.active = True + for callback in self.callbacks: + callback("added", player) + elif not player.active and isinstance(packet, packets.Nick): + if self.askname: + player.nick = packet.nick + player.active = True + for callback in self.callbacks: + callback("added", player) + else: + print "player unknown" def sendAnnounce(self): aq = bridge.QueuePacket(self.announcechannel, diff --git a/tools/game/r0ketrem0te/packets.py b/tools/game/r0ketrem0te/packets.py index 9646603..571ba5f 100644 --- a/tools/game/r0ketrem0te/packets.py +++ b/tools/game/r0ketrem0te/packets.py @@ -137,6 +137,11 @@ class Nickrequest(Packet): s = "Nickrequest packet with " + self.headerString() return s + def toMessage(self): + message = Packet.toMessage(self) + message += '\x00'*19 + return message + class Nick(Packet): def __init__(self, id, flags=None, nick=None): if flags != None and nick != None: diff --git a/tools/game/testgame.py b/tools/game/testgame.py index 33a725d..dc3c037 100644 --- a/tools/game/testgame.py +++ b/tools/game/testgame.py @@ -6,8 +6,8 @@ import Queue def receivedPacket(packet): pass -game = r0ketrem0te.game.Game('/dev/ttyACM0', "testgame", 83, 81, (1,2,3,2,1), 2) - +game = r0ketrem0te.game.Game('/dev/ttyACM0', "testgame", 83, + 81, [ord(x) for x in 'REM0T'], 2, True) queue = Queue.Queue() game.bridge.registerQueue(queue) game.bridge.registerCallback(receivedPacket) From 1448c19e1cd09fdeee04c9f744f673280c29a335 Mon Sep 17 00:00:00 2001 From: schneider Date: Fri, 16 Dec 2011 03:07:36 +0100 Subject: [PATCH 36/37] r_player: removed old code --- firmware/l0dable/r_player.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/firmware/l0dable/r_player.c b/firmware/l0dable/r_player.c index 59513f4..d05b59f 100644 --- a/firmware/l0dable/r_player.c +++ b/firmware/l0dable/r_player.c @@ -267,8 +267,6 @@ void processNickRequest( struct nickrequest *nq) p.c.nick.flags = 0; uint8_t *nick = GLOBAL(nickname); strcpy(p.c.nick.nick, nick); - //p.c.nick.nick[0] = 'S'; - //p.c.nick.nick[1] = 0; nrf_snd_pkt_crc(sizeof(p),(uint8_t*)&p); } From d7dd8237675492c648e4d8e9e1bf6893b6bf1cdb Mon Sep 17 00:00:00 2001 From: schneider Date: Fri, 16 Dec 2011 15:40:16 +0100 Subject: [PATCH 37/37] added .*pyc to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4f6b597..c9e3e5a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ *.a *.swp release +*.pyc