Kontrollera din iRobot skapa med en Palm Pilot (11 / 16 steg)

Steg 11: Programmet, del 3

Nu är det dags för "kärnan" i programmet, filen "robot.c".

Högst upp i filen "robot.c" behöver vi:

#include "robot.h"
#include "string_arrays.c"
#include < PalmTypes.h >
#include < PalmCompatibility.h >
#include < System/SystemPublic.h >
#include < UI/UIPublic.h >

Annullera SetField (UInt16 formID, UInt16 fieldID, MemPtr str);
Int16 Connect();
Booleska Disconnect();
void DisplaySensors();
Boolean MenuHandler (EventPtr händelse);
Boolean SelectFormHandler (EventPtr händelse);
Int16 SendScript (char längd).
void Display (char längd).
Boolean ScriptFormHandler (EventPtr händelse);
Boolean MainFormHandler (EventPtr händelse);
Boolean AppHandleEvent (EventPtr händelse);
void AppEventLoop();
void AppStart();
void AppStop();
UInt32 PilotMain (UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags);
GlobalsUInt16 port = 0;
char prog = 0;
unsigned char skript [30] [2].

I detta ingår generiska Palm OS bibliotekets. Jag är också att förklara alla funktioner jag har använt på toppen, så du inte behöver oroa någon speciell ordning när du redigerar filen robot.c. Längst har jag också några globala variabler som jag använder på olika punkter i programmet.

Funktionen "PilotMain" är inkörsporten till programmet. Det finns olika orsaker till att ett program kan startas på en Palm Pilot, men vi vill bara oroa dig om en; Om programmet startades av användaren PilotMain() börjar resten av programmet.

UInt32 PilotMain (UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags) {
om (cmd == sysAppLaunchCmdNormalLaunch) {
AppStart();
AppEventLoop();
AppStop();
}
Return 0;
}

Funktionerna AppStart() och AppStop() är ganska grundläggande, AppStart() kontrollera att programmet startar ordentligt och AppStop() ser till att programmet stängs renlig.

void AppStart() {
FrmGotoForm(FormMain);
}
void AppStop() {
Disconnect();
FrmCloseAllForms();
}

AppEventLoop() är kärnan i programmet. När något händer AppEventLoop() hämtas "händelse" och försök att hantera den. Först skickas händelsen till några inbyggda funktioner för att prova och låt systemet hantera händelsen. Om systemet inte fullt hanterar händelsen AppEventLoop() så skickas händelsen till våra egna funktioner att försöka hantera det. FrmDispatchEvent() är händelsehanterarfunktionen som vi tilldelats hantera händelser för det aktiva formuläret.

void AppEventLoop() {
EventType händelse;
kort fel;
göra {
EvtGetEvent (& event, 50); Vänta 100 fästingar innan du skickar en nilEvent
Systemet kan attemt att hantera händelser innan du försöker oss själva
om (SysHandleEvent (& event)) fortsätter;
om (MenuHandleEvent ((void*) 0, & event & fel)) fortsätta;
om (AppHandleEvent (& event)) fortsätter;
om (MenuHandler (& event)) fortsätter;
Skicka händelsen till händelsehanteraren form
FrmDispatchEvent(&event);
} medan (event.eType! = appStopEvent);
}

AppHandleEvent() är den första icke-system funktion som AppEventLoop() skickar händelser till. Det kontrollerar om händelsen innebär lastning av någon av formerna. Om så det ställer in i formuläret och tilldelar en standard händelsehanterare (för FrmDispatchEvent()).

Boolean AppHandleEvent (EventPtr händelse) {
FormPtr frm;
Int frmID;
Boolean hanteras = false;
char str [255];
Str [0] = '\0';
om (händelse -> eType == frmLoadEvent) {
frmID = händelse -> data.frmLoad.formID;
frm = FrmInitForm(frmID);
FrmSetActiveForm(frm);
växel (frmID) {
fall FormMain:
FrmSetEventHandler (frm, MainFormHandler);
FrmDrawForm(frm);
StrCat (str, PROGRAM[prog]);
SetField (frmID, FldProg, str);
hanteras = sant;
bryta;
fall FormSelect:
FrmSetEventHandler (frm, SelectFormHandler);
FrmDrawForm(frm);
StrCat (str, DISCRIPIONS[prog]);
SetField (frmID, FldDescription, str);
hanteras = sant;
bryta;
fall FormMacro:
FrmSetEventHandler (frm, ScriptFormHandler);
FrmDrawForm(frm);
hanteras = sant;
bryta;
}
}
avkastning som hanteras,
}

MenuHandler() är nästa funktion AppEventLoop () listan. Om användaren gör en meny val av MenuHandler() kommer att hantera den.

Boolean MenuHandler (EventPtr händelse) {
Boolean hanteras = false;
ERR fela;
Kommandot som kör demo program att corosponds till "prog"
röding data [] = {128, 136, prog};
Handtag på Meny-knappen
om (händelse -> eType == menuEvent) {
MenuEraseStatus(NULL);
Växla (händelse -> data.menu.itemID) {
Gå till verious bildar
fall MnuStatus:
FrmGotoForm(FormMain);
hanteras = sant;
bryta;
fall MnuScript:
FrmGotoForm(FormSelect);
hanteras = sant;
bryta;
fall MnuMacro:
FrmGotoForm(FormMacro);
hanteras = sant;
bryta;
Öppna seriell anslutning
fall MnuConnect:
Connect();
hanteras = sant;
bryta;
Nära seriell anslutning
fall MnuDisconnect:
Disconnect();
hanteras = sant;
bryta;
Starta/stoppa den markerade program eller makro
fall MnuRun:
IF (!. Connect()) semester.
Skicka kommandot till iRobot
SrmSend (port, data, 3, & err);
SrmSendFlush(port);
hanteras = sant;
bryta;
fall MnuStop:
Ersätta den "prog" byten med -1 (255) att stoppa det pågående programmet
data [2] = 255;
Skicka kommandot till iRobot
IF (!. Connect()) semester.
SrmSend (port, data, 3, & err);
SrmSendFlush(port);
hanteras = sant;
bryta;
fall MnuAbout:
FrmAlert(AboutAlert);
hanteras = sant;
bryta;
}
}
avkastning som hanteras,
}

Connect() och Disconnect() används för att öppna och stänga Palm Pilots serieport. Väder eller inte roboten är ansluten till Palm Pilot och aktiverad när dessa funktioner används inte är alltför viktig. Tuff, om roboten inte får kommandot "Start" minst en gång (det skickas när anropas funktionen Connect()) du kan ha några problem att köra egna skript.

Int16 Connect() {
ERR fela;
röding data = 128;
Öppna den seriella porten
om (! port) SrmOpen (serPortCradlePort, 57600 & port);
Skicka kommandot "Start" för att se till att skapa kommer av "Off"-läge
SrmSend (hamn & data, 1, & err);
SrmSendFlush(port);
returnera hamn.
}
Booleska Disconnect() {
Stäng den seriella porten
om (hamn & &! SrmClose(port)) {
Port = 0;
return true;
} annars returnera false;
}

SetFiled() används av olika funktioner för att visa data i textfälten definieras av filen "robot.rcp".

void SetField (UInt16 formID, UInt16 fieldID, MemPtr str) {
FormPtr frm;
FieldPtr fld;
UInt16 obj;
CharPtr p;
VoidHand h;
frm = FrmGetFormPtr(formID);
obj = FrmGetObjectIndex (frm, fieldID);
FLD = (FieldPtr) FrmGetObjectPtr (frm, obj);
h = (VoidHand)FldGetTextHandle(fld);
om (h == NULL) {
h = MemHandleNew (FldGetMaxChars(fld)+1);
ErrFatalDisplayIf (! h, "Inget minne");
}
p = (CharPtr)MemHandleLock(h);
StrCopy (p, str);
MemHandleUnlock(h);
FldSetTextHandle (fld, (handtag) h);
FldDrawField(fld);
}

DisplaySensors() används av huvudformuläret (formuläret "Status") att hämta sensor information från roboten och sedan Visa det för användaren. Det skickas först kommandot "Frågelistan" till roboten med en lista med sensorer som vi är intresserade. DisplaySensors() sedan analysera robotens svar och uppdateras visningen.

void DisplaySensors() {
Datapaketet skickas till skapa som begär sensordata
röding data [] = {128, 149, 8, 8, 7, 9, 10, 11, 12, 22, 21};
Bytematris som innehåller sensor data hämtats av skapa
char inData [9]. char str [20].
UInt16 tmp, * volt;
ERR fela;
VoidHand bitmapHandle;
BitmapPtr bitmappen.
Booleska wheall = false whealr = false; Str [0] = '\0';
om (port) {/ / endast utföra denna slinga om seriell anslutning är öppen
FrmDrawForm(FrmGetActiveForm()); Åter dra formuläret om du vill radera gammal datan
Skicka begäran till skapa för sensordata och lägga svaret i "inData"
SrmReceiveFlush (port, 0);
SrmSend (port, data, 11, & err);
SrmSendFlush(port);
SrmReceive (port, inData, 9, 20, & err);
Väggen sensorn
om (inData[0]) {
bitmapHandle = DmGetResource ("Tbmp", PicWall);
Bitmap = MemHandleLock(bitmapHandle);
WinDrawBitmap (bitmap, 110, 25);
MemHandleUnlock(bitmapHandle);
}
Stötfångaren och wheal droppe sensorer
tmp = (int) inData [1].
om (tmp > = 16) tmp = tmp-16; Caster wheal
om (tmp > = 8) {/ / vänster wheal
wheall = sant;
tmp = tmp - 8.
}
om (tmp > = 4) {/ / höger wheal
whealr = sant;
tmp = tmp - 4.
}
om (tmp > = 2) {/ / vänster wheal
bitmapHandle = DmGetResource ("Tbmp", PicBumpL);
Bitmap = MemHandleLock(bitmapHandle);
WinDrawBitmap (bitmappen, 25, 30).
MemHandleUnlock(bitmapHandle);
tmp = tmp - 2.
}
om (tmp == 1) {/ / höger wheal
bitmapHandle = DmGetResource ("Tbmp", PicBumpR);
Bitmap = MemHandleLock(bitmapHandle);
WinDrawBitmap (bitmappen, 60, 30).
MemHandleUnlock(bitmapHandle);
}
Vi drar wheal efter ritning stötfångare, så den
bilder överlappa ordentligt
om (wheall) {
bitmapHandle = DmGetResource ("Tbmp", PicWhealL);
Bitmap = MemHandleLock(bitmapHandle);
WinDrawBitmap (bitmappen, 37, 52).
MemHandleUnlock(bitmapHandle);
}
om (whealr) {
bitmapHandle = DmGetResource ("Tbmp", PicWhealR);
Bitmap = MemHandleLock(bitmapHandle);
WinDrawBitmap (bitmappen, 71, 52).
MemHandleUnlock(bitmapHandle);
}
4 clif sensorerna
om (inData [2] inData [3] [4] inData + + inData [5] > 0) {
bitmapHandle = DmGetResource ("Tbmp", PicCliff);
Bitmap = MemHandleLock(bitmapHandle);
om (inData [2] > 0)
WinDrawBitmap (bitmappen, 21, 35). Vänster
om (inData [3] > 0)
WinDrawBitmap (bitmap, 31, 25); Överst till vänster
om (inData [4] > 0)
WinDrawBitmap (bitmap, 80, 25); Övre högra
om (inData [5] > 0)
WinDrawBitmap (bitmappen, 90, 35). Höger
MemHandleUnlock(bitmapHandle);
}
Raw batterispänningen
volt = (UInt16 *)(inData + 6);
StrIToA (str, * volt);
StrCat (str, "mV");
om (inData [8] > 0 & & inData [8] < = 3) StrCat (str, "(crg)");
SetField (FormMain, FldVoltage, str);
}
}

MainFormHandler() är funktionen som är tilldelad till formuläret main (status). Den är utformad att hantera händelser specifika för formuläret status. Den individuella formulärhanteraren mest bara hantera händelser orsakade av användaren, men viktigaste formulärhanteraren kommer också svara på nillEvent som genereras varje 50 "fästingar" (en fästing är ett mått på tid som Palm OS använder) av AppEventLoop().

Boolean MainFormHandler (EventPtr händelse) {
Boolean hanteras = false;
Växla (händelse -> eType) {
Varje 50 fästingar vi kontrollera sidan Skapa sensorer (i väntan på en seriell anslutning)
fall nilEvent:
DisplaySensors();
hanteras = sant;
bryta;
fall ctlSelectEvent:
om (händelse -> data.ctlSelect.controlID == BtnDisconnect) {
Disconnect();
hanteras = sant;
}
bryta;
}
avkastning som hanteras,
}

SelectFormHandler() hanterar händelser för formuläret select.

Boolean SelectFormHandler (EventPtr händelse) {
Boolean hanteras = false;
char str [255];
Str [0] = '\0';
Växla (händelse -> eType) {
fall lstSelectEvent:
PROG = händelse -> data.lstSelect.selection;
StrCat (str, DISCRIPIONS[prog]);
SetField (FormSelect, FldDescription, str);
hanteras = sant;
bryta;
}
avkastning som hanteras,
}

ScriptFormHandler() hanterar händelser för formuläret skript. En stor del av ScriptFormHandler() skilja mellan de olika programmera knapparna. Eftersom knappen programmering ID är löpande nummer, istället för att leta menyknapp händelser vi kan leta efter knappen spänner.

Boolean ScriptFormHandler (EventPtr händelse) {
CONST char MAX_SCRIPT = 8;
statiska char längd = 0;
Boolean hanteras = false;
UInt16 id;
Int16 x;
FieldPtr fld;
FormPtr frm;
Växla (händelse -> eType) {
fall frmOpenEvent:
Display(length);
hanteras = sant;
bryta;
Hantera de olika knapparna
fall ctlSelectEvent:
ID = händelse -> data.ctlSelect.controlID;
Växla (id) {
fall BtnSend:
SendScript(length);
hanteras = sant;
bryta;
fall BtnClear:
längd = 0;
Display(length);
hanteras = sant;
bryta;
fall BtnDel:
längd--;
om (längd < 0) längd = 0;
Display(length);
hanteras = sant;
bryta;
}
Skriptet knapparna
om (id > = BtnUp & & id < = BtnPause) {
om (längd < MAX_SCRIPT) {
script [längd] [0] = id-3100;
script [längd] [1] = 1;
längd ++;
}
Display(length);
hanteras = sant;
}
Skriptet tid knapparna
om (id > = BtnP1 & & id < = BtnP16) {
x = skript [längd-1] [1].
skriptet [längd-1] [1] = (x+(id-3200) < = 25)? x+(ID-3200): x;
Display(length);
hanteras = sant;
}
bryta;
Rullningslisten
fall sclRepeatEvent:
x = händelse -> data.sclRepeat.newValue - evenemang -> data.sclRepeat.value;
frm = FrmGetActiveForm();
FLD = (FieldPtr) FrmGetObjectPtr (frm, FrmGetObjectIndex (frm, FldScript));
om (x > = 0) FldScrollField (fld, x, winDown);
annat FldScrollField (fld, x-1, winUp);
bryta;
}
avkastning som hanteras,
}

Funktionen Display() översätter data i matrisen skript till en mänsklig-vänlig sträng och visar det i en textfält som användaren kan se.

Annullera Display (char längd) {
char str [1000];
char x;
char tmp [10].
Str [0] = '\0';
Aktivera skript matrisen till en sträng och Visa den
för (x = 0; x < längd; x ++) {
StrCat (str, COMMANDS[script[x][0]]);
StrCat (str, "för \0");
StrIToA (tmp, script[x][1]);
StrCat (str, tmp);
StrCat (str, "Seconds\n\0");
}
SetField (FormMacro, FldScript, str);
}

Slutligen funktionen SendScript() tar variabeln skript och översätta det till kommandon som iRobot skapa kan förstå och sedan skicka resultatet till din robot. Det kallas på ScriptFormHandler() funktion.

Int16 SendScript (char längd) {
char säker [] = {128, 131};
röding data [150];
röding i.
char x = 0;
char y;
char cmds = 0;
ERR fela;
data [x] = 131;
data [++ x] = 152;
y = ++ x;
för (jag = 0; jag < längd; i ++) {
Switch (script[i][0]) {
fall 0:
data [++ x] = 137;
data [++ x] = 0;
data [++ x] = 150;
data [++ x] = 128;
data [++ x] = 0;
data [++ x] = 155;
data [++ x] = skript [i] [1] * 10;
data [++ x] = 137;
data [++ x] = 0;
data [++ x] = 0;
data [++ x] = 128;
data [++ x] = 0;
cmds += 3.
bryta;
fall 1:
data [++ x] = 137;
data [++ x] = 255;
data [++ x] = 106;
data [++ x] = 128;
data [++ x] = 0;
data [++ x] = 155;
data [++ x] = skript [i] [1] * 10;
data [++ x] = 137;
data [++ x] = 0;
data [++ x] = 0;
data [++ x] = 128;
data [++ x] = 0;
cmds += 3.
bryta;
fall 2:
data [++ x] = 137;
data [++ x] = 0;
data [++ x] = 150;
data [++ x] = 1;
data [++ x] = 44.
data [++ x] = 155;
data [++ x] = skript [i] [1] * 10;
data [++ x] = 137;
data [++ x] = 0;
data [++ x] = 0;
data [++ x] = 128;
data [++ x] = 0;
cmds += 3.
bryta;
fall 3:
data [++ x] = 137;
data [++ x] = 0;
data [++ x] = 150;
data [++ x] = 254;
data [++ x] = 212;
data [++ x] = 155;
data [++ x] = skript [i] [1] * 10;
data [++ x] = 137;
data [++ x] = 0;
data [++ x] = 0;
data [++ x] = 128;
data [++ x] = 0;
cmds += 3.
bryta;
fall 4:
data [++ x] = 137;
data [++ x] = 0;
data [++ x] = 125;
data [++ x] = 0;
data [++ x] = 1;
data [++ x] = 155;
data [++ x] = skript [i] [1] * 10;
data [++ x] = 137;
data [++ x] = 0;
data [++ x] = 0;
data [++ x] = 128;
data [++ x] = 0;
cmds += 3.
bryta;
fall 5:
data [++ x] = 137;
data [++ x] = 0;
data [++ x] = 125;
data [++ x] = 255;
data [++ x] = 255;
data [++ x] = 155;
data [++ x] = skript [i] [1] * 10;
data [++ x] = 137;
data [++ x] = 0;
data [++ x] = 0;
data [++ x] = 128;
data [++ x] = 0;
cmds += 3.
bryta;
fall 6:
data [++ x] = 155;
data [++ x] = skript [i] [1] * 10;
cmds + = 1;
bryta;
}
}
data [y] = x + 1.
Connect();
Skicka manus
SrmSend (port, data, x + 1, & err);
SrmSendWait(port);
SrmSendFlush(port);
återvändande fela;
}

Se Steg
Relaterade Ämnen

Kontrollera din DSLR kamera med iPhone och Arduino BLE modul

Här är 10 minuters instructable för att kontrollera DSLR kamera med Arduino och iPhone.Steg 1: ReservdelslistaEn Arduino UNO eller Mega eller Nano eller etc.Jag använde en HM-10 Bluetooth låg energi modul men jag tror några andra Ble moduler kommer a...

Kontrollera din android-enhet med en wii remote

Hey guys för min tredje instructable jag ska visa dig hur du styr din android-enhet med en wii remotevad du behöver är1 x: wii remote1 x: android-enhetSteg 1: Ladda ner app först måste du gå till google play och hämta en app som kallas wiimote contro...

Kontrollera din nuvarande plats med MediaTek LinkIT en

Det är lätt att hitta koordinaterna för din nuvarande plats med MediaTek LinkIT en.Du behöver:En dator som kör MediaTek SDKMediaTek LinkIt fäst en med GPS-antennSteg 1: Ladda upp kodenDu kan hitta koden under exemplen i MediaTek LinkIT en SDK. Det är...

Kontrollera dina hem apparater med TV-fjärrkontrollen!

Titta på projektets utgång video här.Jag har upptäckt att vissa människor är väldigt lata stå upp gå till styrelse och tryck på knappen för att slå på ljuset eller fläkt eller andra apparat men Hey!, nu finns det en enklare lösning. Nu kan du aktiver...

Kontrollera dina vänner med hjälp av kraften i neurovetenskap (fjärrkontroll mänskliga del II)

I detta Instructable använder vi en 12Sprints Mobile EEG (elektroencefalogram) enhet, en trusty Arduino UNO, en TENS (transkutan elektrisk nervstimulering) enhet och en enkel relä fjärrstyra en mänsklig minion med våra sinnen!Detta Instructable ställ...

Kontrollera din Halloween dekorationer med Arduino

Animerade Halloween rekvisita är mycket roligt. Men rekvisita som du köper i affären har några större begränsningar.Ett problem är att varje prop aktiveras egen givare. Så är det svårt att få dem att samarbeta i unison. Om rekvisita är synkroniserad,...

Kontrollera din värld med din Pi utan programmering

Detta Instructable visar hur du använder en enkel prisvärd förlängning styrelse heter PiFace ovanpå Raspberry Pi för att styra världen runt omkring dig, utan någon teknisk skicklighet.Steg 1: HårdvaraFå först en Raspberry Pi 2 från någon leverantör (...

Styra iRobot skapa genom att använda MSP430fr6989

Detta är vår första instructables handledning genom Mohsine Taarji, Anvesh Loka, Avinash Singh. Vårt projekt är om kontrollerande iRobot skapa med MSP430fr6989 mikrokontroller. IRobot är egentligen en ny programmerbar enhet. Du kan göra alla robotic...

Adaptiv kartläggning och navigering med iRobot skapa

denna handledning visar hur man gör kart- och navigationstjänster med iRobot skapa för under $30! Och ännu bättre, dess utformad att vara ett enkelt tillägg till din redan befintliga robot (butler robot, någon?).Anledningen är att kartlägga användbar...

Enklaste sättet att kontrollera din Arduino med en mobil enhet!

Många projekt som detta innebär som kräver en WiFi sköld eller en Bluetooth-modul. Men, tack vare Blynk, som det är fortfarande möjligt, är det inte längre nödvändigt. Denna handledning kommer att förklara ingående om hur att kontrollera din Arduino...

Hur man gör en autonom basket spela robot med en iRobot skapa som bas

detta är min post för iRobot skapa utmaning. Den svåraste delen av hela denna process för mig var att avgöra vad roboten skulle göra. Jag ville Visa funktionerna i skapa, samtidigt också lägga i några robo flair. Alla mina idéer verkade på antingen f...

Kontrollera din RF försäljningsställen med LinkIt en

Jag har nyligen köpt en uppsättning utlopp vägg timers för några lampor i mitt hus. Även med alla ljusen sväng "på" vid en viss tid är cool, insåg jag att det fanns en hel del nackdelar. Först, medan jag vaknade mycket tidigt på vardagar, jag so...

Kontrollera ditt ljus system med din smarta telefon

Nu kan du styra ditt ljus system i ditt hem med smart-telefonen genom att skriva ett tecken LED slå eller stänga avSteg 1: ProjektidéProjektidé är att bevis på att du kan kontrollera ditt hem belysningssystem med endast din smarta telefon, så lamporn...

Röst kontrollerade iRobot skapa

Jag fick nyligen en android tillbehör utveckling Kit (ADK) och coincidentally, också precis fått min iRobot skapa. Så, jag bestämde mig att sätta de två tillsammans för att få en röststyrd robot med Android API som ett enkelt sätt att utföra tal till...

Ändra en iRobot skapa för att måla

detta är en robotics projekt som antagligen kunde avslutas av någon som har någon erfarenhet med robotar på alla. Jag säger detta eftersom innan jag började, jag hade ingen erfarenhet med robotar. Eller skriva program. I själva verket jag visste hur...

Bygga din egen hemsida med dreamweaver

så jag har tidigare skrivit om detta innan den gamla är värt att läsa först, den innehåller instruktioner om photoshop och dreamweaver som detta inte, men det missar många poäng ut jämfört med den här.Nuförtiden det finns ton av tjänster för att bygg...

Tangentbord Media kontroller för Windows med AutoHotKey

detta är min post för Art of Sound tävling.Detta instructable kommer detalj hur man skapar en uppsättning kortkommandon för Windows XP/Vista som gör att du kan pausa, spela upp, stoppa och hoppa över spår i din mediaspelare utan att behöva navigera t...

Gör din egen Design med DIY LED-belysning

Gör din egen design med DIY belysning LED-lampor med grundläggande delar från den hem förbättring butiken! Detta är ett billigt och flexibelt sätt att skapa ljusa belysning som passar ditt space, oavsett om du designar det för din butik eller ditt he...

Hur man imponera på din betydande andra med en enkel måltid

dessa är stegvisa instruktioner om hur man imponera på din betydande andra med en enkel måltid. Det är inte bara läckra att äta men är förvånansvärt unik och lätt att göra. Även om du ofta befinner dig vilse i köket, bör detta lägga ut processen så a...