Showing posts with label code. Show all posts
Showing posts with label code. Show all posts

January 27, 2012

Getting pyglet setup in OS X 10.7.2


  1. Create a virtual env and activate it
  2. hg clone https://code.google.com/p/pyglet/
  3. pushd pyglet; python setup.py install
  4. popd
  5. easy_install  pyobjc==2.3 # the ==2.3 is required as of 2012-01-27, or pyobjc-core will not install
  6. python -c "import pyglet" # this step should not fail 


Cheers,
Steve

May 18, 2011

Find and svn add all untracked files

svn st | grep '^?' | sed 's/^[? ]*/"/' | sed 's/$/"/' | xargs svn add

Cheers,
Steve

May 08, 2011

Firehol and mDNS


Here is my firehol.conf that allows multicast mDNS packets through:


# define mdns so we will accept it
server_mdns_ports="udp/5353"
client_mdns_ports="5353"


interface eth+ multi
   policy return
   server mdns accept
   server multicast accept                                                                                                        


interface eth+ home src "${home_ips}"
    server  all         accept
    client  all         accept


Initially I had the server mdns accept and server multicast accept inside the home interface, but this didn't work. Firehol's developer, Mr Costa Tsaousis, pointed out that src "${home_ips}" on home would exclude broadcast packets sent from MAC addresses, thus the second interface definition (multi).

Cheers,
Steve

April 18, 2011

TIL: const, NSError, NSApplicationsupportDirectory


  • const char *foo; is not the same as char * const foo; The former declares a pointer to constant char, while the latter declares a constant pointer to char.
  • - [NSError localizedDescription] returns the object for key NSLocalizedDescriptionKey in the error's userInfo dictionary. I always wondered how to set it since there is no setLocalizedDescription:.
  • On iOS the application's Application Support directory doesn't seem to exist by default, unlike the Documents directory. This has to be created programmatically.

Cheers,
Steve

January 27, 2011

iPhone 4 layer export script for Gimp

I often mockup iPhone interfaces on the Gimp, then exporting each layer for use as backgrounds. For iOS 4 each image needs to be exported twice: one at full resolution with @2x in its filename, and once at half-resolution without the @2x. e.g. nav_bg@2x.png and nav_bg.png.

Exporting layers by hand gets old real quick. Thankfully the Gimp supports scripting in my favourite language: python. Even better, Chris Mohler had already written a script to do something very similar, so all I needed to do was make some simple adjustments:
#!/usr/bin/env python
# -*- coding: <utf-8> -*-
# Author: Chris Mohler <cr33dog@gmail.com>
# Copyright 2009 Chris Mohler
# "Only Visible" and filename formatting introduced by mh
# License: GPL v3+
# Version 0.4
# GIMP plugin to export layers as PNGs
# modified by Shuning Bian 2011

from gimpfu import *
import os, re

gettext.install("gimp20-python", gimp.locale_directory, unicode=True)

def format_filename(layer):
    layername = layer.name.decode('utf-8')
    filename1 = layername + "@2x.png"
    filename2 = layername + ".png"
    return filename1,filename2

def get_layers_to_export(img):
    layers = []
    for layer in img.layers:
        if layer.visible:
            layers.append(layer)
    return layers

def export_layers(img, path):
    dupe = img.duplicate()
    savelayers = get_layers_to_export(dupe)
    for layer in dupe.layers:
        layer.visible = 0 
    for layer in dupe.layers:
        if layer in savelayers:
            layer.visible = 1

            if layer.mask:
                pdb.gimp_layer_remove_mask(layer, 0)

            filename1,filename2 = format_filename(layer)
            fullpath = os.path.join(path, filename1);
            tmp = dupe.duplicate()
            pdb.file_png_save(tmp, tmp.layers[0], fullpath, filename1, 0, 9, 1, 1, 1, 1, 1)

            imgwidth = pdb.gimp_image_width(img)
            imgheight = pdb.gimp_image_height(img)
            pdb.gimp_image_scale_full(tmp, imgwidth/2, imgheight/2, 2)

            fullpath = os.path.join(path, filename2)
            pdb.file_png_save(tmp, tmp.layers[0], fullpath, filename2, 0, 9, 1, 1, 1, 1, 1)

        dupe.remove_layer(layer)

            
register(
    proc_name=("python-fu-export-layers-iphone4"),
    blurb=("Export visible layers as PNG"),
    help=("Export all visible layers as individual PNG files."),
    author=("Chris Mohler <cr33dog@gmail.com> + sbian"),
    copyright=("Chris Mohler"),
    date=("2009"),
    label=("as _PNG"),
    imagetypes=("*"),
    params=[
        (PF_IMAGE, "img", "Image", None),
        (PF_DIRNAME, "path", "Save PNGs here", os.getcwd()),
           ],
    results=[],
    function=(export_layers), 
    menu=("<Image>/File/E_xport Layers"), 
    domain=("gimp20-python", gimp.locale_directory)
    )

main()

Cheers,
Steve

January 23, 2011

Socket.IO Framing Protocol

Socket.IO is a great library and framework that doesn't seem to have a good description of the protocol available. In interest of helping others when they google "socket.io framing protocol", this post was born.

The description of the framing protocol used by Socket.IO below is taken from ajaxorg's Socket.IO-node fork on github. I have made some changes with regards to the annotations, specifically that they must be terminated by with newline. This description is current for v0.6+.

Socket.IO Framing Protocol

Socket.IO 's framing protocol is on top of the underlying transport. Messages have the following format:
(message type)":"(content length)":"(data)","
Where message type is one of:

  • 0 for forced disconnect - no data is sent, always sent as 0:0:,
  • 1 for messages - see below
  • 2 for heartbeats - data is an integer, e.g. 2:1:0, 
  • 3 for session ID handshake - data is a session ID, e.g. 3:3:253,


Messages

If you were familiar with the Socket.IO protocol you will notice there appears to be no way now to specify a message contains JSON data as the new protocol has no counter part to the JSON Frame indicator ~j~. Fear not - the new Socket.IO protocol provides a means for this and many other possible extensions via annotations which maybe attachd to messages in a manner reminiscent of HTTP headers. 

A Socket.IO message now has the following format:
[key[:value]["\n"key[:value]]["\n"key[:value]..."\n"]":"[message]
In words: annotations are key-value pairs where the value may be omitted. Individual annotations are delimited by newlines, and annotations are separated from the message by another newline.

From examining the Socket.IO source, if no annotation is present then there should be no newline, but the colon is still required.

A JSON message is sent with a single annotation with key of j and no value. Below is an example taken from a debugging session:
1:128:j
:{"method":"create_user","params":[{"password":"jhhgffufuhgv","email":"h@h.com"}],"id":"83BCA0A8-7F9D-428A-A546-2EFCBDC20AB3"},
Note there is a newline after the j. The official Socket.IO client uses an additional annotation: r for realm. e.g.
1:18:r:chat
:Hello world,
Note the newline after chat.

Cheers,
Steve

January 16, 2011

UIGraphicsBeginImageContext on iPhone 4

I generate UIImages in some of my iOS applications, and prior to the iPhone 4 they all worked fine. With the introduction of the iPhone 4 and Retina Display, I realised my generated images were blurry even though I am rendering them at twice the resolution.

It turns out this is due to the scale property of my generated UIImages being set to 1. This is mainly due to my use of UIGraphicsBeginImageContext(), which creates a bitmap context that is a) non-opaque and b) scale=1. The fix is quite simple - use UIGraphicsBeginImageContextWithOptions() and specify the scale appropriately.

For images derived from an existing image, e.g. rotating an existing image, it is sufficient to use the scale property of UIImage. For images created "from scratch", I use the following bit of code:
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)])
{
  UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
}
else UIGraphicsBeginImageContext(size);

Cheers,
Steve

September 21, 2010

Thoughts on HTML, computer programs, and programming languages

I would like to start this post off by saying that emotionally I agree with the sentiment that HTML is not a programming language. Intellectually, however, my position is that HTML is a programming language.

My argument for HTML as a programming language is very abstract, and to put it simply: programming languages are used to express instructions to the computer on what to do. HTML instructs the computer on how to display content, therefore HTML is a programming language. Against this argument are the following, which can be encountered on any number of programming websites.

The most common argument against HTML being a programming language is that it describes itself as a markup language, as evident by the M in HTML. I find this argument wanting on 2 fronts: a) it implicitly assumes that a markup language can not be a programming language. TEX is a good example of a language which 99% of the time is used for markup, and the other 1% for programming; b) this argument is superficial. It implies if I was to simply rename HTML to HTPL, hypertext programming language, then *poof* now it is a programming language without having changed any of its characteristics. A rose by any other name...

A better argument is that HTML doesn't have control structures, whereas programming languages do. Suppose we accept this argument, which implicitly requires programming languages to support control structures. Consider a strict subset of LOGO, called miniLOGO that contains only the turtle graphics part of LOGO and nothing else - no loops, no conditionals, no control structures. By the definition we have assumed here, miniLOGO isn't a programming language. Now suppose you write a program in miniLOGO to draw "hello world"  on the screen, what have you written? I (and most people) say it is a program. This then presents a problem: you should not be able to write a program with a not-programming-language. At least it is a problem for me.

Suppose now you say no, the miniLOGO hello world isn't a program, and you give one of two reasons - that it doesn't contain control structures or that it wasn't written in a programming language. The first reason implicitly defines all programs as those containing control structures. I can not accept this reason because you can make a non-program a program by injecting a control structure with no side effect, and it is no less absurd and no more useful than the accepting HTML as a programming language. Not to mention the millions of introductory programming texts that will need to be rewritten so "hello world" contains an unecessary control structure. (There are those who will then say: there ARE control structures, they are just a few levels of abstractions down. Well there are similar control structures in a browser. We do not worry about the rest of the abstraction stack, only the top of it).

For the second reason, consider what happens if I write the same program in full is-a-programming-language LOGO. Now it is a program by the virtue of having been written in a programming language, which brings us to an uncomfortable place: now we two things which are absolutely identical, but one is a program and one isn't by virtue of their parentage. Accepting this position is no more absurd than accepting that HTML is a programming language.

The final argument I will discuss is that my definition is such a generic definition just about anything is a programming language, therefore that definition is next to useless. This I agree with. However as I have (hopefully) demonstrated, it isn't easy to come up with a definition of what is a programming language (or in fact what is a program) that isn't contradictory or would invalidate millions of simple programs around the world.

I am still giving this issue thought, but until I am a more learned person in CS and possibly philosophy, my choice is between a definition of programming languages with low discriminatory powers, or ones which are fickle and contradictory.

HTML-is-a-programming-language, I choose you!

Cheers,
Steve

June 09, 2010

Script to make IPAs for ad-hoc distribution.

This script produces an IPA, then verifies to make sure the IPA is actually valid.
PRODUCT_NAME=""
IPA="${PRODUCT_NAME}.ipa"
APP="${PRODUCT_NAME}.app"

if [ -z "${PRODUCT_NAME}" ]; then
        echo "PRODUCT_NAME not set";
        exit 1;
fi

rm -rf ipa
mkdir -p ipa/Payload &&
cp -R build/Debug-iphoneos/${APP} ipa/Payload/ &&
pushd ipa &&
zip -q --symlinks -r ${IPA} Payload/ &&
mkdir test/ &&
cp ${IPA} test &&
cd test &&
unzip -q ${IPA} &&
cd Payload/ &&
codesign --verify --verbose ${APP} &&
popd
open ipa/
Designed to be ran from the command line, but it should be easily made into a build phase.

Cheers,
Steve

March 26, 2010

Notes on PHD Guiding


  • Maximum RA guide pulse is 1000ms (1s). If PHD is not locking on to the star and it says "dur=1000" in the status bar, a better polar alignment is required.
  • RA hysteresis is used in a 2-term weighted moving average:
RA_dist = (1.0 - RA_hysteresis) * RA_dist + RA_hysteresis * last_guide

Cheers,
Steve

March 11, 2010

Auto-generating CoreData Classes

Screenshot2010-03-11at6.42.57PM.eAfsUHuqsZyt.jpg
If you have a xcdatamodel file, open it, and using the menu: File > New File > Cocoa Class, you will see an extra item: Managed Object Class. This will autogenerate the required header and implementation files for your CoreData entities so you can use the convenience accessors instead of [setValue:forKey:]. Doing this will also update your data model so your entities won’t have NSManagedObject as their class. Instead their class is the corresponding generated class.

The thing is, you won’t see “Managed Object Class” item unless you have opened the xcdatamodel file and it is the front most window.

Naturally I discovered this by accident just as I was about to hand write the last CoreData model class.

Cheers,
Steve

December 31, 2009

Order Matters With XCode's Build Phrases

I use a custom script to insert the current git commit into GeoNote, so when I get bug reports I have a better idea of which version the user is running. The script is as below:

import os
from Foundation import NSMutableDictionary

version = os.popen4("/sw/bin/git rev-parse --short HEAD")[1].read()
info = os.environ['INFOPLIST_FILE']
print info
plist = NSMutableDictionary.dictionaryWithContentsOfFile_(info)
print plist
plist['revision'] = version[:-1]
plist.writeToFile_atomically_(info, 1)

This was added as a Run Script Build Phrase. The problem I noticed was that the commit short hash inserted into Info.plist was always one commit behind. After some head scratching, I realised this was because by default the new build phrase is inserted last, and the order matters! It isn't actually possible to reorder build phrases by drag and dropping the children nodes around. You have to do a head-insert by dragging a child to the parent, which inserts it at the top.


Voila, problem solved.

Cheers,
Steve

December 16, 2009

Yet Another Arduino Float Print Function

Note: in arduino-0017, floating point printing is supported by default. The function below is not necessary.

void floatPrint(float f, int places=6)
{
        int _d;
        if (f < 0)
        {
                Serial.print("-");
                f*=-1;
        }

        _d = (int)f;
        
        if (!places)
        {
                return;
        }

        Serial.print(_d, DEC);
        Serial.print('.');

        while(places--)
        {
                f-=_d;
                f*=10;
                _d = (int)f;
                Serial.print(_d, DEC);
        }
}

void floatPrintln(double f, int places=6)
{
        floatPrint(f, places);
        Serial.println();
}
Why another float print function? The ones I found wasn't too nice, one of which required long integers. Yuck. It was also fun, and now I know where to look for one in the future :P
Cheers,
Steve

December 07, 2009

A subtle source of linker errors under XCode



If one source file is sourcecode.c.objc and another is sourcecode.cpp.objcpp, you will have problems if you try to call function defined in one file from the other. The resolve this either make them the same source type, or follow this guide.



This drove me nutty because the template I was working off has code set to sourcecode.cpp.objcpp, but XCode adds new classes as sourcecode.c.objc! To check the file type, use "Get Info" in the source file's context menu.



Cheers,

Steve

October 11, 2009

qrbackup

I generated a gpg keypair for myself today, and I was looking for a fairly safe way to back it up. I don't particularly trust DVD/CDs, and keeping it on flash is even more worrying. I wanted a means of backup I can see and touch.



Paperbak would be great if it was ported to something not windows. Since it wasn't, I settled on QR Code.



Thus qrbackup was born. It will base32 encode a file, then encode it into QR codes using google chart service.



I have tested it from backup to restoration, and it works. YMMV, more instructions available after the jump.




Cheers,

Steve



P.S. Pardon my python.

August 05, 2009

git-daemon on debian vserver

Annoyingly git-daemon-run requires runit on debian, but runit will fail to install properly in a debian vserver because it doesn't have init.



One solution is to reconfigure vserver to use plain init style.



However I didn't want to this because I don't want to take down my vserver just yet. So here is the required line for /etc/inetd.conf:




git stream tcp nowait nobody /usr/bin/git git daemon --inetd /var/git-repos



Cheers,

Steve

June 07, 2009

Dealing with rkhunter warnings

rkhunter often warns on file property changes after upgrade and such, and sometimes you just aren't sure whether it is due to recent upgrades, or because you really were compromised. The following script was written to compare the checksum of all files rkhunter warns about against the originals in a debian repository.



The latest version of this is available in my script.git respos.




#!/bin/bash
desc="
This script will verify whether files for which rkhunter has logged a
warning for is still valid. It does this by finding which debian package
it came out of, and downloads them, unpacks them, then checks
the checksums.

Run it by supplying a rkhunter log file as first argument
"

HASHER="sha256sum"

IFS="
"
function find_suspect_files
{
echo "parsing $1 for suspect files" 1>&2
grep -1 Warning "$1"| grep File | sed 's|.*File: ||'
}

function find_packages
{
echo "finding packages" 1>&2
for suspect_file in $1
do
package=$(dpkg -S $suspect_file|awk '{print $1}'|sed 's/.$//')
echo "suspect file $suspect_file found in $package" 1>&2
echo $package
done

}

function make_aptitude_args
{
echo "generating aptitude arguments" 1>&2
for package in $1
do
version=$(dpkg -p $package | grep Version | awk '{print $2}')
echo $package=$version
done
}

function cleanup
{
echo "cleaning up"
popd
rm -rf tmp
exit $1
}

function setup
{
echo "setting up"
rm -rf tmp
mkdir tmp
pushd tmp
}

if [ $# -ne 1 ];
then
echo "$desc"
exit 1
fi

suspect_files=$(find_suspect_files "$1")

packages=$(find_packages "$suspect_files" | sort | uniq)

if [ -z "$packages" ];
then
echo "***WARNING****"
echo "No packages contain any of the suspect files!"
cleanup 1
fi

aptitude_args=$(make_aptitude_args "$packages")

setup

echo "downloading packages"
aptitude download $aptitude_args 1>/dev/null
if [ $? -ne 0 ];
then
echo "aptitude download failed!"
echo "args=$aptitude_args"
cleanup 1
fi

echo "unpacking"
for deb_file in *.deb
do
ar -x $deb_file
tar zxf data.tar.gz
rm -rf data.tar.gz control.tar.gz
done

for suspect_file in $suspect_files
do
if [ ! -f ".$suspect_file" ]
then
echo "***WARNING****"
echo "For some reason .$suspect_file does not exis!"
continue
fi
echo -n "verifying $suspect_file... "
suspect_sum=$($HASHER $suspect_file | awk '{print $1}')
clean_sum=$($HASHER ".$suspect_file" | awk '{print $1}')
if [ $suspect_sum == $clean_sum ];
then
echo "OK"
else
echo
echo "***WARNING****"
echo "Checksum mistmatch for $suspect_file!!!"
echo "Should be: $clean_sum"
echo "Is: $suspect_sum"
fi
done
cleanup


Cheers,

Steve

May 18, 2009

Sketch to calibrate SEN-08663

Got my hands on a ADJD-S371 on a breakout board from Sparkfun. The code below can be used to calibrate it. The most up-to-date version of the code can be found at my git repository under colour_sensor_calibration.




#include <Wire.h>
/* Calibrates the sensor to get white balance. Pin 2 should be connected
* to LED on the breakout board's LED pin. Calibration is done by placing the
* breakout board inside a pin pong ball, and using the built-in LED for
* illumination.
*
* Amount of light is measured by charging N capacitors for time X then
* reading off the voltage. (Conjecture)
*
* N is controlled by CAP_XXX
* T is controlled by INT_XXX
*
* Calibration is done by adjusting the integration time. No real reason.
*/
int _slave_id = 0x74;
int _LED_pin = 2;

uint8_t read_register(uint8_t addr)
{
i2c_send(_slave_id, &addr, 1);
return i2c_read(_slave_id);
}

void write_register_int(uint8_t addr, int data)
{
write_register_multibyte(addr, (uint8_t*)&data, 2);
}

/* write data[i] = register+i */
void write_register_multibyte(uint8_t addr, uint8_t* data, uint8_t bytes)
{
for (int i = 0; i < bytes; ++i)
{
write_register(addr+i, data[i]);
}
}

void write_register(uint8_t addr, uint8_t data)
{
uint8_t bytes[] = {addr, data};
i2c_send(_slave_id, bytes, 2);
}

uint8_t i2c_read(uint8_t id)
{
Wire.requestFrom(_slave_id, 1);
for(int i = 0; i<10 && !Wire.available(); ++i, delay(10));
if (!Wire.available())
{
return 11;
}
return Wire.receive();

}

void i2c_send(uint8_t id, uint8_t * data, uint8_t len)
{
Wire.beginTransmission(id);
for(int i = 0; i < len; ++i)
{
Wire.send(data[i]);
}
Wire.endTransmission();
}

#define CTRL 0x00
#define CONFIG 0x01

#define CAP_RED 0x06
#define CAP_GREEN 0x07
#define CAP_BLUE 0x08

#define INT_RED_LO 0x0A
#define INT_RED_HI 0x0B
#define INT_GREEN_LO 0x0C
#define INT_GREEN_HI 0x0D
#define INT_BLUE_LO 0x0E
#define INT_BLUE_HI 0x0F

#define DATA_RED_LO 0x40
#define DATA_RED_HI 0x41
#define DATA_GREEN_LO 0x42
#define DATA_GREEN_HI 0x43
#define DATA_BLUE_LO 0x44
#define DATA_BLUE_HI 0x45

int read_colour(uint8_t low_addr)
{
int lo = read_register(low_addr);
int hi = read_register(low_addr+1);

return lo|(hi<<8);
}

int red_integration_time = 2048;
int green_integration_time = 2048;
int blue_integration_time = 2048;

void set_integration_times(int red, int green, int blue)
{
write_register_int(INT_RED_LO, red);
write_register_int(INT_GREEN_LO, green);
write_register_int(INT_BLUE_LO, blue);
}

void setup()
{
pinMode(_LED_pin, OUTPUT);
digitalWrite(_LED_pin, HIGH);

Serial.begin(57600);
Wire.begin(); // join i2c bus (address optional for master)
Serial.println("Setting up...");

// datasheet says, wait 10us for hardware reset, so lets wait 1000
delay(1);

// gain setup
write_register(CAP_RED, 0x08);
write_register(CAP_GREEN, 0x08);
write_register(CAP_BLUE, 0x08);

set_integration_times(
red_integration_time,
green_integration_time,
blue_integration_time
);

// ask for colour data and offset
write_register(CTRL, 0x01);
}

void loop()
{
if (read_register(CTRL))
{
return;
}

int red, green, blue;
red = read_colour(DATA_RED_LO);
green = read_colour(DATA_GREEN_LO);
blue = read_colour(DATA_BLUE_LO);

Serial.println("--------------");
Serial.print("red: ");Serial.println(red);
Serial.print("green: ");Serial.println(green);
Serial.print("blue: ");Serial.println(blue);


Serial.print("red_int: ");Serial.println(red_integration_time);
Serial.print("green_int: ");Serial.println(green_integration_time);
Serial.print("blue_int: ");Serial.println(blue_integration_time);

// have to calibrate against blue, because LED has a blue bias otherwise
// it would look like blue has high gain than it does
float P = 1;
int reference = blue;
red_integration_time += (reference - red)*P;
green_integration_time += (reference - green)*P;
blue_integration_time += (reference - blue)*P;

// set the new integration times
set_integration_times(
red_integration_time,
green_integration_time,
blue_integration_time
);

// ask for colour data again
write_register(CTRL, 0x01);
}


Cheers,

Steve

May 08, 2009

Facebook python authentication gateway

Edit: it occurred to me what I have below is the basics of a thin facebook api wrapper. I might make it into one at some point in the future.



If you don't know what this does, you don't need it. Hope this helps some one. Written because pyfacebook is broken, always returns error 100.




FB_API_HOST="api.facebook.com"
FB_API_PATH="/restserver.php"

def get_session(auth_token):
params={
"api_key":FB_API_KEY,
"v":"1.0",
"auth_token":auth_token,
"generate_session_secret":1,
"method":"auth.getSession",
}

sorted = params.items()
sorted.sort(key=lambda x:x[0])

str_to_hash = ''.join(["%s=%s"%(x[0], x[1]) for x in sorted])
str_to_hash += FB_API_SECRET

md5 = hashlib.md5()
md5.update(str_to_hash)

sig = md5.hexdigest()

params["sig"] = sig

encoded_params = urllib.urlencode(params)
headers = {
"Content-type":"application/x-www-form-urlencoded",
}

conn = httplib.HTTPConnection(FB_API_HOST)
conn.request("POST", FB_API_PATH, encoded_params, headers)

response = conn.getresponse()

print response.status, response.reason
return response.read()


This Works For Me when I use it with iphone facebook-connect client:



[FBSession sessionForApplication:myApiKey getSessionProxy:myURL delegate:self];


Cheers,

Steve

April 29, 2009

Stripping trailing whitespace from XCode




Firstly, my usual approach of s/\s+$//g doesn't work since it eats up the newline too. s/\s+$/\n/g doesn't work either because the Replace: field in the find dialogue doesn't escape the \n.



Final solution is as shown: s/[ \t]+$//g




Cheers,

Steve